Split SftpSession class into 2 to allow future extensability of other sub systems

This commit is contained in:
olegkap_cp
2011-08-22 13:51:15 +00:00
parent ae45316542
commit 097246c153
3 changed files with 305 additions and 222 deletions
@@ -182,6 +182,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Security\KeyHostAlgorithm.cs" />
<Compile Include="SubsystemSession.cs" />
<Compile Include="Shell.NET40.cs" />
<Compile Include="SftpClient.NET40.cs" />
<Compile Include="PasswordConnectionInfo.NET40.cs" />
+95 -222
View File
@@ -13,26 +13,14 @@ using Renci.SshNet.Sftp.Requests;
namespace Renci.SshNet.Sftp
{
internal class SftpSession : IDisposable
internal class SftpSession : SubsystemSession
{
private Session _session;
private ChannelSession _channel;
private Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
private List<byte> _data = new List<byte>(32 * 1024);
private Exception _exception;
private EventWaitHandle _errorOccuredWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
private TimeSpan _operationTimeout;
public event EventHandler<ExceptionEventArgs> ErrorOccured;
/// <summary>
/// Gets remote working directory.
/// </summary>
@@ -70,36 +58,8 @@ namespace Renci.SshNet.Sftp
#endregion
public SftpSession(Session session, TimeSpan operationTimeout)
: base(session, "sftp", operationTimeout)
{
this._session = session;
this._operationTimeout = operationTimeout;
}
public void Connect()
{
this._channel = this._session.CreateChannel<ChannelSession>();
this._session.ErrorOccured += Session_ErrorOccured;
this._session.Disconnected += Session_Disconnected;
this._channel.DataReceived += Channel_DataReceived;
this._channel.Open();
this._channel.SendSubsystemRequest("sftp");
this.SendMessage(new SftpInitRequest(3));
this.WaitHandle(this._sftpVersionConfirmed, this._operationTimeout);
this.ProtocolVersion = 3;
// Resolve current directory
this.WorkingDirectory = this.RequestRealPath(".").Keys.First();
}
public void Disconnect()
{
this.Dispose();
}
public void ChangeDirectory(string path)
@@ -113,96 +73,9 @@ namespace Renci.SshNet.Sftp
this.WorkingDirectory = fullPath;
}
private void Channel_DataReceived(object sender, Common.ChannelDataEventArgs e)
{
// Add channel data to internal data holder
this._data.AddRange(e.Data);
while (this._data.Count > 4 + 1)
{
// Extract packet length
var packetLength = (this._data[0] << 24 | this._data[1] << 16 | this._data[2] << 8 | this._data[3]);
// Check if complete packet data is available
if (this._data.Count < packetLength + 4)
{
// Wait for complete message to arrive first
break;
}
this._data.RemoveRange(0, 4);
// Create buffer to hold packet data
var packetData = new byte[packetLength];
// Cope packet data to array
this._data.CopyTo(0, packetData, 0, packetLength);
// Remove loaded data from _data holder
this._data.RemoveRange(0, packetLength);
// Load SFTP Message and handle it
dynamic response = SftpMessage.Load(packetData);
try
{
if (response is SftpVersionResponse)
{
if (response.Version == 3)
{
this._sftpVersionConfirmed.Set();
}
else
{
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", response.Version));
}
}
else
{
this.HandleResponse(response);
}
}
catch (Exception exp)
{
this.RaiseError(exp);
break;
}
}
}
private void SendRequest(SftpRequest request)
{
lock (this._requests)
{
this._requests.Add(request.RequestId, request);
}
this._session.SendMessage(new SftpDataMessage(this._channel.RemoteChannelNumber, request));
}
internal void SendMessage(SftpMessage sftpMessage)
{
this._session.SendMessage(new SftpDataMessage(this._channel.RemoteChannelNumber, sftpMessage));
}
internal void WaitHandle(WaitHandle waitHandle, TimeSpan operationTimeout)
{
var waitHandles = new WaitHandle[]
{
this._errorOccuredWaitHandle,
waitHandle,
};
var index = EventWaitHandle.WaitAny(waitHandles, operationTimeout);
if (index < 1)
{
throw this._exception;
}
else if (index > 1)
{
// throw time out error
throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Sftp operation has timed out."));
}
this.SendData(new SftpDataMessage(this.ChannelNumber, sftpMessage));
}
/// <summary>
@@ -288,6 +161,98 @@ namespace Renci.SshNet.Sftp
}
}
protected override void OnChannelOpen()
{
this.SendMessage(new SftpInitRequest(3));
this.WaitHandle(this._sftpVersionConfirmed, this._operationTimeout);
this.ProtocolVersion = 3;
// Resolve current directory
this.WorkingDirectory = this.RequestRealPath(".").Keys.First();
}
protected override void OnDataReceived(uint dataTypeCode, byte[] data)
{
// Add channel data to internal data holder
this._data.AddRange(data);
while (this._data.Count > 4 + 1)
{
// Extract packet length
var packetLength = (this._data[0] << 24 | this._data[1] << 16 | this._data[2] << 8 | this._data[3]);
// Check if complete packet data is available
if (this._data.Count < packetLength + 4)
{
// Wait for complete message to arrive first
break;
}
this._data.RemoveRange(0, 4);
// Create buffer to hold packet data
var packetData = new byte[packetLength];
// Cope packet data to array
this._data.CopyTo(0, packetData, 0, packetLength);
// Remove loaded data from _data holder
this._data.RemoveRange(0, packetLength);
// Load SFTP Message and handle it
dynamic response = SftpMessage.Load(packetData);
try
{
if (response is SftpVersionResponse)
{
if (response.Version == 3)
{
this._sftpVersionConfirmed.Set();
}
else
{
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", response.Version));
}
}
else
{
this.HandleResponse(response);
}
}
catch (Exception exp)
{
this.RaiseError(exp);
break;
}
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
if (this._sftpVersionConfirmed != null)
{
this._sftpVersionConfirmed.Dispose();
this._sftpVersionConfirmed = null;
}
}
}
private void SendRequest(SftpRequest request)
{
lock (this._requests)
{
this._requests.Add(request.RequestId, request);
}
this.SendData(new SftpDataMessage(this.ChannelNumber, request));
}
#region SFTP API functions
//#define SSH_FXP_INIT 1
@@ -948,97 +913,5 @@ namespace Renci.SshNet.Sftp
request.Complete(response);
}
private void Session_Disconnected(object sender, EventArgs e)
{
this.RaiseError(new SshException("Connection was lost"));
}
private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
{
this.RaiseError(e.Exception);
}
private void RaiseError(Exception error)
{
this._exception = error;
this._errorOccuredWaitHandle.Set();
if (this.ErrorOccured != null)
{
this.ErrorOccured(this, new ExceptionEventArgs(error));
}
}
#region IDisposable Members
private bool _isDisposed = false;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!this._isDisposed)
{
if (this._channel != null)
{
this._channel.DataReceived -= Channel_DataReceived;
this._channel.Dispose();
this._channel = null;
}
this._session.ErrorOccured -= Session_ErrorOccured;
this._session.Disconnected -= Session_Disconnected;
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// Dispose managed resources.
if (this._errorOccuredWaitHandle != null)
{
this._errorOccuredWaitHandle.Dispose();
this._errorOccuredWaitHandle = null;
}
if (this._sftpVersionConfirmed != null)
{
this._sftpVersionConfirmed.Dispose();
this._sftpVersionConfirmed = null;
}
}
// Note disposing has been done.
_isDisposed = true;
}
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="SftpSession"/> is reclaimed by garbage collection.
/// </summary>
~SftpSession()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
#endregion
}
}
@@ -0,0 +1,209 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using System.Diagnostics;
using System.Collections.Generic;
using System.Globalization;
using Renci.SshNet.Sftp.Responses;
using Renci.SshNet.Sftp.Requests;
using Renci.SshNet.Messages.Connection;
namespace Renci.SshNet.Sftp
{
internal abstract class SubsystemSession : IDisposable
{
private Session _session;
private string _subsystemName;
private ChannelSession _channel;
private Exception _exception;
private EventWaitHandle _errorOccuredWaitHandle = new AutoResetEvent(false);
protected TimeSpan _operationTimeout;
public event EventHandler<ExceptionEventArgs> ErrorOccured;
protected uint ChannelNumber
{
get
{
return this._channel.RemoteChannelNumber;
}
}
#region SFTP messages
//internal event EventHandler<MessageEventArgs<StatusMessage>> StatusMessageReceived;
//internal event EventHandler<MessageEventArgs<DataMessage>> DataMessageReceived;
//internal event EventHandler<MessageEventArgs<HandleMessage>> HandleMessageReceived;
//internal event EventHandler<MessageEventArgs<NameMessage>> NameMessageReceived;
//internal event EventHandler<MessageEventArgs<AttributesMessage>> AttributesMessageReceived;
#endregion
public SubsystemSession(Session session, string subsystemName, TimeSpan operationTimeout)
{
this._session = session;
this._subsystemName = subsystemName;
this._operationTimeout = operationTimeout;
}
public void Connect()
{
this._channel = this._session.CreateChannel<ChannelSession>();
this._session.ErrorOccured += Session_ErrorOccured;
this._session.Disconnected += Session_Disconnected;
this._channel.DataReceived += Channel_DataReceived;
this._channel.Open();
this._channel.SendSubsystemRequest(_subsystemName);
this.OnChannelOpen();
}
public void Disconnect()
{
this.Dispose();
}
//public void SendData(byte[] data)
//{
// this._session.SendMessage(new ChannelDataMessage(this._channel.RemoteChannelNumber, data));
//}
public void SendData(ChannelDataMessage message)
{
this._session.SendMessage(message);
}
protected abstract void OnChannelOpen();
protected abstract void OnDataReceived(uint dataTypeCode, byte[] data);
protected void RaiseError(Exception error)
{
this._exception = error;
this._errorOccuredWaitHandle.Set();
if (this.ErrorOccured != null)
{
this.ErrorOccured(this, new ExceptionEventArgs(error));
}
}
private void Channel_DataReceived(object sender, Common.ChannelDataEventArgs e)
{
this.OnDataReceived(e.DataTypeCode, e.Data);
}
internal void WaitHandle(WaitHandle waitHandle, TimeSpan operationTimeout)
{
var waitHandles = new WaitHandle[]
{
this._errorOccuredWaitHandle,
waitHandle,
};
var index = EventWaitHandle.WaitAny(waitHandles, operationTimeout);
if (index < 1)
{
throw this._exception;
}
else if (index > 1)
{
// throw time out error
throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Sftp operation has timed out."));
}
}
private void Session_Disconnected(object sender, EventArgs e)
{
this.RaiseError(new SshException("Connection was lost"));
}
private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
{
this.RaiseError(e.Exception);
}
#region IDisposable Members
private bool _isDisposed = false;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!this._isDisposed)
{
if (this._channel != null)
{
this._channel.DataReceived -= Channel_DataReceived;
this._channel.Dispose();
this._channel = null;
}
this._session.ErrorOccured -= Session_ErrorOccured;
this._session.Disconnected -= Session_Disconnected;
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// Dispose managed resources.
if (this._errorOccuredWaitHandle != null)
{
this._errorOccuredWaitHandle.Dispose();
this._errorOccuredWaitHandle = null;
}
}
// Note disposing has been done.
_isDisposed = true;
}
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="SftpSession"/> is reclaimed by garbage collection.
/// </summary>
~SubsystemSession()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
#endregion
}
}