/// <summary> /// Initializes a new instance of the <see cref="ReadBufferIoResult"/> class. /// Constructor, given a plain IoResult, plus type and origin. /// </summary> /// <param name="r">Plain IoResult.</param> /// <param name="type">ASCII or EBCDIC mode.</param> /// <param name="origin">Coordinate origin.</param> public ReadBufferIoResult(IoResult r, ReadBufferType type, int origin) : base(r) { // Initialize the ReadBufferType. this.ReadBufferType = type; this.Origin = origin; }
/// <summary> /// Initializes a new instance of the <see cref="IoResult"/> class. /// Cloning constructor. /// </summary> /// <param name="r">IoResult to clone.</param> public IoResult(IoResult r) { this.Success = r.Success; this.Result = r.Result; this.Command = r.Command; this.StatusLine = r.StatusLine; this.ExecutionTime = r.ExecutionTime; this.Encoding = r.Encoding; }
/// <summary> /// Store a new command status. /// </summary> /// <param name="result">Result to store.</param> /// <returns>The <paramref name="result"/> parameter.</returns> private IoResult SaveRecentCommand(IoResult result) { lock (this.recentCommandsLock) { this.recentCommands.AddFirst(result); if (this.recentCommands.Count() > MaxCommands) { this.recentCommands.RemoveLast(); } } return(result); }
/// <summary> /// Run the emulator Query action, asynchronous version. /// </summary> /// <param name="queryType">Type of query.</param> /// <returns>Success/failure and failure text.</returns> /// <exception cref="InvalidOperationException">Session is not started.</exception> /// <exception cref="X3270ifCommandException"><see cref="ExceptionMode"/> is enabled and the command fails.</exception> public async Task <IoResult> QueryAsync(QueryType queryType) { var result = await this.IoAsync("Query(" + queryType.ToString() + ")").ConfigureAwait(continueOnCapturedContext: false); if (queryType == QueryType.Cursor && result.Success) { // Translate the cursor coordinates. // This violates the idea that the ioResult always contains the actual value returned by the emulator, // but because we clone the result here, the history will have a reference to the original value. var rowCol = result.Result[0].Split(' '); result = new IoResult(result); result.Result = new[] { string.Format("{0} {1}", int.Parse(rowCol[0]) + this.Config.Origin, int.Parse(rowCol[1]) + this.Config.Origin) }; } return(result); }
/// <summary> /// Basic emulator I/O function, asynchronous version. /// Given a command string and an optional timeout, send it and return the reply. /// The emulator status is saved in the session and can be queried after. /// </summary> /// <param name="command">The command and parameters to pass to the emulator. /// Must be formatted correctly for the emulator. This method does no translation.</param> /// <param name="timeoutMsec">Optional timeout. The emulator session will be stopped if the timeout expires, so this /// is a dead-man timer, not to be used casually.</param> /// <param name="isModify">True if command modifies the host</param> /// <returns>I/O result.</returns> /// <exception cref="InvalidOperationException">Session is not started.</exception> /// <exception cref="X3270ifCommandException"><see cref="ExceptionMode"/> is enabled and the command fails.</exception> /// <exception cref="ArgumentException"><paramref name="command"/> contains control characters.</exception> public async Task <IoResult> IoAsync(string command, int?timeoutMsec = null, bool isModify = false) { string[] reply = null; if (!this.EmulatorRunning) { throw new InvalidOperationException("Not running"); } // If this is a screen-modifying command, see if a forced failure is in order. if (isModify) { var result = this.ForceModifyFailure(); if (!result.Success) { if (this.ExceptionMode) { throw new X3270ifCommandException(result.Result[0]); } return(result); } } // Control characters are verboten. if (command.Any(c => char.IsControl(c))) { throw new ArgumentException("command contains control character(s)"); } Util.Log("Io: command '{0}'", command); // Write out the command. byte[] nl = this.encoding.GetBytes(command + "\n"); var stopwatch = new Stopwatch(); stopwatch.Start(); var state = IoStates.Waiting; var accum = new StringBuilder(); var tokenSource = new CancellationTokenSource(); Task timeoutTask = null; try { var ns = this.Client.GetStream(); await ns.WriteAsync(nl, 0, nl.Length).ConfigureAwait(continueOnCapturedContext: false); // Create a task to time out the read operations after <n> seconds, in case the emulator hangs or we get confused. int thisTimeoutMsec = timeoutMsec ?? this.Config.DefaultTimeoutMsec; if (thisTimeoutMsec > 0) { timeoutTask = Task.Run(async delegate { // When the timeout expires, close the socket. await Task.Delay(thisTimeoutMsec, tokenSource.Token).ConfigureAwait(continueOnCapturedContext: false); this.Client.Close(); }); } // Read until we get a prompt. var buf = new byte[1024]; while (state == IoStates.Waiting) { var nr = await ns.ReadAsync(buf, 0, buf.Length).ConfigureAwait(continueOnCapturedContext: false); if (nr == 0) { state = IoStates.Crashed; break; } accum.Append(this.encoding.GetString(buf, 0, nr)); if (accum.ToString().EndsWith("\nok\n")) { state = IoStates.Succeeded; } else if (accum.ToString().EndsWith("\nerror\n")) { state = IoStates.Failed; } } } catch (ObjectDisposedException) { // This seems to happen when we Close a TcpClient. reply = new[] { "Operation timed out" }; state = IoStates.Crashed; } catch (SocketException) { // Server process died, for example. reply = new[] { "Socket exception" }; state = IoStates.Crashed; } catch (InvalidOperationException) { // Also happens when the server process dies; the socket is no longer valid. reply = new[] { "Invalid operation exception" }; state = IoStates.Crashed; } catch (System.IO.IOException) { // Also happens when the server process dies; the socket is no longer valid. reply = new[] { "I/O exception" }; state = IoStates.Crashed; } // All done talking to the server. Stop timing. stopwatch.Stop(); if (timeoutTask != null) { // Cancel the timeout. Yes, there is a race here, but if we lose, it means that // the emulator took a long time to answer, and something is wrong. tokenSource.Cancel(); // Collect the status of the timeout task. try { timeoutTask.Wait(); } catch (Exception) { } } Util.Log("Io: {0}, got '{1}'", state, accum.ToString().Replace("\n", "<\\n>")); string statusLine = null; if (state != IoStates.Crashed) { // The array looks like: // data: xxx // data: ... // status line // ok or error // (empty line) // Remember the status. reply = accum.ToString().Split('\n'); var nlines = reply.Length; statusLine = reply[nlines - 3]; // Return everything else, removing the "data: " from the beginning. Array.Resize(ref reply, nlines - 3); for (int i = 0; i < reply.Length; i++) { const string DataPrefix = "data: "; if (reply[i].StartsWith(DataPrefix)) { reply[i] = reply[i].Substring(DataPrefix.Length); } } } // In Exception Mode, throw a descriptive error if anything ever fails. try { if (this.ExceptionMode && state != IoStates.Succeeded) { var commandAndArgs = command.Split(new char[] { ' ', '(', ')' }, StringSplitOptions.RemoveEmptyEntries); string commandName; if (commandAndArgs.Length > 0 && !string.IsNullOrEmpty(commandAndArgs[0])) { commandName = commandAndArgs[0]; } else { commandName = "(empty)"; } string failureMessage = string.Format("Command {0} failed:", commandName); if (state == IoStates.Failed) { foreach (string r in reply) { failureMessage += " " + r; } } else { failureMessage += " Timeout or socket EOF"; } throw new X3270ifCommandException(failureMessage); } } finally { // Close the session, whether or not we throw an exception (but after // we test for an exception), if the server crashed. if (state == IoStates.Crashed) { this.Close(saveHistory: true); } } // Save the original response in history. var ioResult = new IoResult { Success = state == IoStates.Succeeded, Result = reply, Command = command, StatusLine = statusLine, ExecutionTime = stopwatch.Elapsed, Encoding = this.encoding }; this.SaveRecentCommand(ioResult); this.lastStatusLine = statusLine; if (this.Config.Origin == 0 || statusLine == null) { return(ioResult); } // Edit the cursor position in the status line for Origin and return it. var statusFields = statusLine.Split(' '); statusFields[(int)StatusLineField.CursorRow] = (int.Parse(statusFields[(int)StatusLineField.CursorRow]) + this.Config.Origin).ToString(); statusFields[(int)StatusLineField.CursorColumn] = (int.Parse(statusFields[(int)StatusLineField.CursorColumn]) + this.Config.Origin).ToString(); this.lastStatusLine = string.Join(" ", statusFields); var retIoResult = new IoResult(ioResult); retIoResult.StatusLine = this.lastStatusLine; return(retIoResult); }
/// <summary> /// Initializes a new instance of the <see cref="EbcdicIoResult"/> class. /// </summary> /// <param name="r">Result from an <c>Ebcdic</c> call.</param> public EbcdicIoResult(IoResult r) : base(r) { }