Partially implemented SftpClient, still missing few public methods to finish implementation

This commit is contained in:
olegkap_cp
2010-12-07 05:12:58 +00:00
parent 935d7dd40d
commit 0b6997f969
20 changed files with 1180 additions and 396 deletions
@@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
namespace Renci.SshClient.Common
{
public class FtpFileInfo
{
public string Name { get; set; }
public string FullName { get; set; }
public DateTime? CreationTime { get; set; }
public DateTime? LastAccessTime { get; set; }
public DateTime? LastModifyTime { get; set; }
public ulong? Size { get; set; }
public uint? UserId { get; set; }
public uint? GroupId { get; set; }
public uint? Permissions { get; set; }
public IDictionary<string, string> Extentions { get; set; }
}
}
@@ -1,7 +1,7 @@
namespace Renci.SshClient.Messages.Sftp
{
internal class AttrsMessage : SftpRequestMessage
internal class AttributesMessage : SftpRequestMessage
{
public override SftpMessageTypes SftpMessageType
{
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using Renci.SshClient.Common;
using Renci.SshClient.Sftp;
namespace Renci.SshClient.Messages.Sftp
{
@@ -13,35 +13,37 @@ namespace Renci.SshClient.Messages.Sftp
public uint Count { get; set; }
public IList<FtpFileInfo> Files { get; set; }
public IEnumerable<SftpFile> Files { get; set; }
public NameMessage()
{
this.Files = new List<FtpFileInfo>();
}
protected override void LoadData()
{
base.LoadData();
this.Count = this.ReadUInt32();
var files = new List<SftpFile>();
for (int i = 0; i < this.Count; i++)
{
var fileName = this.ReadString();
var fullName = this.ReadString();
var attribute = this.ReadAttributes();
this.Files.Add(new FtpFileInfo
// TODO: Complete files info population
files.Add(new SftpFile
{
Name = fileName,
FullName = fullName,
Size = attribute.Size,
UserId = attribute.UserId,
GroupId = attribute.GroupId,
LastAccessTime = attribute.AccessTime,
LastModifyTime = attribute.ModifyTime,
AbsolutePath = fullName,
//Size = attribute.Size,
//UserId = attribute.UserId,
//GroupId = attribute.GroupId,
//AccessedTime = attribute.AccessTime,
//ModifiedTime = attribute.ModifyTime,
Extentions = attribute.Extentions,
});
}
this.Files = files;
}
protected override void SaveData()
@@ -52,7 +52,7 @@ namespace Renci.SshClient.Messages.Sftp
SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Handle, new LoadFunc<SftpMessage>(Load<HandleMessage>));
SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Data, new LoadFunc<SftpMessage>(Load<DataMessage>));
SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Name, new LoadFunc<SftpMessage>(Load<NameMessage>));
SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Attrs, new LoadFunc<SftpMessage>(Load<AttrsMessage>));
SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Attrs, new LoadFunc<SftpMessage>(Load<AttributesMessage>));
SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Extended, new LoadFunc<SftpMessage>(Load<ExtendedMessage>));
SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.ExtendedReply, new LoadFunc<SftpMessage>(Load<ExtendedReplyMessage>));
}
@@ -84,7 +84,19 @@
<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="SftpClient.cs" />
<Compile Include="Sftp\CreateRemoteDirectoryCommand.cs" />
<Compile Include="Sftp\DownloadFileCommand.cs" />
<Compile Include="Sftp\ListDirectoryCommand.cs" />
<Compile Include="Sftp\RemoveDirectoryCommand.cs" />
<Compile Include="Sftp\RemoveFileCommand.cs" />
<Compile Include="Sftp\RenameFileCommand.cs" />
<Compile Include="Sftp\SftpAsyncResult.cs" />
<Compile Include="Sftp\SftpCommand.cs" />
<Compile Include="Sftp\SftpFile.cs" />
<Compile Include="Sftp\SftpFileAttributes.cs" />
<Compile Include="Sftp\SftpSession.cs" />
<Compile Include="Sftp\UploadFileCommand.cs" />
<Compile Include="SshCommand.cs" />
<Compile Include="MessageEventArgs.cs" />
<Compile Include="ChannelAsyncResult.cs" />
@@ -104,7 +116,6 @@
<Compile Include="Security\CipherTripleDES.cs" />
<Compile Include="Compression\Compressor.cs" />
<Compile Include="Common\DataReceivedEventArgs.cs" />
<Compile Include="Common\FtpFileInfo.cs" />
<Compile Include="Messages\Sftp\AceMasks.cs" />
<Compile Include="Messages\Sftp\Attributes.cs" />
<Compile Include="Messages\Sftp\AttrsMessage.cs" />
@@ -229,9 +240,7 @@
<Compile Include="Session.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Common\SshData.cs" />
<Compile Include="Sftp.cs" />
<Compile Include="Shell.cs" />
<Compile Include="SshBase.cs" />
<Compile Include="SshClient.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-273
View File
@@ -1,273 +0,0 @@
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 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");
this.SendMessage(new InitMessage
{
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));
}
}
private void HandleMessage(SftpRequestMessage message)
{
if (this._requestActions.ContainsKey(message.RequestId))
{
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));
}
}
private void Session_Disconnected(object sender, EventArgs e)
{
}
private void Session_ErrorOccured(object sender, ErrorEventArgs e)
{
this._exception = e.GetException();
this._sessionErrorOccuredWaitHandle.Set();
}
private void SendMessage(SftpMessage sftpMessage)
{
var message = new SftpDataMessage
{
LocalChannelNumber = this._channel.RemoteChannelNumber,
Message = sftpMessage,
};
this._session.SendMessage(message);
}
private void SendMessage(SftpRequestMessage sftpMessage, Action<SftpRequestMessage> action)
{
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,28 @@
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal class CreateRemoteDirectoryCommand : SftpCommand
{
private string _path;
public CreateRemoteDirectoryCommand(SftpSession sftpSession, string path)
: base(sftpSession)
{
this._path = path;
}
protected override void OnExecute()
{
this.SendMkDirMessage(this._path);
}
protected override void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
if (statusCode == StatusCodes.Ok)
{
this.CompleteExecution();
}
}
}
}
@@ -0,0 +1,61 @@
using System.IO;
using System.Linq;
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal class DownloadFileCommand : SftpCommand
{
private string _path;
private Stream _output;
private string _handle;
private ulong _offset = 0;
private uint _bufferSize = 1024;
private string _data;
public DownloadFileCommand(SftpSession sftpSession, string path, Stream output)
: base(sftpSession)
{
this._path = path;
this._output = output;
// TODO: Make this field to be customizable
this._bufferSize = 1024;
}
protected override void OnExecute()
{
this.SendOpenMessage(this._path, Flags.Read);
}
protected override void OnHandle(string handle)
{
base.OnHandle(handle);
this._handle = handle;
this.SendReadMessage(this._handle, this._offset, this._bufferSize);
}
protected override void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
base.OnStatus(statusCode, errorMessage, language);
if (statusCode == StatusCodes.Eof)
{
this.CompleteExecution();
}
}
protected override void OnData(string data, bool isEof)
{
base.OnData(data, isEof);
var fileData = data.GetSshBytes().ToArray();
this._output.Write(fileData, 0, fileData.Length);
this._output.Flush();
this._offset += (ulong)fileData.Length;
this.SendReadMessage(this._handle, this._offset, this._bufferSize);
}
}
}
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using Renci.SshClient.Common;
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal class ListDirectoryCommand : SftpCommand
{
private string _path;
private string _handle;
public IEnumerable<SftpFile> Files { get; private set; }
public ListDirectoryCommand(SftpSession sftpSession, string path)
: base(sftpSession)
{
this._path = path;
}
protected override void OnExecute()
{
this.SendOpenDirMessage(this._path);
}
protected override void OnHandle(string handle)
{
base.OnHandle(handle);
this._handle = handle;
this.SendReadDirMessage(this._handle);
}
protected override void OnName(IEnumerable<SftpFile> files)
{
base.OnName(files);
this.Files = files;
this.CompleteExecution();
this.SendCloseMessage(this._handle);
}
protected override void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
if (statusCode == StatusCodes.NoSuchFile)
{
throw new SshException(string.Format("Path '{0}' is not found.", this._path));
}
}
}
}
@@ -0,0 +1,28 @@
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal class RemoveDirectoryCommand : SftpCommand
{
private string _path;
public RemoveDirectoryCommand(SftpSession sftpSession, string path)
: base(sftpSession)
{
this._path = path;
}
protected override void OnExecute()
{
this.SendRmDirMessage(this._path);
}
protected override void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
if (statusCode == StatusCodes.Ok)
{
this.CompleteExecution();
}
}
}
}
@@ -0,0 +1,28 @@
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal class RemoveFileCommand : SftpCommand
{
private string _path;
public RemoveFileCommand(SftpSession sftpSession, string path)
: base(sftpSession)
{
this._path = path;
}
protected override void OnExecute()
{
this.SendRemoveMessage(this._path);
}
protected override void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
if (statusCode == StatusCodes.Ok)
{
this.CompleteExecution();
}
}
}
}
@@ -0,0 +1,30 @@
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal class RenameFileCommand : SftpCommand
{
private string _oldPath;
private string _newPath;
public RenameFileCommand(SftpSession sftpSession, string oldPath, string newPath)
: base(sftpSession)
{
this._oldPath = oldPath;
this._newPath = newPath;
}
protected override void OnExecute()
{
this.SendRenameMessage(this._oldPath, this._newPath);
}
protected override void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
if (statusCode == StatusCodes.Ok)
{
this.CompleteExecution();
}
}
}
}
@@ -1,30 +1,32 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Renci.SshClient.Common;
using System.Threading.Tasks;
namespace Renci.SshClient
namespace Renci.SshClient.Sftp
{
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 SftpCommand Command { get; private set; }
internal SftpAsyncResult(Channels.ChannelSession channelSession, AsyncCallback callback, object state)
internal SftpAsyncResult(SftpCommand command, AsyncCallback callback, object state)
{
this._channelSession = channelSession;
this.Command = command;
this._callback = callback;
this.AsyncState = state;
this.AsyncWaitHandle = _completedWaitHandle;
}
internal T GetCommand<T>() where T : SftpCommand
{
return this.Command as T;
}
#region IAsyncResult Members
public object AsyncState { get; private set; }
@@ -39,14 +41,20 @@ namespace Renci.SshClient
{
return this._isCompleted;
}
internal set
{
this._isCompleted = value;
if (value)
{
if (this._callback != null)
{
this._callback(this);
// Execute callback on new pool thread
Task.Factory.StartNew(() =>
{
this._callback(this);
});
}
this._completedWaitHandle.Set();
}
@@ -0,0 +1,298 @@
using System;
using System.Collections.Generic;
using Renci.SshClient.Common;
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal abstract class SftpCommand
{
private SftpSession _sftpSession;
private uint _requestId;
private SftpAsyncResult _asyncResult;
public SftpCommand(SftpSession sftpSession)
{
this._sftpSession = sftpSession;
this._sftpSession.AttributesMessageReceived += SftpSession_AttributesMessageReceived;
this._sftpSession.DataMessageReceived += SftpSession_DataMessageReceived;
this._sftpSession.HandleMessageReceived += SftpSession_HandleMessageReceived;
this._sftpSession.NameMessageReceived += SftpSession_NameMessageReceived;
this._sftpSession.StatusMessageReceived += SftpSession_StatusMessageReceived;
}
public SftpAsyncResult BeginExecute(AsyncCallback callback, object state)
{
this._asyncResult = new SftpAsyncResult(this, callback, state);
this.OnExecute();
return this._asyncResult;
}
public void EndExecute(SftpAsyncResult result)
{
// TODO: Add timeout info here
this._sftpSession.WaitHandle(result.AsyncWaitHandle);
}
public void Execute()
{
this.EndExecute(this.BeginExecute(null, null));
}
protected abstract void OnExecute();
protected virtual void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
}
protected virtual void OnName(IEnumerable<SftpFile> files)
{
}
protected virtual void OnHandle(string handle)
{
}
protected virtual void OnData(string data, bool isEof)
{
}
protected virtual void OnAttributes(Attributes attributes)
{
}
protected virtual void OnHandleClosed()
{
}
protected void SendOpenMessage(string path, Flags flags)
{
this.SendMessage(new OpenMessage
{
Filename = path,
Flags = flags,
});
}
protected void SendCloseMessage(string handle)
{
this.SendMessage(new CloseMessage
{
Handle = handle,
});
}
protected void SendReadMessage(string handle, ulong offset, uint bufferSize)
{
this.SendMessage(new ReadMessage
{
Handle = handle,
Offset = offset,
Length = bufferSize,
});
}
protected void SendWriteMessage(string handle, ulong offset, string data)
{
this.SendMessage(new WriteMessage
{
Handle = handle,
Offset = offset,
Data = data,
});
}
protected void SendLStatMessage(string path)
{
this.SendMessage(new LStatMessage
{
Path = path,
});
}
protected void SendFStatMessage(string handle)
{
this.SendMessage(new FStatMessage
{
Handle = handle,
});
}
protected void SendSetStatMessage(string path, Attributes attributes)
{
this.SendMessage(new SetStatMessage
{
Path = path,
Attributes = attributes,
});
}
protected void SendFSetStatMessage(string handle, Attributes attributes)
{
this.SendMessage(new FSetStatMessage
{
Handle = handle,
Attributes = attributes,
});
}
protected void SendOpenDirMessage(string path)
{
this.SendMessage(new OpenDirMessage
{
Path = path,
});
}
protected void SendReadDirMessage(string handle)
{
this.SendMessage(new ReadDirMessage
{
Handle = handle,
});
}
protected void SendRemoveMessage(string filename)
{
this.SendMessage(new RemoveMessage
{
Filename = filename,
});
}
protected void SendMkDirMessage(string path)
{
this.SendMessage(new MkDirMessage
{
Path = path,
});
}
protected void SendRmDirMessage(string path)
{
this.SendMessage(new RmDirMessage
{
Path = path,
});
}
protected void SendRealPathMessage(string path)
{
this.SendMessage(new RealPathMessage
{
Path = path,
});
}
protected void SendStatMessage(string path)
{
this.SendMessage(new StatMessage
{
Path = path,
});
}
protected void SendRenameMessage(string oldPath, string newPath)
{
this.SendMessage(new RenameMessage
{
OldPath = oldPath,
NewPath = newPath,
});
}
protected void SendReadLinkMessage(string path)
{
this.SendMessage(new ReadLinkMessage
{
Path = path,
});
}
protected void SendSymLinkMessage(string existingPath, string newLinkPath, bool isSymLink)
{
this.SendMessage(new SymLinkMessage
{
ExistingPath = existingPath,
NewLinkPath = newLinkPath,
IsSymLink = isSymLink,
});
}
protected void CompleteExecution()
{
this._asyncResult.IsCompleted = true;
this._sftpSession.AttributesMessageReceived -= SftpSession_AttributesMessageReceived;
this._sftpSession.DataMessageReceived -= SftpSession_DataMessageReceived;
this._sftpSession.HandleMessageReceived -= SftpSession_HandleMessageReceived;
this._sftpSession.NameMessageReceived -= SftpSession_NameMessageReceived;
this._sftpSession.StatusMessageReceived -= SftpSession_StatusMessageReceived;
}
private void SftpSession_StatusMessageReceived(object sender, MessageEventArgs<StatusMessage> e)
{
if (this._requestId == e.Message.RequestId)
{
this.OnStatus(e.Message.StatusCode, e.Message.ErrorMessage, e.Message.Language);
}
if (e.Message.StatusCode == StatusCodes.NoSuchFile ||
e.Message.StatusCode == StatusCodes.PermissionDenied ||
e.Message.StatusCode == StatusCodes.Failure ||
e.Message.StatusCode == StatusCodes.BadMessage ||
e.Message.StatusCode == StatusCodes.NoConnection ||
e.Message.StatusCode == StatusCodes.ConnectionLost ||
e.Message.StatusCode == StatusCodes.OperationUnsupported
)
{
// Throw an exception if it was not handled by the command
throw new SshException(e.Message.ErrorMessage);
}
}
private void SftpSession_NameMessageReceived(object sender, MessageEventArgs<NameMessage> e)
{
if (this._requestId == e.Message.RequestId)
{
this.OnName(e.Message.Files);
}
}
private void SftpSession_HandleMessageReceived(object sender, MessageEventArgs<HandleMessage> e)
{
if (this._requestId == e.Message.RequestId)
{
this.OnHandle(e.Message.Handle);
}
}
private void SftpSession_DataMessageReceived(object sender, MessageEventArgs<DataMessage> e)
{
if (this._requestId == e.Message.RequestId)
{
this.OnData(e.Message.Data, e.Message.IsEof);
}
}
private void SftpSession_AttributesMessageReceived(object sender, MessageEventArgs<AttributesMessage> e)
{
if (this._requestId == e.Message.RequestId)
{
this.OnAttributes(e.Message.Attributes);
}
}
private void SendMessage(SftpRequestMessage message)
{
this._sftpSession.SendMessage(message);
// Remeber command request id that was sent
this._requestId = message.RequestId;
}
}
}
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
namespace Renci.SshClient.Sftp
{
public class SftpFile
{
public SftpFile()
{
this.Attributes = new SftpFileAttributes();
this.CreatedTime = DateTime.MinValue;
this.AccessedTime = DateTime.MinValue;
this.ModifiedTime = DateTime.MinValue;
this.Size = -1;
this.UserId = -1;
this.GroupId = -1;
this.Permissions = -1;
this.Extentions = new Dictionary<string, string>();
}
public string Name { get; set; }
public string AbsolutePath { get; set; }
public SftpFileAttributes Attributes { get; set; }
public string Filename { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime AccessedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public long Size { get; set; }
public int UserId { get; set; }
public int GroupId { get; set; }
public int Permissions { get; set; }
public IDictionary<string, string> Extentions { get; set; }
}
}
@@ -0,0 +1,7 @@
namespace Renci.SshClient.Sftp
{
public class SftpFileAttributes
{
}
}
@@ -0,0 +1,245 @@
using System;
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.Sftp
{
internal class SftpSession
{
private Session _session;
private uint _requestId;
private ChannelSession _channel;
private StringBuilder _packetData;
private Exception _exception;
private EventWaitHandle _errorOccuredWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
#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 int OperationTimeout { get; private set; }
public SftpSession(Session session, int 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 InitMessage
{
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 void Disconnect()
{
}
internal void SendMessage(SftpRequestMessage sftpMessage)
{
sftpMessage.RequestId = this._requestId++;
var message = new SftpDataMessage
{
LocalChannelNumber = this._channel.RemoteChannelNumber,
Message = sftpMessage,
};
this._session.SendMessage(message);
}
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;
try
{
// TODO: Check to run on different thread
this.HandleMessage(sftpMessage);
}
catch (Exception exp)
{
this._exception = exp;
this._errorOccuredWaitHandle.Set();
}
}
#region Handle SFTP incoming messages and raise appropriate events
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));
}
}
private void HandleMessage(StatusMessage message)
{
if (this.StatusMessageReceived != null)
{
this.StatusMessageReceived(this, new MessageEventArgs<StatusMessage>(message));
}
}
private void HandleMessage(DataMessage message)
{
if (this.DataMessageReceived != null)
{
this.DataMessageReceived(this, new MessageEventArgs<DataMessage>(message));
}
}
private void HandleMessage(HandleMessage message)
{
if (this.HandleMessageReceived != null)
{
this.HandleMessageReceived(this, new MessageEventArgs<HandleMessage>(message));
}
}
private void HandleMessage(NameMessage message)
{
if (this.NameMessageReceived != null)
{
this.NameMessageReceived(this, new MessageEventArgs<NameMessage>(message));
}
}
private void HandleMessage(AttributesMessage message)
{
if (this.AttributesMessageReceived != null)
{
this.AttributesMessageReceived(this, new MessageEventArgs<AttributesMessage>(message));
}
}
#endregion
private void Session_Disconnected(object sender, EventArgs e)
{
// TODO: Do something when session is closed and operation still in progress
}
private void Session_ErrorOccured(object sender, ErrorEventArgs e)
{
this._exception = e.GetException();
this._errorOccuredWaitHandle.Set();
}
private void SendMessage(SftpMessage sftpMessage)
{
var message = new SftpDataMessage
{
LocalChannelNumber = this._channel.RemoteChannelNumber,
Message = sftpMessage,
};
this._session.SendMessage(message);
}
internal void WaitHandle(WaitHandle waitHandle)
{
var waitHandles = new WaitHandle[]
{
this._errorOccuredWaitHandle,
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."));
}
}
}
}
@@ -0,0 +1,77 @@
using System.IO;
using System.Linq;
using Renci.SshClient.Messages.Sftp;
namespace Renci.SshClient.Sftp
{
internal class UploadFileCommand : SftpCommand
{
private string _path;
private Stream _input;
private string _handle;
private byte[] _buffer;
private ulong _offset;
private bool _hasMoreData;
public UploadFileCommand(SftpSession sftpSession, string path, Stream input)
: base(sftpSession)
{
this._path = path;
this._input = input;
// TODO: Make this field to be customizable
this._buffer = new byte[1024];
}
protected override void OnExecute()
{
this.SendOpenMessage(this._path, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate);
}
protected override void OnHandle(string handle)
{
base.OnHandle(handle);
this._handle = handle;
this.SendData();
}
protected override void OnStatus(StatusCodes statusCode, string errorMessage, string language)
{
base.OnStatus(statusCode, errorMessage, language);
if (statusCode == StatusCodes.Ok)
{
if (this._hasMoreData)
{
this.SendData();
}
}
}
protected override void OnHandleClosed()
{
base.OnHandleClosed();
this.CompleteExecution();
}
private void SendData()
{
var bytesRead = this._input.Read(this._buffer, 0, this._buffer.Length);
this._hasMoreData = (bytesRead > 0);
if (this._hasMoreData)
{
this.SendWriteMessage(this._handle, this._offset, this._buffer.Take(bytesRead).GetSshString());
this._offset += (ulong)bytesRead;
}
else
{
this.SendCloseMessage(this._handle);
}
}
}
}
@@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.IO;
using Renci.SshClient.Sftp;
namespace Renci.SshClient
{
public class SftpClient
{
private SftpSession _sftpSession;
private Session _session;
public ConnectionInfo ConnectionInfo { get; private set; }
public bool IsConnected
{
get
{
if (this._session == null)
{
return false;
}
else
{
return this._session.IsConnected;
}
}
}
public int OperationTimeout { get; set; }
public int BufferSize { get; set; }
#region Constructors
public SftpClient(ConnectionInfo connectionInfo)
{
this.OperationTimeout = -1;
this.ConnectionInfo = connectionInfo;
}
public SftpClient(string host, int port, string username, string password)
: this(new ConnectionInfo
{
Host = host,
Port = port,
Username = username,
Password = password,
})
{
}
public SftpClient(string host, string username, string password)
: this(new ConnectionInfo
{
Host = host,
Username = username,
Password = password,
})
{
}
public SftpClient(string host, int port, string username, PrivateKeyFile keyFile)
: this(new ConnectionInfo
{
Host = host,
Port = port,
Username = username,
KeyFile = keyFile,
})
{
}
public SftpClient(string host, string username, PrivateKeyFile keyFile)
: this(new ConnectionInfo
{
Host = host,
Username = username,
KeyFile = keyFile,
})
{
}
#endregion
public void Connect()
{
if (this._session == null)
{
this._session = new Session(this.ConnectionInfo);
}
this._session.Connect();
this._sftpSession = new SftpSession(this._session, this.OperationTimeout);
this._sftpSession.Connect();
}
public void Disconnect()
{
this._sftpSession.Disconnect();
this._session.Disconnect();
this._session = null;
}
#region List Directory
public IAsyncResult BeginListDirectory(string path, AsyncCallback asyncCallback, object state)
{
var cmd = new ListDirectoryCommand(this._sftpSession, path);
return cmd.BeginExecute(asyncCallback, state);
}
public IEnumerable<SftpFile> EndListDirectory(IAsyncResult asyncResult)
{
var sftpAsyncResult = asyncResult as SftpAsyncResult;
if (sftpAsyncResult == null)
{
throw new InvalidOperationException("Not valid IAsyncResult object.");
}
sftpAsyncResult.Command.EndExecute(sftpAsyncResult);
var cmd = sftpAsyncResult.Command as ListDirectoryCommand;
if (cmd == null)
{
throw new InvalidOperationException("Not valid IAsyncResult object.");
}
return cmd.Files;
}
public IEnumerable<SftpFile> ListDirectory(string path)
{
return this.EndListDirectory(this.BeginListDirectory(path, null, null));
}
#endregion
public void RenameFile(string oldFilename, string newFileName)
{
}
public void RemoveFile(string filename)
{
}
public void RemoveDirectory(string path)
{
}
public void CreateDirectory(string path)
{
}
#region Upload File
public IAsyncResult BeginUploadFile(string filename, Stream input, AsyncCallback asyncCallback, object state)
{
var cmd = new UploadFileCommand(this._sftpSession, filename, input);
return cmd.BeginExecute(asyncCallback, state);
}
public void EndUploadFile(IAsyncResult asyncResult)
{
var sftpAsyncResult = asyncResult as SftpAsyncResult;
if (sftpAsyncResult == null)
{
throw new InvalidOperationException("Not valid IAsyncResult object.");
}
sftpAsyncResult.Command.EndExecute(sftpAsyncResult);
var cmd = sftpAsyncResult.Command as UploadFileCommand;
if (cmd == null)
{
throw new InvalidOperationException("Not valid IAsyncResult object.");
}
}
public void UploadFile(string filename, Stream input)
{
this.EndUploadFile(this.BeginUploadFile(filename, input, null, null));
}
#endregion
#region Download File
public IAsyncResult BeginDownload(string filename, Stream output, AsyncCallback asyncCallback, object state)
{
var cmd = new DownloadFileCommand(this._sftpSession, filename, output);
return cmd.BeginExecute(asyncCallback, state);
}
public void EndDownload(IAsyncResult asyncResult)
{
var sftpAsyncResult = asyncResult as SftpAsyncResult;
if (sftpAsyncResult == null)
{
throw new InvalidOperationException("Not valid IAsyncResult object.");
}
sftpAsyncResult.Command.EndExecute(sftpAsyncResult);
var cmd = sftpAsyncResult.Command as DownloadFileCommand;
if (cmd == null)
{
throw new InvalidOperationException("Not valid IAsyncResult object.");
}
}
public void Download(string filename, Stream output)
{
this.EndDownload(this.BeginDownload(filename, output, null, null));
}
#endregion
public void ChangeDirectory(string path)
{
}
}
}
@@ -1,69 +0,0 @@
namespace Renci.SshClient
{
public abstract class SshBase
{
public ConnectionInfo ConnectionInfo { get; private set; }
internal Session Session { get; private set; }
public SshBase(ConnectionInfo connectionInfo)
{
this.ConnectionInfo = connectionInfo;
this.Session = new Session(this.ConnectionInfo);
}
public SshBase(string host, int port, string username, string password)
: this(new ConnectionInfo
{
Host = host,
Port = port,
Username = username,
Password = password,
})
{
}
public SshBase(string host, string username, string password)
: this(new ConnectionInfo
{
Host = host,
Username = username,
Password = password,
})
{
}
public SshBase(string host, int port, string username, PrivateKeyFile keyFile)
: this(new ConnectionInfo
{
Host = host,
Port = port,
Username = username,
KeyFile = keyFile,
})
{
}
public SshBase(string host, string username, PrivateKeyFile keyFile)
: this(new ConnectionInfo
{
Host = host,
Username = username,
KeyFile = keyFile,
})
{
}
public void Connect()
{
this.Session.Connect();
}
public virtual void Disconnect()
{
this.Session.Disconnect();
}
}
}