< Summary

Information
Class: Renci.SshNet.Shell
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\Shell.cs
Line coverage
61%
Covered lines: 92
Uncovered lines: 57
Coverable lines: 149
Total lines: 307
Line coverage: 61.7%
Branch coverage
34%
Covered branches: 11
Total branches: 32
Branch coverage: 34.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
get_IsStarted()100%1100%
.ctor(...)100%1100%
Start()50%685.71%
Stop()50%466.66%
Session_ErrorOccured(...)100%10%
RaiseError(...)0%20%
Session_Disconnected(...)100%10%
Channel_ExtendedDataReceived(...)0%20%
Channel_DataReceived(...)50%2100%
Channel_Closed(...)50%472.72%
UnsubscribeFromSessionEvents(...)50%271.42%
Dispose()100%10%
Dispose(...)20%1014.28%
Finalize()100%1100%

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\Shell.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Threading;
 5
 6using Renci.SshNet.Abstractions;
 7using Renci.SshNet.Channels;
 8using Renci.SshNet.Common;
 9
 10namespace Renci.SshNet
 11{
 12    /// <summary>
 13    /// Represents instance of the SSH shell object.
 14    /// </summary>
 15    public class Shell : IDisposable
 16    {
 17        private readonly ISession _session;
 18        private readonly string _terminalName;
 19        private readonly uint _columns;
 20        private readonly uint _rows;
 21        private readonly uint _width;
 22        private readonly uint _height;
 23        private readonly IDictionary<TerminalModes, uint> _terminalModes;
 24        private readonly Stream _outputStream;
 25        private readonly Stream _extendedOutputStream;
 26        private readonly int _bufferSize;
 27        private ManualResetEvent _dataReaderTaskCompleted;
 28        private IChannelSession _channel;
 29        private AutoResetEvent _channelClosedWaitHandle;
 30        private Stream _input;
 31
 32        /// <summary>
 33        /// Gets a value indicating whether this shell is started.
 34        /// </summary>
 35        /// <value>
 36        /// <see langword="true"/> if started is started; otherwise, <see langword="false"/>.
 37        /// </value>
 338        public bool IsStarted { get; private set; }
 39
 40        /// <summary>
 41        /// Occurs when shell is starting.
 42        /// </summary>
 43        public event EventHandler<EventArgs> Starting;
 44
 45        /// <summary>
 46        /// Occurs when shell is started.
 47        /// </summary>
 48        public event EventHandler<EventArgs> Started;
 49
 50        /// <summary>
 51        /// Occurs when shell is stopping.
 52        /// </summary>
 53        public event EventHandler<EventArgs> Stopping;
 54
 55        /// <summary>
 56        /// Occurs when shell is stopped.
 57        /// </summary>
 58        public event EventHandler<EventArgs> Stopped;
 59
 60        /// <summary>
 61        /// Occurs when an error occurred.
 62        /// </summary>
 63        public event EventHandler<ExceptionEventArgs> ErrorOccurred;
 64
 65        /// <summary>
 66        /// Initializes a new instance of the <see cref="Shell"/> class.
 67        /// </summary>
 68        /// <param name="session">The session.</param>
 69        /// <param name="input">The input.</param>
 70        /// <param name="output">The output.</param>
 71        /// <param name="extendedOutput">The extended output.</param>
 72        /// <param name="terminalName">Name of the terminal.</param>
 73        /// <param name="columns">The columns.</param>
 74        /// <param name="rows">The rows.</param>
 75        /// <param name="width">The width.</param>
 76        /// <param name="height">The height.</param>
 77        /// <param name="terminalModes">The terminal modes.</param>
 78        /// <param name="bufferSize">Size of the buffer for output stream.</param>
 179        internal Shell(ISession session, Stream input, Stream output, Stream extendedOutput, string terminalName, uint c
 180        {
 181            _session = session;
 182            _input = input;
 183            _outputStream = output;
 184            _extendedOutputStream = extendedOutput;
 185            _terminalName = terminalName;
 186            _columns = columns;
 187            _rows = rows;
 188            _width = width;
 189            _height = height;
 190            _terminalModes = terminalModes;
 191            _bufferSize = bufferSize;
 192        }
 93
 94        /// <summary>
 95        /// Starts this shell.
 96        /// </summary>
 97        /// <exception cref="SshException">Shell is started.</exception>
 98        public void Start()
 199        {
 1100            if (IsStarted)
 0101            {
 0102                throw new SshException("Shell is started.");
 103            }
 104
 1105            Starting?.Invoke(this, EventArgs.Empty);
 106
 1107            _channel = _session.CreateChannelSession();
 1108            _channel.DataReceived += Channel_DataReceived;
 1109            _channel.ExtendedDataReceived += Channel_ExtendedDataReceived;
 1110            _channel.Closed += Channel_Closed;
 1111            _session.Disconnected += Session_Disconnected;
 1112            _session.ErrorOccured += Session_ErrorOccured;
 113
 1114            _channel.Open();
 1115            _ = _channel.SendPseudoTerminalRequest(_terminalName, _columns, _rows, _width, _height, _terminalModes);
 1116            _ = _channel.SendShellRequest();
 117
 1118            _channelClosedWaitHandle = new AutoResetEvent(initialState: false);
 119
 120            // Start input stream listener
 1121            _dataReaderTaskCompleted = new ManualResetEvent(initialState: false);
 1122            ThreadAbstraction.ExecuteThread(() =>
 1123            {
 1124                try
 1125                {
 1126                    var buffer = new byte[_bufferSize];
 1127
 7146128                    while (_channel.IsOpen)
 7145129                    {
 7145130                        var readTask = _input.ReadAsync(buffer, 0, buffer.Length);
 7145131                        var readWaitHandle = ((IAsyncResult) readTask).AsyncWaitHandle;
 1132
 7145133                        if (WaitHandle.WaitAny(new[] { readWaitHandle, _channelClosedWaitHandle }) == 0)
 7145134                        {
 7145135                            var read = readTask.GetAwaiter().GetResult();
 7145136                            _channel.SendData(buffer, 0, read);
 7145137                            continue;
 1138                        }
 1139
 0140                        break;
 1141                    }
 1142                }
 0143                catch (Exception exp)
 0144                {
 0145                    RaiseError(new ExceptionEventArgs(exp));
 0146                }
 1147                finally
 1148                {
 1149                    _ = _dataReaderTaskCompleted.Set();
 1150                }
 2151            });
 152
 1153            IsStarted = true;
 154
 1155            Started?.Invoke(this, EventArgs.Empty);
 1156        }
 157
 158        /// <summary>
 159        /// Stops this shell.
 160        /// </summary>
 161        /// <exception cref="SshException">Shell is not started.</exception>
 162        public void Stop()
 1163        {
 1164            if (!IsStarted)
 0165            {
 0166                throw new SshException("Shell is not started.");
 167            }
 168
 1169            _channel?.Dispose();
 1170        }
 171
 172        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
 0173        {
 0174            RaiseError(e);
 0175        }
 176
 177        private void RaiseError(ExceptionEventArgs e)
 0178        {
 0179            ErrorOccurred?.Invoke(this, e);
 0180        }
 181
 182        private void Session_Disconnected(object sender, EventArgs e)
 0183        {
 0184            Stop();
 0185        }
 186
 187        private void Channel_ExtendedDataReceived(object sender, ChannelExtendedDataEventArgs e)
 0188        {
 0189            _extendedOutputStream?.Write(e.Data, 0, e.Data.Length);
 0190        }
 191
 192        private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
 1193        {
 1194            _outputStream?.Write(e.Data, 0, e.Data.Length);
 1195        }
 196
 197        private void Channel_Closed(object sender, ChannelEventArgs e)
 1198        {
 1199            if (Stopping is not null)
 0200            {
 201                // Handle event on different thread
 0202                ThreadAbstraction.ExecuteThread(() => Stopping(this, EventArgs.Empty));
 0203            }
 204
 1205            _channel.Dispose();
 1206            _ = _channelClosedWaitHandle.Set();
 207
 1208            _input.Dispose();
 1209            _input = null;
 210
 1211            _ = _dataReaderTaskCompleted.WaitOne(_session.ConnectionInfo.Timeout);
 1212            _dataReaderTaskCompleted.Dispose();
 1213            _dataReaderTaskCompleted = null;
 214
 1215            _channel.DataReceived -= Channel_DataReceived;
 1216            _channel.ExtendedDataReceived -= Channel_ExtendedDataReceived;
 1217            _channel.Closed -= Channel_Closed;
 218
 1219            UnsubscribeFromSessionEvents(_session);
 220
 1221            if (Stopped != null)
 0222            {
 223                // Handle event on different thread
 0224                ThreadAbstraction.ExecuteThread(() => Stopped(this, EventArgs.Empty));
 0225            }
 226
 1227            _channel = null;
 1228        }
 229
 230        /// <summary>
 231        /// Unsubscribes the current <see cref="Shell"/> from session events.
 232        /// </summary>
 233        /// <param name="session">The session.</param>
 234        /// <remarks>
 235        /// Does nothing when <paramref name="session"/> is <see langword="null"/>.
 236        /// </remarks>
 237        private void UnsubscribeFromSessionEvents(ISession session)
 1238        {
 1239            if (session is null)
 0240            {
 0241                return;
 242            }
 243
 1244            session.Disconnected -= Session_Disconnected;
 1245            session.ErrorOccured -= Session_ErrorOccured;
 1246        }
 247
 248        private bool _disposed;
 249
 250        /// <summary>
 251        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 252        /// </summary>
 253        public void Dispose()
 0254        {
 0255            Dispose(disposing: true);
 0256            GC.SuppressFinalize(this);
 0257        }
 258
 259        /// <summary>
 260        /// Releases unmanaged and - optionally - managed resources.
 261        /// </summary>
 262        /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langwor
 263        protected virtual void Dispose(bool disposing)
 1264        {
 1265            if (_disposed)
 0266            {
 0267                return;
 268            }
 269
 1270            if (disposing)
 0271            {
 0272                UnsubscribeFromSessionEvents(_session);
 273
 0274                var channelClosedWaitHandle = _channelClosedWaitHandle;
 0275                if (channelClosedWaitHandle is not null)
 0276                {
 0277                    channelClosedWaitHandle.Dispose();
 0278                    _channelClosedWaitHandle = null;
 0279                }
 280
 0281                var channel = _channel;
 0282                if (channel is not null)
 0283                {
 0284                    channel.Dispose();
 0285                    _channel = null;
 0286                }
 287
 0288                var dataReaderTaskCompleted = _dataReaderTaskCompleted;
 0289                if (dataReaderTaskCompleted is not null)
 0290                {
 0291                    dataReaderTaskCompleted.Dispose();
 0292                    _dataReaderTaskCompleted = null;
 0293                }
 294
 0295                _disposed = true;
 0296            }
 1297        }
 298
 299        /// <summary>
 300        /// Finalizes an instance of the <see cref="Shell"/> class.
 301        /// </summary>
 302        ~Shell()
 2303        {
 1304            Dispose(disposing: false);
 2305        }
 306    }
 307}