Skip to content

Add async support to SftpClient and SftpFileStream #819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 14, 2021
Merged
4 changes: 2 additions & 2 deletions src/Renci.SshNet/ISftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ public interface ISftpClient
/// </summary>
/// <param name="input">Data input stream.</param>
/// <param name="path">Remote file path.</param>
/// <param name="canOverride">if set to <c>true</c> then existing file will be overwritten.</param>
/// <param name="createMode">Specifies how the file should be created.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous upload operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="input" /> is <b>null</b>.</exception>
Expand All @@ -1042,7 +1042,7 @@ public interface ISftpClient
/// <remarks>
/// Method calls made by this method to <paramref name="input" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
Task UploadFileAsync(Stream input, string path, bool canOverride, CancellationToken cancellationToken);
Task UploadFileAsync(Stream input, string path, UploadMode createMode, CancellationToken cancellationToken);
#endif

/// <summary>
Expand Down
4 changes: 4 additions & 0 deletions src/Renci.SshNet/Sftp/ISftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ internal interface ISftpSession : ISubsystemSession
/// </returns>
SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError);

#if FEATURE_TAP
Task<SftpFileAttributes> RequestFStatAsync(byte[] handle, CancellationToken cancellationToken);
#endif

/// <summary>
/// Performs SSH_FXP_STAT request.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Renci.SshNet/Sftp/SftpFileStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -382,12 +382,12 @@ internal static async Task<SftpFileStream> OpenAsync(ISftpSession session, strin
{
try
{
var attributes = session.RequestFStat(handle, false); // ToDo: Async
var attributes = await session.RequestFStatAsync(handle, cancellationToken).ConfigureAwait(false);
position = attributes.Size;
}
catch
{
session.RequestClose(handle); // ToDo: Async
session.RequestClose(handle);
throw;
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/Renci.SshNet/Sftp/SftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,24 @@ public SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError)
return attributes;
}

#if FEATURE_TAP
public async Task<SftpFileAttributes> RequestFStatAsync(byte[] handle, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

TaskCompletionSource<SftpFileAttributes> tcs = new TaskCompletionSource<SftpFileAttributes>(TaskCreationOptions.RunContinuationsAsynchronously);

using (cancellationToken.Register((s) => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(), tcs, false))
{
SendRequest(new SftpFStatRequest(ProtocolVersion, NextRequestId, handle,
response => tcs.TrySetResult(response.Attributes),
response => tcs.TrySetException(GetSftpException(response))));

return await tcs.Task.ConfigureAwait(false);
}
}
#endif

/// <summary>
/// Performs SSH_FXP_SETSTAT request.
/// </summary>
Expand Down
23 changes: 23 additions & 0 deletions src/Renci.SshNet/Sftp/UploadMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Renci.SshNet.Sftp
{
/// <summary>
/// Specifies how the uploaded file should be created.
/// </summary>
public enum UploadMode
{
/// <summary>
/// A new file should be created. If the file already exists, an exception is thrown.
/// </summary>
CreateNew = 1,

/// <summary>
/// A new file should be created. If the file already exists, it will be overwritten.
/// </summary>
Overwrite = 2,

/// <summary>
/// Opens the file if it exists and seeks to the end of the file, or creates a new file.
/// </summary>
Append = 6
}
}
6 changes: 3 additions & 3 deletions src/Renci.SshNet/SftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ public void UploadFile(Stream input, string path, bool canOverride, Action<ulong
/// </summary>
/// <param name="input">Data input stream.</param>
/// <param name="path">Remote file path.</param>
/// <param name="canOverride">if set to <c>true</c> then existing file will be overwritten.</param>
/// <param name="createMode">Specifies how the file should be created.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous upload operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="input" /> is <b>null</b> -or- <paramref name="path" /> is <b>null</b>.</exception>
Expand All @@ -975,7 +975,7 @@ public void UploadFile(Stream input, string path, bool canOverride, Action<ulong
/// <remarks>
/// Method calls made by this method to <paramref name="input" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
public async Task UploadFileAsync(Stream input, string path, bool canOverride, CancellationToken cancellationToken)
public async Task UploadFileAsync(Stream input, string path, UploadMode createMode, CancellationToken cancellationToken)
{
if (input == null)
throw new ArgumentNullException("input");
Expand All @@ -985,7 +985,7 @@ public async Task UploadFileAsync(Stream input, string path, bool canOverride, C
CheckDisposedOrNotConnected();
cancellationToken.ThrowIfCancellationRequested();

using (SftpFileStream output = await SftpFileStream.OpenAsync(_sftpSession, path, canOverride ? FileMode.Create : FileMode.CreateNew, FileAccess.Write, (int)_bufferSize, cancellationToken).ConfigureAwait(false))
using (SftpFileStream output = await SftpFileStream.OpenAsync(_sftpSession, path, (FileMode)createMode, FileAccess.Write, (int)_bufferSize, cancellationToken).ConfigureAwait(false))
{
await input.CopyToAsync(output, (int)_sftpSession.CalculateOptimalWriteLength(_bufferSize, output.Handle), cancellationToken).ConfigureAwait(false);
}
Expand Down