Skip to content

Added CreateDirectoryAsync to SftpClient #1505

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 3 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/Renci.SshNet/ISftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,19 @@ public interface ISftpClient : IBaseClient
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
void CreateDirectory(string path);

/// <summary>
/// Asynchronously requests to create a remote directory specified by path.
/// </summary>
/// <param name="path">Directory path to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous create directory operation.</returns>
/// <exception cref="ArgumentException"><paramref name="path"/> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to create the directory was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SshException">A SSH error where <see cref="Exception.Message"/> is the message from the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
Task CreateDirectoryAsync(string path, CancellationToken cancellationToken = default);

/// <summary>
/// Creates or opens a file for writing UTF-8 encoded text.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/Renci.SshNet/Sftp/ISftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ internal interface ISftpSession : ISubsystemSession
/// <param name="path">The path.</param>
void RequestMkDir(string path);

/// <summary>
/// Asynchronously performs SSH_FXP_MKDIR request.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous <c>SSH_FXP_MKDIR</c> operation.</returns>
Task RequestMkDirAsync(string path, CancellationToken cancellationToken = default);

/// <summary>
/// Performs a <c>SSH_FXP_OPEN</c> request.
/// </summary>
Expand Down
38 changes: 38 additions & 0 deletions src/Renci.SshNet/Sftp/SftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,44 @@ public void RequestMkDir(string path)
}
}

/// <summary>
/// Asynchronously performs SSH_FXP_MKDIR request.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous <c>SSH_FXP_MKDIR</c> operation.</returns>
public async Task RequestMkDirAsync(string path, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

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

#if NET || NETSTANDARD2_1_OR_GREATER
await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
#else
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
#endif // NET || NETSTANDARD2_1_OR_GREATER
{
SendRequest(new SftpMkDirRequest(ProtocolVersion,
NextRequestId,
path,
_encoding,
response =>
{
if (response.StatusCode == StatusCodes.Ok)
{
_ = tcs.TrySetResult(true);
}
else
{
tcs.TrySetException(GetSftpException(response));
}
}));

_ = await tcs.Task.ConfigureAwait(false);
}
}

/// <summary>
/// Performs SSH_FXP_RMDIR request.
/// </summary>
Expand Down
26 changes: 26 additions & 0 deletions src/Renci.SshNet/SftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,32 @@ public void CreateDirectory(string path)
_sftpSession.RequestMkDir(fullPath);
}

/// <summary>
/// Asynchronously requests to create a remote directory specified by path.
/// </summary>
/// <param name="path">Directory path to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous create directory operation.</returns>
/// <exception cref="ArgumentException"><paramref name="path"/> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to create the directory was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SshException">A SSH error where <see cref="Exception.Message"/> is the message from the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public async Task CreateDirectoryAsync(string path, CancellationToken cancellationToken = default)
{
CheckDisposed();
ThrowHelper.ThrowIfNullOrWhiteSpace(path);

if (_sftpSession is null)
{
throw new SshConnectionException("Client not connected.");
}

var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);

await _sftpSession.RequestMkDirAsync(fullPath, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Deletes remote directory specified by path.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,15 @@ public async Task Test_Sftp_Change_DirectoryAsync()

Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet");

sftp.CreateDirectory("test1");
await sftp.CreateDirectoryAsync("test1", CancellationToken.None).ConfigureAwait(false);

await sftp.ChangeDirectoryAsync("test1", CancellationToken.None).ConfigureAwait(false);

Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1");

sftp.CreateDirectory("test1_1");
sftp.CreateDirectory("test1_2");
sftp.CreateDirectory("test1_3");
await sftp.CreateDirectoryAsync("test1_1", CancellationToken.None).ConfigureAwait(false);
await sftp.CreateDirectoryAsync("test1_2", CancellationToken.None).ConfigureAwait(false);
await sftp.CreateDirectoryAsync("test1_3", CancellationToken.None).ConfigureAwait(false);

var files = sftp.ListDirectory(".");

Expand Down
2 changes: 1 addition & 1 deletion test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task Create_directory_with_contents_and_list_it_async()
var testContent = "file content";

// Create new directory and check if it exists
_sftpClient.CreateDirectory(testDirectory);
await _sftpClient.CreateDirectoryAsync(testDirectory, CancellationToken.None).ConfigureAwait(false);
Assert.IsTrue(await _sftpClient.ExistsAsync(testDirectory));

// Upload file and check if it exists
Expand Down