diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs index af6d31b94..759d4ecbe 100644 --- a/src/Renci.SshNet/ISftpClient.cs +++ b/src/Renci.SshNet/ISftpClient.cs @@ -1133,6 +1133,22 @@ public interface ISftpClient : IBaseClient /// The method was called after the client was disposed. Task UploadFileAsync(Stream input, string path, CancellationToken cancellationToken = default); + /// + /// Asynchronously uploads a to a remote file path. + /// + /// The to write to the remote path. + /// The remote file path to write to. + /// Whether the remote file can be overwritten if it already exists. + /// The to observe. + /// A that represents the asynchronous upload operation. + /// or is . + /// is empty or contains only whitespace characters. + /// Client is not connected. + /// Permission to upload the file was denied by the remote host. -or- An SSH command was denied by the server. + /// An SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + Task UploadFileAsync(Stream input, string path, bool canOverride, CancellationToken cancellationToken = default); + /// /// Writes the specified byte array to the specified file, and closes the file. /// diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index 69e739b29..afd047108 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -1077,15 +1077,32 @@ public void UploadFile(Stream input, string path, bool canOverride, Action public Task UploadFileAsync(Stream input, string path, CancellationToken cancellationToken = default) + { + return UploadFileAsync(input, path, canOverride: true, cancellationToken); + } + + /// + public Task UploadFileAsync(Stream input, string path, bool canOverride, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(input); ArgumentException.ThrowIfNullOrWhiteSpace(path); CheckDisposed(); + var flags = Flags.Write | Flags.Truncate; + + if (canOverride) + { + flags |= Flags.CreateNewOrOpen; + } + else + { + flags |= Flags.CreateNew; + } + return InternalUploadFile( input, path, - Flags.Write | Flags.Truncate | Flags.CreateNewOrOpen, + flags, asyncResult: null, uploadCallback: null, isAsync: true, diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs index e141f19d1..99af3a370 100644 --- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs +++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs @@ -74,6 +74,18 @@ public async Task Test_Sftp_Upload_And_Download_Async_1MB_File() await sftp.UploadFileAsync(file, remoteFileName).ConfigureAwait(false); } + // uploading again should not throw because of the default canOverride = true + using (var file = File.OpenRead(uploadedFileName)) + { + await sftp.UploadFileAsync(file, remoteFileName).ConfigureAwait(false); + } + + // uploading with canOverride = false should throw because the file already exists + using (var file = File.OpenRead(uploadedFileName)) + { + await Assert.ThrowsAsync(async () => await sftp.UploadFileAsync(file, remoteFileName, canOverride: false).ConfigureAwait(false)); + } + var downloadedFileName = Path.GetTempFileName(); using (var file = File.OpenWrite(downloadedFileName))