using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Globalization; using System.Linq; using System.Net; using System.Text; using System.Threading; using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Sftp; namespace Renci.SshNet { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// public class SftpClient : BaseClient { /// /// Holds the instance that is used to communicate to the /// SFTP server. /// private ISftpSession _sftpSession; /// /// Holds the operation timeout. /// private int _operationTimeout; /// /// Holds the size of the buffer. /// private uint _bufferSize; /// /// Gets or sets the operation timeout. /// /// /// The timeout to wait until an operation completes. The default value is negative /// one (-1) milliseconds, which indicates an infinite timeout period. /// /// The method was called after the client was disposed. public TimeSpan OperationTimeout { get { CheckDisposed(); return TimeSpan.FromMilliseconds(_operationTimeout); } set { CheckDisposed(); var timeoutInMilliseconds = value.TotalMilliseconds; if (timeoutInMilliseconds < -1d || timeoutInMilliseconds > int.MaxValue) throw new ArgumentOutOfRangeException("timeout", "The timeout must represent a value between -1 and Int32.MaxValue, inclusive."); _operationTimeout = (int) timeoutInMilliseconds; } } /// /// Gets or sets the maximum size of the buffer in bytes. /// /// /// The size of the buffer. The default buffer size is 32768 bytes (32 KB). /// /// /// /// For write operations, this limits the size of the payload for /// individual SSH_FXP_WRITE messages. The actual size is always /// capped at the maximum packet size supported by the peer /// (minus the size of protocol fields). /// /// /// For read operations, this controls the size of the payload which /// is requested from the peer in each SSH_FXP_READ message. The peer /// will send the requested number of bytes in one or more SSH_FXP_DATA /// messages. To optimize the size of the SSH packets sent by the peer, /// the actual requested size will take into account the size of the /// SSH_FXP_DATA protocol fields. /// /// /// The size of the each indivual SSH_FXP_DATA message is limited to the /// local maximum packet size of the channel, which is set to 64 KB /// for SSH.NET. However, the peer can limit this even further. /// /// /// The method was called after the client was disposed. public uint BufferSize { get { CheckDisposed(); return _bufferSize; } set { CheckDisposed(); _bufferSize = value; } } /// /// Gets remote working directory. /// /// Client is not connected. /// The method was called after the client was disposed. public string WorkingDirectory { get { CheckDisposed(); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); return _sftpSession.WorkingDirectory; } } /// /// Gets sftp protocol version. /// /// Client is not connected. /// The method was called after the client was disposed. public int ProtocolVersion { get { CheckDisposed(); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); return (int) _sftpSession.ProtocolVersion; } } #region Constructors /// /// Initializes a new instance of the class. /// /// The connection info. /// is null. public SftpClient(ConnectionInfo connectionInfo) : this(connectionInfo, false) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Connection port. /// Authentication username. /// Authentication password. /// is null. /// is invalid. -or- is null or contains only whitespace characters. /// is not within and . [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SftpClient(string host, int port, string username, string password) : this(new PasswordConnectionInfo(host, port, username, password), true) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Authentication username. /// Authentication password. /// is null. /// is invalid. -or- is null contains only whitespace characters. public SftpClient(string host, string username, string password) : this(host, ConnectionInfo.DefaultPort, username, password) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Connection port. /// Authentication username. /// Authentication private key file(s) . /// is null. /// is invalid. -or- is nunullll or contains only whitespace characters. /// is not within and . [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SftpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles) : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), true) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Authentication username. /// Authentication private key file(s) . /// is null. /// is invalid. -or- is null or contains only whitespace characters. public SftpClient(string host, string username, params PrivateKeyFile[] keyFiles) : this(host, ConnectionInfo.DefaultPort, username, keyFiles) { } /// /// Initializes a new instance of the class. /// /// The connection info. /// Specified whether this instance owns the connection info. /// is null. /// /// If is true, the connection info will be disposed when this /// instance is disposed. /// private SftpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo) : this(connectionInfo, ownsConnectionInfo, new ServiceFactory()) { } /// /// Initializes a new instance of the class. /// /// The connection info. /// Specified whether this instance owns the connection info. /// The factory to use for creating new services. /// is null. /// is null. /// /// If is true, the connection info will be disposed when this /// instance is disposed. /// internal SftpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) : base(connectionInfo, ownsConnectionInfo, serviceFactory) { _operationTimeout = SshNet.Session.Infinite; BufferSize = 1024 * 32; } #endregion Constructors /// /// Changes remote directory to path. /// /// New directory path. /// is null. /// Client is not connected. /// Permission to change directory denied by remote host. -or- A SSH command was denied by the server. /// was not found on the remote host. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void ChangeDirectory(string path) { CheckDisposed(); if (path == null) throw new ArgumentNullException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); _sftpSession.ChangeDirectory(path); } /// /// Changes permissions of file(s) to specified mode. /// /// File(s) path, may match multiple files. /// The mode. /// is null. /// Client is not connected. /// Permission to change permission on the path(s) was denied by the remote host. -or- A SSH command was denied by the server. /// was not found on the remote host. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void ChangePermissions(string path, short mode) { var file = Get(path); file.SetPermissions(mode); } /// /// Creates remote directory specified by path. /// /// Directory path to create. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to create the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void CreateDirectory(string path) { CheckDisposed(); if (path.IsNullOrWhiteSpace()) throw new ArgumentException(path); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); _sftpSession.RequestMkDir(fullPath); } /// /// Deletes remote directory specified by path. /// /// Directory to be deleted path. /// is null or contains only whitespace characters. /// Client is not connected. /// was not found on the remote host. /// Permission to delete the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void DeleteDirectory(string path) { CheckDisposed(); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); _sftpSession.RequestRmDir(fullPath); } /// /// Deletes remote file specified by path. /// /// File to be deleted path. /// is null or contains only whitespace characters. /// Client is not connected. /// was not found on the remote host. /// Permission to delete the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void DeleteFile(string path) { CheckDisposed(); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); _sftpSession.RequestRemove(fullPath); } /// /// Renames remote file from old path to new path. /// /// Path to the old file location. /// Path to the new file location. /// is null. -or- or is null. /// Client is not connected. /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void RenameFile(string oldPath, string newPath) { RenameFile(oldPath, newPath, false); } /// /// Renames remote file from old path to new path. /// /// Path to the old file location. /// Path to the new file location. /// if set to true then perform a posix rename. /// is null. -or- or is null. /// Client is not connected. /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void RenameFile(string oldPath, string newPath, bool isPosix) { CheckDisposed(); if (oldPath == null) throw new ArgumentNullException("oldPath"); if (newPath == null) throw new ArgumentNullException("newPath"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var oldFullPath = _sftpSession.GetCanonicalPath(oldPath); var newFullPath = _sftpSession.GetCanonicalPath(newPath); if (isPosix) { _sftpSession.RequestPosixRename(oldFullPath, newFullPath); } else { _sftpSession.RequestRename(oldFullPath, newFullPath); } } /// /// Creates a symbolic link from old path to new path. /// /// The old path. /// The new path. /// is null. -or- is null or contains only whitespace characters. /// Client is not connected. /// Permission to create the symbolic link was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public void SymbolicLink(string path, string linkPath) { CheckDisposed(); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (linkPath.IsNullOrWhiteSpace()) throw new ArgumentException("linkPath"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); var linkFullPath = _sftpSession.GetCanonicalPath(linkPath); _sftpSession.RequestSymLink(fullPath, linkFullPath); } /// /// Retrieves list of files in remote directory. /// /// The path. /// The list callback. /// /// A list of files. /// /// is null. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public IEnumerable ListDirectory(string path, Action listCallback = null) { CheckDisposed(); return InternalListDirectory(path, listCallback); } /// /// Begins an asynchronous operation of retrieving list of files in remote directory. /// /// The path. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The list callback. /// /// An that references the asynchronous operation. /// /// The method was called after the client was disposed. public IAsyncResult BeginListDirectory(string path, AsyncCallback asyncCallback, object state, Action listCallback = null) { CheckDisposed(); var asyncResult = new SftpListDirectoryAsyncResult(asyncCallback, state); ThreadAbstraction.ExecuteThread(() => { try { var result = InternalListDirectory(path, count => { asyncResult.Update(count); if (listCallback != null) { listCallback(count); } }); asyncResult.SetAsCompleted(result, false); } catch (Exception exp) { asyncResult.SetAsCompleted(exp, false); } }); return asyncResult; } /// /// Ends an asynchronous operation of retrieving list of files in remote directory. /// /// The pending asynchronous SFTP request. /// /// A list of files. /// /// The object did not come from the corresponding async method on this type.-or- was called multiple times with the same . public IEnumerable EndListDirectory(IAsyncResult asyncResult) { var ar = asyncResult as SftpListDirectoryAsyncResult; if (ar == null || ar.EndInvokeCalled) throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); // Wait for operation to complete, then return result or throw exception return ar.EndInvoke(); } /// /// Gets reference to remote file or directory. /// /// The path. /// /// A reference to file object. /// /// Client is not connected. /// was not found on the remote host. /// is null. /// The method was called after the client was disposed. public SftpFile Get(string path) { CheckDisposed(); if (path == null) throw new ArgumentNullException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); var attributes = _sftpSession.RequestLStat(fullPath); return new SftpFile(_sftpSession, fullPath, attributes); } /// /// Checks whether file or directory exists; /// /// The path. /// /// true if directory or file exists; otherwise false. /// /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. public bool Exists(string path) { CheckDisposed(); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); // using SSH_FXP_REALPATH is not an alternative as the SFTP specification has not always // been clear on how the server should respond when the specified path is not present on // the server: // // SSH 1 to 4: // No mention of how the server should respond if the path is not present on the server. // // SSH 5: // The server SHOULD fail the request if the path is not present on the server. // // SSH 6: // Draft 06: The server SHOULD fail the request if the path is not present on the server. // Draft 07 to 13: The server MUST NOT fail the request if the path does not exist. // // Note that SSH 6 (draft 06 and forward) allows for more control options, but we // currently only support up to v3. try { _sftpSession.RequestLStat(fullPath); return true; } catch (SftpPathNotFoundException) { return false; } } /// /// Downloads remote file specified by the path into the stream. /// /// File to download. /// Stream to write the file into. /// The download callback. /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// was not found on the remote host./// /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public void DownloadFile(string path, Stream output, Action downloadCallback = null) { CheckDisposed(); InternalDownloadFile(path, output, null, downloadCallback); } /// /// Begins an asynchronous file downloading into the stream. /// /// The path. /// The output. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginDownloadFile(string path, Stream output) { return BeginDownloadFile(path, output, null, null); } /// /// Begins an asynchronous file downloading into the stream. /// /// The path. /// The output. /// The method to be called when the asynchronous write operation is completed. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginDownloadFile(string path, Stream output, AsyncCallback asyncCallback) { return BeginDownloadFile(path, output, asyncCallback, null); } /// /// Begins an asynchronous file downloading into the stream. /// /// The path. /// The output. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The download callback. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains only whitespace characters. /// The method was called after the client was disposed. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginDownloadFile(string path, Stream output, AsyncCallback asyncCallback, object state, Action downloadCallback = null) { CheckDisposed(); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (output == null) throw new ArgumentNullException("output"); var asyncResult = new SftpDownloadAsyncResult(asyncCallback, state); ThreadAbstraction.ExecuteThread(() => { try { InternalDownloadFile(path, output, asyncResult, offset => { asyncResult.Update(offset); if (downloadCallback != null) { downloadCallback(offset); } }); asyncResult.SetAsCompleted(null, false); } catch (Exception exp) { asyncResult.SetAsCompleted(exp, false); } }); return asyncResult; } /// /// Ends an asynchronous file downloading into the stream. /// /// The pending asynchronous SFTP request. /// The object did not come from the corresponding async method on this type.-or- was called multiple times with the same . /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// The path was not found on the remote host. /// A SSH error where is the message from the remote host. public void EndDownloadFile(IAsyncResult asyncResult) { var ar = asyncResult as SftpDownloadAsyncResult; if (ar == null || ar.EndInvokeCalled) throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); // Wait for operation to complete, then return result or throw exception ar.EndInvoke(); } /// /// Uploads stream into remote file. /// /// Data input stream. /// Remote file path. /// The upload callback. /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to upload the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public void UploadFile(Stream input, string path, Action uploadCallback = null) { UploadFile(input, path, true, uploadCallback); } /// /// Uploads stream into remote file. /// /// Data input stream. /// Remote file path. /// if set to true then existing file will be overwritten. /// The upload callback. /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to upload the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public void UploadFile(Stream input, string path, bool canOverride, Action uploadCallback = null) { CheckDisposed(); var flags = Flags.Write | Flags.Truncate; if (canOverride) flags |= Flags.CreateNewOrOpen; else flags |= Flags.CreateNew; InternalUploadFile(input, path, flags, null, uploadCallback); } /// /// Begins an asynchronous uploading the stream into remote file. /// /// Data input stream. /// Remote file path. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// /// /// If the remote file already exists, it is overwritten and truncated. /// /// public IAsyncResult BeginUploadFile(Stream input, string path) { return BeginUploadFile(input, path, true, null, null); } /// /// Begins an asynchronous uploading the stream into remote file. /// /// Data input stream. /// Remote file path. /// The method to be called when the asynchronous write operation is completed. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// /// /// If the remote file already exists, it is overwritten and truncated. /// /// public IAsyncResult BeginUploadFile(Stream input, string path, AsyncCallback asyncCallback) { return BeginUploadFile(input, path, true, asyncCallback, null); } /// /// Begins an asynchronous uploading the stream into remote file. /// /// Data input stream. /// Remote file path. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The upload callback. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains only whitespace characters. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// The method was called after the client was disposed. /// /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// /// /// If the remote file already exists, it is overwritten and truncated. /// /// public IAsyncResult BeginUploadFile(Stream input, string path, AsyncCallback asyncCallback, object state, Action uploadCallback = null) { return BeginUploadFile(input, path, true, asyncCallback, state, uploadCallback); } /// /// Begins an asynchronous uploading the stream into remote file. /// /// Data input stream. /// Remote file path. /// Specified whether an existing file can be overwritten. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The upload callback. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains only whitespace characters. /// The method was called after the client was disposed. /// /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// /// /// When refers to an existing file, set to true to overwrite and truncate that file. /// If is false, the upload will fail and will throw an /// . /// /// public IAsyncResult BeginUploadFile(Stream input, string path, bool canOverride, AsyncCallback asyncCallback, object state, Action uploadCallback = null) { CheckDisposed(); if (input == null) throw new ArgumentNullException("input"); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); var flags = Flags.Write | Flags.Truncate; if (canOverride) flags |= Flags.CreateNewOrOpen; else flags |= Flags.CreateNew; var asyncResult = new SftpUploadAsyncResult(asyncCallback, state); ThreadAbstraction.ExecuteThread(() => { try { InternalUploadFile(input, path, flags, asyncResult, offset => { asyncResult.Update(offset); if (uploadCallback != null) { uploadCallback(offset); } }); asyncResult.SetAsCompleted(null, false); } catch (Exception exp) { asyncResult.SetAsCompleted(exp, false); } }); return asyncResult; } /// /// Ends an asynchronous uploading the stream into remote file. /// /// The pending asynchronous SFTP request. /// The object did not come from the corresponding async method on this type.-or- was called multiple times with the same . /// Client is not connected. /// The directory of the file was not found on the remote host. /// Permission to upload the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public void EndUploadFile(IAsyncResult asyncResult) { var ar = asyncResult as SftpUploadAsyncResult; if (ar == null || ar.EndInvokeCalled) throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); // Wait for operation to complete, then return result or throw exception ar.EndInvoke(); } /// /// Gets status using statvfs@openssh.com request. /// /// The path. /// /// A instance that contains file status information. /// /// Client is not connected. /// is null. /// The method was called after the client was disposed. public SftpFileSytemInformation GetStatus(string path) { CheckDisposed(); if (path == null) throw new ArgumentNullException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); return _sftpSession.RequestStatVfs(fullPath); } #region File Methods /// /// Appends lines to a file, and closes the file. /// /// The file to append the lines to. The file is created if it does not already exist. /// The lines to append to the file. /// isnull -or- is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public void AppendAllLines(string path, IEnumerable contents) { CheckDisposed(); if (contents == null) throw new ArgumentNullException("contents"); using (var stream = AppendText(path)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Appends lines to a file by using a specified encoding, and closes the file. /// /// The file to append the lines to. The file is created if it does not already exist. /// The lines to append to the file. /// The character encoding to use. /// is null. -or- is null. -or- is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public void AppendAllLines(string path, IEnumerable contents, Encoding encoding) { CheckDisposed(); if (contents == null) throw new ArgumentNullException("contents"); using (var stream = AppendText(path, encoding)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Appends the specified string to the file, and closes the file. /// /// The file to append the specified string to. /// The string to append to the file. /// is null. -or- is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public void AppendAllText(string path, string contents) { using (var stream = AppendText(path)) { stream.Write(contents); } } /// /// Appends the specified string to the file, and closes the file. /// /// The file to append the specified string to. /// The string to append to the file. /// The character encoding to use. /// is null. -or- is null. -or- is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public void AppendAllText(string path, string contents, Encoding encoding) { using (var stream = AppendText(path, encoding)) { stream.Write(contents); } } /// /// Creates a that appends UTF-8 encoded text to an existing file. /// /// The path to the file to append to. /// /// A that appends UTF-8 encoded text to an existing file. /// /// is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public StreamWriter AppendText(string path) { return AppendText(path, Encoding.UTF8); } /// /// Creates a that appends text to an existing file using the specified encoding. /// /// The path to the file to append to. /// The character encoding to use. /// /// A that appends text to an existing file using the specified encoding. /// /// is null. -or- is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public StreamWriter AppendText(string path, Encoding encoding) { CheckDisposed(); if (encoding == null) throw new ArgumentNullException("encoding"); return new StreamWriter(new SftpFileStream(_sftpSession, path, FileMode.Append, FileAccess.Write, (int) _bufferSize), encoding); } /// /// Creates or overwrites a file in the specified path. /// /// The path and name of the file to create. /// /// A that provides read/write access to the file specified in path. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// If the target file already exists, it is first truncated to zero bytes. /// public SftpFileStream Create(string path) { CheckDisposed(); return new SftpFileStream(_sftpSession, path, FileMode.Create, FileAccess.ReadWrite, (int) _bufferSize); } /// /// Creates or overwrites the specified file. /// /// The path and name of the file to create. /// The maximum number of bytes buffered for reads and writes to the file. /// /// A that provides read/write access to the file specified in path. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// If the target file already exists, it is first truncated to zero bytes. /// public SftpFileStream Create(string path, int bufferSize) { CheckDisposed(); return new SftpFileStream(_sftpSession, path, FileMode.Create, FileAccess.ReadWrite, bufferSize); } /// /// Creates or opens a file for writing UTF-8 encoded text. /// /// The file to be opened for writing. /// /// A that writes to the specified file using UTF-8 encoding. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public StreamWriter CreateText(string path) { return CreateText(path, Encoding.UTF8); } /// /// Creates or opens a file for writing text using the specified encoding. /// /// The file to be opened for writing. /// The character encoding to use. /// /// A that writes to the specified file using the specified encoding. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public StreamWriter CreateText(string path, Encoding encoding) { CheckDisposed(); return new StreamWriter(OpenWrite(path), encoding); } /// /// Deletes the specified file or directory. /// /// The name of the file or directory to be deleted. Wildcard characters are not supported. /// is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public void Delete(string path) { var file = Get(path); file.Delete(); } /// /// Returns the date and time the specified file or directory was last accessed. /// /// The file or directory for which to obtain access date and time information. /// /// A structure set to the date and time that the specified file or directory was last accessed. /// This value is expressed in local time. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public DateTime GetLastAccessTime(string path) { var file = Get(path); return file.LastAccessTime; } /// /// Returns the date and time, in coordinated universal time (UTC), that the specified file or directory was last accessed. /// /// The file or directory for which to obtain access date and time information. /// /// A structure set to the date and time that the specified file or directory was last accessed. /// This value is expressed in UTC time. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public DateTime GetLastAccessTimeUtc(string path) { var lastAccessTime = GetLastAccessTime(path); return lastAccessTime.ToUniversalTime(); } /// /// Returns the date and time the specified file or directory was last written to. /// /// The file or directory for which to obtain write date and time information. /// /// A structure set to the date and time that the specified file or directory was last written to. /// This value is expressed in local time. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public DateTime GetLastWriteTime(string path) { var file = Get(path); return file.LastWriteTime; } /// /// Returns the date and time, in coordinated universal time (UTC), that the specified file or directory was last written to. /// /// The file or directory for which to obtain write date and time information. /// /// A structure set to the date and time that the specified file or directory was last written to. /// This value is expressed in UTC time. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public DateTime GetLastWriteTimeUtc(string path) { var lastWriteTime = GetLastWriteTime(path); return lastWriteTime.ToUniversalTime(); } /// /// Opens a on the specified path with read/write access. /// /// The file to open. /// A value that specifies whether a file is created if one does not exist, and determines whether the contents of existing files are retained or overwritten. /// /// An unshared that provides access to the specified file, with the specified mode and read/write access. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public SftpFileStream Open(string path, FileMode mode) { return Open(path, mode, FileAccess.ReadWrite); } /// /// Opens a on the specified path, with the specified mode and access. /// /// The file to open. /// A value that specifies whether a file is created if one does not exist, and determines whether the contents of existing files are retained or overwritten. /// A value that specifies the operations that can be performed on the file. /// /// An unshared that provides access to the specified file, with the specified mode and access. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public SftpFileStream Open(string path, FileMode mode, FileAccess access) { CheckDisposed(); return new SftpFileStream(_sftpSession, path, mode, access, (int) _bufferSize); } /// /// Opens an existing file for reading. /// /// The file to be opened for reading. /// /// A read-only on the specified path. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public SftpFileStream OpenRead(string path) { return Open(path, FileMode.Open, FileAccess.Read); } /// /// Opens an existing UTF-8 encoded text file for reading. /// /// The file to be opened for reading. /// /// A on the specified path. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public StreamReader OpenText(string path) { return new StreamReader(OpenRead(path), Encoding.UTF8); } /// /// Opens a file for writing. /// /// The file to be opened for writing. /// /// An unshared object on the specified path with access. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// If the file does not exist, it is created. /// public SftpFileStream OpenWrite(string path) { CheckDisposed(); return new SftpFileStream(_sftpSession, path, FileMode.OpenOrCreate, FileAccess.Write, (int) _bufferSize); } /// /// Opens a binary file, reads the contents of the file into a byte array, and closes the file. /// /// The file to open for reading. /// /// A byte array containing the contents of the file. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public byte[] ReadAllBytes(string path) { using (var stream = OpenRead(path)) { var buffer = new byte[stream.Length]; stream.Read(buffer, 0, buffer.Length); return buffer; } } /// /// Opens a text file, reads all lines of the file using UTF-8 encoding, and closes the file. /// /// The file to open for reading. /// /// A string array containing all lines of the file. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public string[] ReadAllLines(string path) { return ReadAllLines(path, Encoding.UTF8); } /// /// Opens a file, reads all lines of the file with the specified encoding, and closes the file. /// /// The file to open for reading. /// The encoding applied to the contents of the file. /// /// A string array containing all lines of the file. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public string[] ReadAllLines(string path, Encoding encoding) { var lines = new List(); using (var stream = new StreamReader(OpenRead(path), encoding)) { while (!stream.EndOfStream) { lines.Add(stream.ReadLine()); } } return lines.ToArray(); } /// /// Opens a text file, reads all lines of the file with the UTF-8 encoding, and closes the file. /// /// The file to open for reading. /// /// A string containing all lines of the file. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public string ReadAllText(string path) { return ReadAllText(path, Encoding.UTF8); } /// /// Opens a file, reads all lines of the file with the specified encoding, and closes the file. /// /// The file to open for reading. /// The encoding applied to the contents of the file. /// /// A string containing all lines of the file. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public string ReadAllText(string path, Encoding encoding) { using (var stream = new StreamReader(OpenRead(path), encoding)) { return stream.ReadToEnd(); } } /// /// Reads the lines of a file with the UTF-8 encoding. /// /// The file to read. /// /// The lines of the file. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public IEnumerable ReadLines(string path) { return ReadAllLines(path); } /// /// Read the lines of a file that has a specified encoding. /// /// The file to read. /// The encoding that is applied to the contents of the file. /// /// The lines of the file. /// /// is null. /// Client is not connected. /// The method was called after the client was disposed. public IEnumerable ReadLines(string path, Encoding encoding) { return ReadAllLines(path, encoding); } /// /// Sets the date and time the specified file was last accessed. /// /// The file for which to set the access date and time information. /// A containing the value to set for the last access date and time of path. This value is expressed in local time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastAccessTime(string path, DateTime lastAccessTime) { throw new NotImplementedException(); } /// /// Sets the date and time, in coordinated universal time (UTC), that the specified file was last accessed. /// /// The file for which to set the access date and time information. /// A containing the value to set for the last access date and time of path. This value is expressed in UTC time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc) { throw new NotImplementedException(); } /// /// Sets the date and time that the specified file was last written to. /// /// The file for which to set the date and time information. /// A containing the value to set for the last write date and time of path. This value is expressed in local time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastWriteTime(string path, DateTime lastWriteTime) { throw new NotImplementedException(); } /// /// Sets the date and time, in coordinated universal time (UTC), that the specified file was last written to. /// /// The file for which to set the date and time information. /// A containing the value to set for the last write date and time of path. This value is expressed in UTC time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc) { throw new NotImplementedException(); } /// /// Writes the specified byte array to the specified file, and closes the file. /// /// The file to write to. /// The bytes to write to the file. /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public void WriteAllBytes(string path, byte[] bytes) { using (var stream = OpenWrite(path)) { stream.Write(bytes, 0, bytes.Length); } } /// /// Writes a collection of strings to the file using the UTF-8 encoding, and closes the file. /// /// The file to write to. /// The lines to write to the file. /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public void WriteAllLines(string path, IEnumerable contents) { WriteAllLines(path, contents, Encoding.UTF8); } /// /// Write the specified string array to the file using the UTF-8 encoding, and closes the file. /// /// The file to write to. /// The string array to write to the file. /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public void WriteAllLines(string path, string[] contents) { WriteAllLines(path, contents, Encoding.UTF8); } /// /// Writes a collection of strings to the file using the specified encoding, and closes the file. /// /// The file to write to. /// The lines to write to the file. /// The character encoding to use. /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public void WriteAllLines(string path, IEnumerable contents, Encoding encoding) { using (var stream = CreateText(path, encoding)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Writes the specified string array to the file by using the specified encoding, and closes the file. /// /// The file to write to. /// The string array to write to the file. /// An object that represents the character encoding applied to the string array. /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public void WriteAllLines(string path, string[] contents, Encoding encoding) { using (var stream = CreateText(path, encoding)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Writes the specified string to the file using the UTF-8 encoding, and closes the file. /// /// The file to write to. /// The string to write to the file. /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public void WriteAllText(string path, string contents) { using (var stream = CreateText(path)) { stream.Write(contents); } } /// /// Writes the specified string to the file using the specified encoding, and closes the file. /// /// The file to write to. /// The string to write to the file. /// The encoding to apply to the string. /// is null. /// Client is not connected. /// The method was called after the client was disposed. /// /// /// If the target file already exists, it is overwritten. It is not first truncated to zero bytes. /// /// /// If the target file does not exist, it is created. /// /// public void WriteAllText(string path, string contents, Encoding encoding) { using (var stream = CreateText(path, encoding)) { stream.Write(contents); } } /// /// Gets the of the file on the path. /// /// The path to the file. /// /// The of the file on the path. /// /// is null. /// Client is not connected. /// was not found on the remote host. /// The method was called after the client was disposed. public SftpFileAttributes GetAttributes(string path) { CheckDisposed(); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); return _sftpSession.RequestLStat(fullPath); } /// /// Sets the specified of the file on the specified path. /// /// The path to the file. /// The desired . /// is null. /// Client is not connected. /// The method was called after the client was disposed. public void SetAttributes(string path, SftpFileAttributes fileAttributes) { CheckDisposed(); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); _sftpSession.RequestSetStat(fullPath, fileAttributes); } // Please don't forget this when you implement these methods: is null. //public FileSecurity GetAccessControl(string path); //public FileSecurity GetAccessControl(string path, AccessControlSections includeSections); //public DateTime GetCreationTime(string path); //public DateTime GetCreationTimeUtc(string path); //public void SetAccessControl(string path, FileSecurity fileSecurity); //public void SetCreationTime(string path, DateTime creationTime); //public void SetCreationTimeUtc(string path, DateTime creationTimeUtc); #endregion // File Methods #region SynchronizeDirectories /// /// Synchronizes the directories. /// /// The source path. /// The destination path. /// The search pattern. /// /// A list of uploaded files. /// /// is null. /// is null or contains only whitespace. /// was not found on the remote host. public IEnumerable SynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern) { if (sourcePath == null) throw new ArgumentNullException("sourcePath"); if (destinationPath.IsNullOrWhiteSpace()) throw new ArgumentException("destinationPath"); return InternalSynchronizeDirectories(sourcePath, destinationPath, searchPattern, null); } /// /// Begins the synchronize directories. /// /// The source path. /// The destination path. /// The search pattern. /// The async callback. /// The state. /// /// An that represents the asynchronous directory synchronization. /// /// is null. /// is null or contains only whitespace. public IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, AsyncCallback asyncCallback, object state) { if (sourcePath == null) throw new ArgumentNullException("sourcePath"); if (destinationPath.IsNullOrWhiteSpace()) throw new ArgumentException("destDir"); var asyncResult = new SftpSynchronizeDirectoriesAsyncResult(asyncCallback, state); ThreadAbstraction.ExecuteThread(() => { try { var result = InternalSynchronizeDirectories(sourcePath, destinationPath, searchPattern, asyncResult); asyncResult.SetAsCompleted(result, false); } catch (Exception exp) { asyncResult.SetAsCompleted(exp, false); } }); return asyncResult; } /// /// Ends the synchronize directories. /// /// The async result. /// /// A list of uploaded files. /// /// The object did not come from the corresponding async method on this type.-or- was called multiple times with the same . /// The destination path was not found on the remote host. public IEnumerable EndSynchronizeDirectories(IAsyncResult asyncResult) { var ar = asyncResult as SftpSynchronizeDirectoriesAsyncResult; if (ar == null || ar.EndInvokeCalled) throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); // Wait for operation to complete, then return result or throw exception return ar.EndInvoke(); } private IEnumerable InternalSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, SftpSynchronizeDirectoriesAsyncResult asynchResult) { if (!Directory.Exists(sourcePath)) throw new FileNotFoundException(string.Format("Source directory not found: {0}", sourcePath)); var uploadedFiles = new List(); var sourceDirectory = new DirectoryInfo(sourcePath); var sourceFiles = FileSystemAbstraction.EnumerateFiles(sourceDirectory, searchPattern).ToList(); if (sourceFiles.Count == 0) return uploadedFiles; #region Existing Files at The Destination var destFiles = InternalListDirectory(destinationPath, null); var destDict = new Dictionary(); foreach (var destFile in destFiles) { if (destFile.IsDirectory) continue; destDict.Add(destFile.Name, destFile); } #endregion #region Upload the difference const Flags uploadFlag = Flags.Write | Flags.Truncate | Flags.CreateNewOrOpen; foreach (var localFile in sourceFiles) { var isDifferent = !destDict.ContainsKey(localFile.Name); if (!isDifferent) { var temp = destDict[localFile.Name]; // TODO: Use md5 to detect a difference //ltang: File exists at the destination => Using filesize to detect the difference isDifferent = localFile.Length != temp.Length; } if (isDifferent) { var remoteFileName = string.Format(CultureInfo.InvariantCulture, @"{0}/{1}", destinationPath, localFile.Name); try { using (var file = File.OpenRead(localFile.FullName)) { InternalUploadFile(file, remoteFileName, uploadFlag, null, null); } uploadedFiles.Add(localFile); if (asynchResult != null) { asynchResult.Update(uploadedFiles.Count); } } catch (Exception ex) { throw new Exception(string.Format("Failed to upload {0} to {1}", localFile.FullName, remoteFileName), ex); } } } #endregion return uploadedFiles; } #endregion /// /// Internals the list directory. /// /// The path. /// The list callback. /// /// A list of files in the specfied directory. /// /// is null. /// Client not connected. private IEnumerable InternalListDirectory(string path, Action listCallback) { if (path == null) throw new ArgumentNullException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); var handle = _sftpSession.RequestOpenDir(fullPath); var basePath = fullPath; if (!basePath.EndsWith("/")) basePath = string.Format("{0}/", fullPath); var result = new List(); var files = _sftpSession.RequestReadDir(handle); while (files != null) { result.AddRange(from f in files select new SftpFile(_sftpSession, string.Format(CultureInfo.InvariantCulture, "{0}{1}", basePath, f.Key), f.Value)); // Call callback to report number of files read if (listCallback != null) { // Execute callback on different thread ThreadAbstraction.ExecuteThread(() => listCallback(result.Count)); } files = _sftpSession.RequestReadDir(handle); } _sftpSession.RequestClose(handle); return result; } /// /// Internals the download file. /// /// The path. /// The output. /// An that references the asynchronous request. /// The download callback. /// is null. /// is null or contains whitespace. /// Client not connected. private void InternalDownloadFile(string path, Stream output, SftpDownloadAsyncResult asyncResult, Action downloadCallback) { if (output == null) throw new ArgumentNullException("output"); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); using (var fileReader = ServiceFactory.CreateSftpFileReader(fullPath, _sftpSession, _bufferSize)) { var totalBytesRead = 0UL; while (true) { // Cancel download if (asyncResult != null && asyncResult.IsDownloadCanceled) break; var data = fileReader.Read(); if (data.Length == 0) break; output.Write(data, 0, data.Length); totalBytesRead += (ulong) data.Length; if (downloadCallback != null) { // copy offset to ensure it's not modified between now and execution of callback var downloadOffset = totalBytesRead; // Execute callback on different thread ThreadAbstraction.ExecuteThread(() => { downloadCallback(downloadOffset); }); } } } } /// /// Internals the upload file. /// /// The input. /// The path. /// The flags. /// An that references the asynchronous request. /// The upload callback. /// is null. /// is null or contains whitespace. /// Client not connected. private void InternalUploadFile(Stream input, string path, Flags flags, SftpUploadAsyncResult asyncResult, Action uploadCallback) { if (input == null) throw new ArgumentNullException("input"); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (_sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = _sftpSession.GetCanonicalPath(path); var handle = _sftpSession.RequestOpen(fullPath, flags); ulong offset = 0; // create buffer of optimal length var buffer = new byte[_sftpSession.CalculateOptimalWriteLength(_bufferSize, handle)]; var bytesRead = input.Read(buffer, 0, buffer.Length); var expectedResponses = 0; var responseReceivedWaitHandle = new AutoResetEvent(false); do { // Cancel upload if (asyncResult != null && asyncResult.IsUploadCanceled) break; if (bytesRead > 0) { var writtenBytes = offset + (ulong) bytesRead; _sftpSession.RequestWrite(handle, offset, buffer, 0, bytesRead, null, s => { if (s.StatusCode == StatusCodes.Ok) { Interlocked.Decrement(ref expectedResponses); responseReceivedWaitHandle.Set(); // Call callback to report number of bytes written if (uploadCallback != null) { // Execute callback on different thread ThreadAbstraction.ExecuteThread(() => uploadCallback(writtenBytes)); } } }); Interlocked.Increment(ref expectedResponses); offset += (ulong) bytesRead; bytesRead = input.Read(buffer, 0, buffer.Length); } else if (expectedResponses > 0) { // Wait for expectedResponses to change _sftpSession.WaitOnHandle(responseReceivedWaitHandle, _operationTimeout); } } while (expectedResponses > 0 || bytesRead > 0); _sftpSession.RequestClose(handle); } /// /// Called when client is connected to the server. /// protected override void OnConnected() { base.OnConnected(); _sftpSession = ServiceFactory.CreateSftpSession(Session, _operationTimeout, ConnectionInfo.Encoding); _sftpSession.Connect(); } /// /// Called when client is disconnecting from the server. /// protected override void OnDisconnecting() { base.OnDisconnecting(); // disconnect, dispose and dereference the SFTP session since we create a new SFTP session // on each connect var sftpSession = _sftpSession; if (sftpSession != null) { _sftpSession = null; sftpSession.Dispose(); } } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { var sftpSession = _sftpSession; if (sftpSession != null) { _sftpSession = null; sftpSession.Dispose(); } } } } }