Temporary remove SFTP functionality while refactoring to allow asynch execution

This commit is contained in:
olegkap_cp
2010-11-29 18:29:27 +00:00
parent 4c871a0962
commit b84ad09fd5
38 changed files with 391 additions and 784 deletions
@@ -1,580 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Renci.SshClient.Common;
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Channels
{
// TODO: Add Begin* and End* methods for async calls
internal class ChannelSessionSftp : ChannelSession
{
private EventWaitHandle _channelRequestSuccessWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _responseMessageReceivedWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
private uint _requestId;
private SftpMessage _responseMessage;
private string _remoteCurrentDir;
private string _localCurentDir;
private StringBuilder _packetData;
// TODO: Repalce with SFTP specific async class if needed
private CommandAsyncResult _asyncResult;
public ChannelSessionSftp()
: base()
{
}
public override void Open()
{
base.Open();
// Send channel command request
this.SendSubsystemRequest("sftp");
this.WaitHandle(this._channelRequestSuccessWaitHandle);
this.SendMessage(new InitMessage
{
Version = 3,
});
var versionMessage = this.ReceiveMessage<VersionMessage>();
if (versionMessage == null)
{
throw new InvalidOperationException("Version message expected.");
}
if (versionMessage.Version != 3)
{
throw new NotSupportedException(string.Format("Server SFTP version {0} is not supported.", versionMessage.Version));
}
// Get default current directories
var files = this.GetRealPath(".");
this._remoteCurrentDir = files.First().Name;
this._localCurentDir = Directory.GetCurrentDirectory();
}
public void UploadFile(Stream source, string destination)
{
this.Open();
string handle = string.Empty;
try
{
handle = this.OpenRemoteFile(destination, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate);
var buffer = new byte[1024];
ulong offset = 0;
var bytesRead = 0;
while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
{
this.RemoteWrite(handle, offset, buffer.Take(bytesRead).GetSshString());
offset += (ulong)buffer.Length;
}
}
finally
{
if (!string.IsNullOrEmpty(handle))
this.CloseRemoteHandle(handle);
}
this.Close();
}
internal void DownloadFile(string fileName, Stream destination)
{
this.Open();
string handle = string.Empty;
try
{
handle = this.OpenRemoteFile(fileName, Flags.Read);
ulong offset = 0;
uint bufferSize = 1024;
string data;
while ((data = this.RemoteRead(handle, offset, bufferSize)) != null)
{
var fileData = data.GetSshBytes().ToArray();
destination.Write(fileData, 0, fileData.Length);
destination.Flush();
offset += (ulong)fileData.Length;
}
}
finally
{
if (!string.IsNullOrEmpty(handle))
this.CloseRemoteHandle(handle);
}
this.Close();
}
public void CreateDirectory(string directoryName)
{
this.Open();
this.CreateRemoteDirectory(directoryName);
this.Close();
}
public void RemoveDirectory(string directoryName)
{
this.Open();
this.RemoveRemoteDirectory(directoryName);
this.Close();
}
public void RemoveFile(string fileName)
{
this.Open();
this.RemoveRemoteFile(fileName);
this.Close();
}
public void RenameFile(string oldFileName, string newFileName)
{
this.Open();
this.RenameRemoteFile(oldFileName, newFileName);
this.Close();
}
public IEnumerable<FtpFileInfo> ListDirectory(string path)
{
// Open channel
this.Open();
string handle = string.Empty;
IEnumerable<FtpFileInfo> files = null;
try
{
// Open directory
handle = this.OpenRemoteDirectory(path);
// Read directory data
files = this.ReadRemoteDirectory(handle);
}
finally
{
// Close directory
if (!string.IsNullOrEmpty(handle))
this.CloseRemoteHandle(handle);
}
// Read directory
this.Close();
return files;
}
protected override void OnSuccess()
{
base.OnSuccess();
this._channelRequestSuccessWaitHandle.Set();
}
protected override void OnData(string data)
{
base.OnData(data);
if (this._packetData == null)
{
var packetLength = (uint)(data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]);
this._packetData = new StringBuilder((int)packetLength, (int)packetLength);
this._packetData.Append(data.GetSshBytes().Skip(4).GetSshString());
}
else
{
this._packetData.Append(data);
}
if (this._packetData.Length < this._packetData.MaxCapacity)
{
// Wait for more packet data
return;
}
dynamic sftpMessage = SftpMessage.Load(this._packetData.ToString().GetSshBytes());
this._packetData = null;
if (sftpMessage.RequestId != null)
{
if (sftpMessage.RequestId != this._requestId)
{
throw new InvalidOperationException("Invalid request id.");
}
this._requestId++;
}
this._responseMessage = sftpMessage;
this._responseMessageReceivedWaitHandle.Set();
}
private T ReceiveMessage<T>() where T : SftpMessage
{
var message = this.ReceiveMessage() as T;
if (message == null)
{
throw new InvalidOperationException(string.Format("Message of type '{0}' expected in this context.", typeof(T).Name));
}
return message;
}
private SftpMessage ReceiveMessage()
{
this.WaitHandle(this._responseMessageReceivedWaitHandle);
var statusMessage = this._responseMessage as StatusMessage;
if (statusMessage != null)
{
// Handle error status messages
switch (statusMessage.StatusCode)
{
case StatusCodes.Ok:
break;
case StatusCodes.Eof:
break;
case StatusCodes.NoSuchFile:
throw new FileNotFoundException("File or directory not found on the remote server.");
case StatusCodes.PermissionDenied:
throw new NotImplementedException();
case StatusCodes.Failure:
throw new InvalidOperationException("Operation failed.");
case StatusCodes.BadMessage:
throw new NotImplementedException();
case StatusCodes.NoConnection:
throw new NotImplementedException();
case StatusCodes.ConnectionLost:
throw new NotImplementedException();
case StatusCodes.OperationUnsupported:
throw new NotSupportedException("Operation is not supported.");
default:
break;
}
}
return this._responseMessage;
}
private void SendMessage(SftpMessage sftpMessage)
{
sftpMessage.RequestId = this._requestId;
var message = new SftpDataMessage
{
LocalChannelNumber = this.RemoteChannelNumber,
Message = sftpMessage,
};
this.SendMessage(message);
this._responseMessageReceivedWaitHandle.Reset();
}
private string OpenRemoteFile(string fileName, Flags flags)
{
this.SendMessage(new OpenMessage
{
Filename = fileName,
Flags = flags,
});
var handleMessage = this.ReceiveMessage<HandleMessage>();
return handleMessage.Handle;
}
private string RemoteRead(string handle, ulong offset, uint length)
{
this.SendMessage(new ReadMessage
{
Handle = handle,
Offset = offset,
Length = length,
});
var message = this.ReceiveMessage();
var statusMessage = message as StatusMessage;
var dataMessage = message as DataMessage;
if (statusMessage != null)
{
if (statusMessage.StatusCode == StatusCodes.Eof)
{
return null;
}
throw new InvalidOperationException("Invalid status code.");
}
else if (dataMessage != null)
{
if (this._asyncResult != null)
{
this._asyncResult.BytesReceived += dataMessage.Data.Length;
}
return dataMessage.Data;
}
else
{
throw new InvalidOperationException(string.Format("Message type '{0}' is not valid in this context.", message.SftpMessageType));
}
}
private void RemoteWrite(string handle, ulong offset, string data)
{
this.SendMessage(new WriteMessage
{
Handle = handle,
Offset = offset,
Data = data,
});
var message = this.ReceiveMessage<StatusMessage>();
this.EnsureStatusCode(message, StatusCodes.Ok);
if (this._asyncResult != null)
{
this._asyncResult.BytesSent += data.Length;
}
}
private void RemoveRemoteFile(string fileName)
{
this.SendMessage(new RemoveMessage
{
Filename = fileName,
});
var message = this.ReceiveMessage<StatusMessage>();
this.EnsureStatusCode(message, StatusCodes.Ok);
}
private void RenameRemoteFile(string oldFileName, string newFileName)
{
this.SendMessage(new RenameMessage
{
OldPath = oldFileName,
NewPath = newFileName,
});
var message = this.ReceiveMessage<StatusMessage>();
this.EnsureStatusCode(message, StatusCodes.Ok);
}
private void CreateRemoteDirectory(string directoryName)
{
this.SendMessage(new MkDirMessage
{
Path = directoryName,
});
var message = this.ReceiveMessage<StatusMessage>();
this.EnsureStatusCode(message, StatusCodes.Ok);
}
private void RemoveRemoteDirectory(string directoryName)
{
this.SendMessage(new RmDirMessage
{
Path = directoryName,
});
var message = this.ReceiveMessage<StatusMessage>();
this.EnsureStatusCode(message, StatusCodes.Ok);
}
private string OpenRemoteDirectory(string path)
{
this.SendMessage(new OpenDirMessage
{
Path = path,
});
var handleMessage = this.ReceiveMessage<HandleMessage>();
return handleMessage.Handle;
}
private IEnumerable<FtpFileInfo> ReadRemoteDirectory(string handle)
{
this.SendMessage(new ReadDirMessage
{
Handle = handle,
});
var message = this.ReceiveMessage<NameMessage>();
return message.Files;
}
private void CloseRemoteHandle(string handle)
{
this.SendMessage(new CloseMessage
{
Handle = handle,
});
var status = this.ReceiveMessage<StatusMessage>();
var attempts = 0;
// If close fails wait a litle a try to close it again, in case server flushed data into the file during close
while (status.StatusCode != StatusCodes.Ok && attempts++ < this.ConnectionInfo.RetryAttempts)
{
Thread.Sleep(50);
status = this.ReceiveMessage<StatusMessage>();
}
if (status.StatusCode != StatusCodes.Ok)
{
throw new InvalidOperationException(string.Format("File handle cannot be closed after {0} attempts.", attempts));
}
}
private Attributes GetRemoteFileAttributes(string filename)
{
this.SendMessage(new StatMessage
{
Path = filename,
});
var message = this.ReceiveMessage<AttrsMessage>();
return message.Attributes;
}
private Attributes GetRemoteLinkFileAttributes(string filename)
{
this.SendMessage(new LStatMessage
{
Path = filename,
});
var message = this.ReceiveMessage<AttrsMessage>();
return message.Attributes;
}
private Attributes GetRemoteOpenFileAttributes(string handle)
{
this.SendMessage(new FStatMessage
{
Handle = handle,
});
var message = this.ReceiveMessage<AttrsMessage>();
return message.Attributes;
}
private void SetRemoteFileAttributes(string filename, Attributes attributes)
{
this.SendMessage(new SetStatMessage
{
Path = filename,
Attributes = attributes
});
var message = this.ReceiveMessage<StatusMessage>();
this.EnsureStatusCode(message, StatusCodes.Ok);
}
private void SetRemoteOpenFileAttributes(string handle, Attributes attributes)
{
this.SendMessage(new FSetStatMessage
{
Handle = handle,
Attributes = attributes
});
var message = this.ReceiveMessage<StatusMessage>();
this.EnsureStatusCode(message, StatusCodes.Ok);
}
private IEnumerable<FtpFileInfo> GetRealPath(string path)
{
this.SendMessage(new RealPathMessage
{
Path = path,
});
var message = this.ReceiveMessage<NameMessage>();
return message.Files;
}
private void EnsureStatusCode(StatusMessage message, StatusCodes code)
{
if (message.StatusCode == code)
{
return;
}
else
{
throw new InvalidOperationException("Invalid status code.");
}
}
#region IDisposable Members
protected override void OnDisposing()
{
// Dispose managed resources.
if (this._channelRequestSuccessWaitHandle != null)
{
this._channelRequestSuccessWaitHandle.Dispose();
}
if (this._responseMessageReceivedWaitHandle != null)
{
this._responseMessageReceivedWaitHandle.Dispose();
}
}
#endregion
}
}
@@ -1,99 +0,0 @@
using System.IO;
using System.Linq;
using System.Threading;
using Renci.SshClient.Messages.Connection;
namespace Renci.SshClient.Channels
{
internal class ChannelSessionShell : ChannelSession
{
private EventWaitHandle _success = new AutoResetEvent(false);
/// <summary>
/// Holds channel data stream
/// </summary>
private Stream _channelData;
/// <summary>
/// Holds channel extended data stream
/// </summary>
private Stream _channelExtendedData;
public void Start(Stream output, Stream extendedOutput)
{
this.Open();
this._channelData = output;
this._channelExtendedData = extendedOutput;
this.SendPseudoTerminalRequest("xterm", 80, 24, 640, 240, "");
_success.WaitOne();
this.SendShellRequest();
}
public void Stop()
{
// Close channel
this.Close();
}
protected override void OnSuccess()
{
base.OnSuccess();
_success.Set();
}
protected override void OnFailure()
{
base.OnFailure();
}
protected override void OnData(string data)
{
base.OnData(data);
this._channelData.Write(data.GetSshBytes().ToArray(), 0, data.Length);
this._channelData.Flush();
}
protected override void OnExtendedData(string data, uint dataTypeCode)
{
base.OnExtendedData(data, dataTypeCode);
// TODO: dataTypeCode is not handled
this._channelExtendedData.Write(data.GetSshBytes().ToArray(), 0, data.Length);
this._channelExtendedData.Flush();
}
public void Send(string data)
{
this.SendMessage(new ChannelDataMessage
{
LocalChannelNumber = this.RemoteChannelNumber,
Data = data,
});
}
public class ShellStream : MemoryStream
{
public ShellStream()
{
}
public override void Write(byte[] buffer, int offset, int count)
{
base.Write(buffer, offset, count);
}
public override void WriteByte(byte value)
{
base.WriteByte(value);
}
}
}
}
@@ -25,7 +25,7 @@ namespace Renci.SshClient.Common
private IEnumerable<byte> _loadedData;
public virtual int ZeroReaderIndex
protected virtual int ZeroReaderIndex
{
get
{
@@ -23,7 +23,7 @@ namespace Renci.SshClient.Messages
return message;
}
public override int ZeroReaderIndex
protected override int ZeroReaderIndex
{
get
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class AttrsMessage : SftpMessage
internal class AttrsMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class CloseMessage : SftpMessage
internal class CloseMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class DataMessage : SftpMessage
internal class DataMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class ExtendedMessage : SftpMessage
internal class ExtendedMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class ExtendedReplyMessage : SftpMessage
internal class ExtendedReplyMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class FSetStat : SftpMessage
internal class FSetStat : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,6 +1,6 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class FSetStatMessage : SftpMessage
internal class FSetStatMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class FStatMessage : SftpMessage
internal class FStatMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class HandleMessage : SftpMessage
internal class HandleMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -11,7 +11,8 @@
protected override void LoadData()
{
base.LoadData();
this.Version = this.ReadUInt32();
}
protected override void SaveData()
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class LStatMessage : SftpMessage
internal class LStatMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,6 +1,6 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class MkDirMessage : SftpMessage
internal class MkDirMessage : SftpRequestMessage
{
public MkDirMessage()
{
@@ -29,5 +29,6 @@
this.Write(this.Path);
this.Write(this.Attributes);
}
}
}
@@ -3,7 +3,7 @@ using Renci.SshClient.Common;
namespace Renci.SshClient.Messages.Sftp
{
internal class NameMessage : SftpMessage
internal class NameMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
@@ -2,7 +2,7 @@
using System.Text;
namespace Renci.SshClient.Messages.Sftp
{
internal class OpenDirMessage : SftpMessage
internal class OpenDirMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -11,6 +11,16 @@ namespace Renci.SshClient.Messages.Sftp
public string Path { get; set; }
public OpenDirMessage()
{
}
public OpenDirMessage(string path)
{
this.Path = path;
}
protected override void LoadData()
{
base.LoadData();
@@ -2,7 +2,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class OpenMessage : SftpMessage
internal class OpenMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class ReadDirMessage : SftpMessage
internal class ReadDirMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -10,6 +10,16 @@ namespace Renci.SshClient.Messages.Sftp
public string Handle { get; set; }
public ReadDirMessage()
{
}
public ReadDirMessage(string handle)
{
this.Handle = handle;
}
protected override void LoadData()
{
base.LoadData();
@@ -2,7 +2,7 @@
using System.Text;
namespace Renci.SshClient.Messages.Sftp
{
internal class ReadLinkMessage : SftpMessage
internal class ReadLinkMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -2,7 +2,7 @@
using System;
namespace Renci.SshClient.Messages.Sftp
{
internal class ReadMessage : SftpMessage
internal class ReadMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class RealPathMessage : SftpMessage
internal class RealPathMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -2,7 +2,7 @@
using System.Text;
namespace Renci.SshClient.Messages.Sftp
{
internal class RemoveMessage : SftpMessage
internal class RemoveMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class RenameMessage : SftpMessage
internal class RenameMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -2,7 +2,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class RmDirMessage : SftpMessage
internal class RmDirMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class SetStatMessage : SftpMessage
internal class SetStatMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -18,7 +18,7 @@ namespace Renci.SshClient.Messages.Sftp
return Load(data, messageType);
}
public override int ZeroReaderIndex
protected override int ZeroReaderIndex
{
get
{
@@ -59,22 +59,13 @@ namespace Renci.SshClient.Messages.Sftp
public abstract SftpMessageTypes SftpMessageType { get; }
public uint? RequestId { get; set; }
protected override void LoadData()
{
// SSH_FXP_INIT and SSH_FXP_VERSION doesnt have RequestID, all other messaages do
if (!(this.SftpMessageType == SftpMessageTypes.Init || this.SftpMessageType == SftpMessageTypes.Version))
{
this.RequestId = this.ReadUInt32();
}
}
protected override void SaveData()
{
this.Write((byte)this.SftpMessageType);
if (this.RequestId.HasValue)
this.Write(this.RequestId.Value);
}
protected Attributes ReadAttributes()
@@ -0,0 +1,20 @@
namespace Renci.SshClient.Messages.Sftp
{
internal abstract class SftpRequestMessage : SftpMessage
{
public uint RequestId { get; set; }
protected override void LoadData()
{
base.LoadData();
this.RequestId = this.ReadUInt32();
}
protected override void SaveData()
{
base.SaveData();
this.Write(this.RequestId);
}
}
}
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class StatMessage : SftpMessage
internal class StatMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,6 +1,6 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class StatusMessage : SftpMessage
internal class StatusMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -2,7 +2,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class SymLinkMessage : SftpMessage
internal class SymLinkMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -31,6 +31,5 @@ namespace Renci.SshClient.Messages.Sftp
this.Write(this.Version);
this.Write(this.Extentions);
}
}
}
@@ -2,7 +2,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class WriteMessage : SftpMessage
internal class WriteMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -64,7 +64,6 @@
<Compile Include="Common\ChannelEventArgs.cs" />
<Compile Include="Common\ChannelOpenFailedEventArgs.cs" />
<Compile Include="Common\ChannelRequestEventArgs.cs" />
<Compile Include="Channels\ChannelSessionShell.cs" />
<Compile Include="Common\SshConnectionException.cs" />
<Compile Include="Common\SshOperationTimeoutException.cs" />
<Compile Include="Messages\Connection\ChannelOpen\ChannelOpenInfo.cs" />
@@ -84,6 +83,8 @@
<Compile Include="Messages\Connection\ChannelRequest\WindowChangeRequestInfo.cs" />
<Compile Include="Messages\Connection\ChannelRequest\X11ForwardingRequestInfo.cs" />
<Compile Include="Messages\Connection\ChannelRequest\XonXoffRequestInfo.cs" />
<Compile Include="Messages\Sftp\SftpRequestMessage.cs" />
<Compile Include="SftpAsyncResult.cs" />
<Compile Include="SshCommand.cs" />
<Compile Include="MessageEventArgs.cs" />
<Compile Include="ChannelAsyncResult.cs" />
@@ -102,7 +103,6 @@
<Compile Include="Security\CipherAES.cs" />
<Compile Include="Security\CipherTripleDES.cs" />
<Compile Include="Compression\Compressor.cs" />
<Compile Include="Channels\ChannelSessionSftp.cs" />
<Compile Include="Common\DataReceivedEventArgs.cs" />
<Compile Include="Common\FtpFileInfo.cs" />
<Compile Include="Messages\Sftp\AceMasks.cs" />
+236 -45
View File
@@ -1,82 +1,273 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Renci.SshClient.Channels;
using Renci.SshClient.Common;
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient
{
public class Sftp
{
private ChannelSessionSftp _channel;
private ChannelSessionSftp Channel
{
get
{
if (this._channel == null)
{
this._channel = this._session.CreateChannel<ChannelSessionSftp>();
}
return this._channel;
}
}
private readonly ChannelSession _channel;
private readonly Session _session;
private StringBuilder _packetData;
private uint _requestId;
private Exception _exception;
private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
private EventWaitHandle _sessionErrorOccuredWaitHandle = new AutoResetEvent(false);
private IDictionary<uint, Action<SftpRequestMessage>> _requestActions;
private string _remoteCurrentDir;
public int OperationTimeout { get; set; }
internal Sftp(Session session)
{
this.OperationTimeout = -1;
this._requestActions = new Dictionary<uint, Action<SftpRequestMessage>>();
this._session = session;
}
this._session.ErrorOccured += Session_ErrorOccured;
this._session.Disconnected += Session_Disconnected;
this._channel = session.CreateChannel<ChannelSession>();
this._channel.DataReceived += Channel_DataReceived;
this._channel.Open();
this._channel.SendSubsystemRequest("sftp");
public IEnumerable<FtpFileInfo> ListDirectory(string path)
{
return this.Channel.ListDirectory(path);
}
public void UploadFile(Stream source, string fileName)
{
this.Channel.UploadFile(source, fileName);
}
public void UploadFile(string source, string fileName)
{
using (var sourceFile = File.OpenRead(source))
this.SendMessage(new InitMessage
{
this.Channel.UploadFile(sourceFile, fileName);
Version = 3,
});
this.WaitHandle(this._sftpVersionConfirmed);
this.SendMessage(new RealPathMessage
{
Path = ".",
}, (m) =>
{
var nameMessage = m as NameMessage;
if (nameMessage != null)
{
this._remoteCurrentDir = nameMessage.Files.First().Name;
}
else
{
throw new InvalidOperationException("");
}
});
}
public IAsyncResult BeginListDirectory(string path, AsyncCallback callback, object state)
{
var asyncResult = new SftpAsyncResult(this._channel, callback, state);
this.SendMessage(new OpenDirMessage(path), (m) =>
{
var message = m as HandleMessage;
if (message == null)
throw new InvalidOperationException("Handle message is not expected.");
this.SendMessage(new ReadDirMessage(message.Handle), (m1) =>
{
var message1 = m1 as NameMessage;
if (message1 == null)
throw new InvalidOperationException("Handle message is not expected.");
asyncResult.Names = message1.Files;
asyncResult.IsCompleted = true;
});
});
return asyncResult;
}
public IEnumerable<FtpFileInfo> EndListDirectory(IAsyncResult result)
{
var r = result as SftpAsyncResult;
if (r == null)
{
throw new ArgumentException("Invalid IAsyncResult parameter.");
}
r.AsyncWaitHandle.WaitOne();
return r.Names;
}
private void Channel_DataReceived(object sender, Common.ChannelDataEventArgs e)
{
if (this._packetData == null)
{
var packetLength = (uint)(e.Data[0] << 24 | e.Data[1] << 16 | e.Data[2] << 8 | e.Data[3]);
this._packetData = new StringBuilder((int)packetLength, (int)packetLength);
this._packetData.Append(e.Data.GetSshBytes().Skip(4).GetSshString());
}
else
{
this._packetData.Append(e.Data);
}
if (this._packetData.Length < this._packetData.MaxCapacity)
{
// Wait for more packet data
return;
}
dynamic sftpMessage = SftpMessage.Load(this._packetData.ToString().GetSshBytes());
this._packetData = null;
this.HandleMessage(sftpMessage);
}
private void HandleMessage(InitMessage message)
{
throw new InvalidOperationException("Init message should not be received by client.");
}
private void HandleMessage(VersionMessage message)
{
if (message.Version == 3)
{
this._sftpVersionConfirmed.Set();
}
else
{
throw new NotSupportedException(string.Format("Server SFTP version {0} is not supported.", message.Version));
}
}
public void DownloadFile(string fileName, Stream destination)
private void HandleMessage(SftpRequestMessage message)
{
this.Channel.DownloadFile(fileName, destination);
}
public void DownloadFile(string fileName, string destination)
{
using (var destinationFile = File.Create(destination))
if (this._requestActions.ContainsKey(message.RequestId))
{
this.Channel.DownloadFile(fileName, destinationFile);
var action = this._requestActions[message.RequestId];
action(message);
this._requestActions.Remove(message.RequestId);
}
else
{
throw new InvalidOperationException(string.Format("Request #{0} is invalid.", message.RequestId));
}
}
public void RemoveFile(string fileName)
private void Session_Disconnected(object sender, EventArgs e)
{
this.Channel.RemoveFile(fileName);
}
public void RenameFile(string oldFileName, string newFileName)
private void Session_ErrorOccured(object sender, ErrorEventArgs e)
{
this.Channel.RenameFile(oldFileName, newFileName);
this._exception = e.GetException();
this._sessionErrorOccuredWaitHandle.Set();
}
public void CreateDirectory(string directoryName)
private void SendMessage(SftpMessage sftpMessage)
{
this.Channel.CreateDirectory(directoryName);
var message = new SftpDataMessage
{
LocalChannelNumber = this._channel.RemoteChannelNumber,
Message = sftpMessage,
};
this._session.SendMessage(message);
}
public void RemoveDirectory(string directoryName)
private void SendMessage(SftpRequestMessage sftpMessage, Action<SftpRequestMessage> action)
{
this.Channel.RemoveDirectory(directoryName);
sftpMessage.RequestId = this._requestId++;
var message = new SftpDataMessage
{
LocalChannelNumber = this._channel.RemoteChannelNumber,
Message = sftpMessage,
};
this._session.SendMessage(message);
this._requestActions.Add(sftpMessage.RequestId, action);
}
private void WaitHandle(WaitHandle waitHandle)
{
var waitHandles = new WaitHandle[]
{
this._sessionErrorOccuredWaitHandle,
waitHandle,
};
var index = EventWaitHandle.WaitAny(waitHandles, this.OperationTimeout);
if (index < 1)
{
throw this._exception;
}
else if (index > 1)
{
// throw time out error
throw new SshOperationTimeoutException(string.Format("Sftp operation has timed out."));
}
}
//public void UploadFile(Stream source, string fileName)
//{
// this.Channel.UploadFile(source, fileName);
//}
//public void UploadFile(string source, string fileName)
//{
// using (var sourceFile = File.OpenRead(source))
// {
// this.Channel.UploadFile(sourceFile, fileName);
// }
//}
//public void DownloadFile(string fileName, Stream destination)
//{
// this.Channel.DownloadFile(fileName, destination);
//}
//public void DownloadFile(string fileName, string destination)
//{
// using (var destinationFile = File.Create(destination))
// {
// this.Channel.DownloadFile(fileName, destinationFile);
// }
//}
//public void RemoveFile(string fileName)
//{
// this.Channel.RemoveFile(fileName);
//}
//public void RenameFile(string oldFileName, string newFileName)
//{
// this.Channel.RenameFile(oldFileName, newFileName);
//}
//public void CreateDirectory(string directoryName)
//{
// this.Channel.CreateDirectory(directoryName);
//}
//public void RemoveDirectory(string directoryName)
//{
// this.Channel.RemoveDirectory(directoryName);
//}
}
}
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Renci.SshClient.Common;
namespace Renci.SshClient
{
public class SftpAsyncResult : IAsyncResult
{
private bool _isCompleted;
private Channels.ChannelSession _channelSession;
private AsyncCallback _callback;
private EventWaitHandle _completedWaitHandle = new ManualResetEvent(false);
public IList<FtpFileInfo> Names { get; internal set; }
internal SftpAsyncResult(Channels.ChannelSession channelSession, AsyncCallback callback, object state)
{
this._channelSession = channelSession;
this._callback = callback;
this.AsyncState = state;
this.AsyncWaitHandle = _completedWaitHandle;
}
#region IAsyncResult Members
public object AsyncState { get; private set; }
public WaitHandle AsyncWaitHandle { get; private set; }
public bool CompletedSynchronously { get; private set; }
public bool IsCompleted
{
get
{
return this._isCompleted;
}
internal set
{
this._isCompleted = value;
if (value)
{
if (this._callback != null)
{
this._callback(this);
}
this._completedWaitHandle.Set();
}
}
}
#endregion
}
}
+23 -18
View File
@@ -23,22 +23,22 @@ namespace Renci.SshClient
}
}
private Sftp _sftp;
/// <summary>
/// Gets the shell.
/// </summary>
/// <value>The shell.</value>
public Sftp Sftp
{
get
{
if (this._sftp == null)
{
this._sftp = new Sftp(this._session);
}
return this._sftp;
}
}
//private Sftp _sftp;
///// <summary>
///// Gets the shell.
///// </summary>
///// <value>The shell.</value>
//public Sftp Sftp
//{
// get
// {
// if (this._sftp == null)
// {
// this._sftp = new Sftp(this._session);
// }
// return this._sftp;
// }
//}
public IEnumerable<ForwardedPort> ForwardedPorts
{
@@ -111,7 +111,7 @@ namespace Renci.SshClient
this._session.Disconnect();
// Clean up objects created using previouse session instance
this._sftp = null;
//this._sftp = null;
}
public T AddForwardedPort<T>(uint boundPort, string connectedHost, uint connectedPort) where T : ForwardedPort, new()
@@ -135,7 +135,7 @@ namespace Renci.SshClient
public SshCommand RunCommand(string commandText)
{
var cmd = new SshCommand(this._session, commandText);
var cmd = this.CreateCommand(commandText);
cmd.Execute();
return cmd;
}
@@ -145,6 +145,11 @@ namespace Renci.SshClient
return new Shell(this._session, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalMode);
}
//public Sftp CreateSftp()
//{
// return new Sftp(this._session);
//}
public void RemoveForwardedPort(ForwardedPort port)
{
this._forwardedPorts.Remove(port);