From c5a7f9c160a0415d012336a2b015e23b145f9bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sampo=20Kivist=C3=B6?= Date: Tue, 8 Oct 2024 16:01:36 +0300 Subject: [PATCH 1/3] Adds DownloadFileAsync, UploadFileAsync and SynchronizeDirectoriesAsync overloads --- src/Renci.SshNet/ISftpClient.cs | 55 +++++++++++++ src/Renci.SshNet/SftpClient.cs | 82 +++++++++++++++++++ .../OldIntegrationTests/SftpFileTest.cs | 22 ++++- .../SftpClientTests.cs | 6 +- .../SftpTests.cs | 2 +- 5 files changed, 162 insertions(+), 5 deletions(-) diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs index d3c4b3b6a..45145498e 100644 --- a/src/Renci.SshNet/ISftpClient.cs +++ b/src/Renci.SshNet/ISftpClient.cs @@ -573,6 +573,30 @@ public interface ISftpClient : IBaseClient /// void DownloadFile(string path, Stream output, Action? downloadCallback = null); + /// + /// Asynchronously downloads remote file specified by the path into the stream. + /// + /// File to download. + /// Stream to write the file into. + /// The download callback. + /// The TaskFactory used to create the Task. + /// The TaskCreationOptions value that controls the behavior of the + /// created Task. + /// The TaskScheduler + /// that is used to schedule the task that executes the end method. + /// is . + /// is 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. + /// + /// A representing the asynchronous operation. + Task DownloadFileAsync(string path, Stream output, Action? downloadCallback = null, TaskFactory? factory = null, TaskCreationOptions creationOptions = default, TaskScheduler? scheduler = null); + /// /// Ends an asynchronous file downloading into the stream. /// @@ -1038,6 +1062,22 @@ public interface ISftpClient : IBaseClient /// If a problem occurs while copying the file. IEnumerable SynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern); + /// + /// Asynchronously synchronizes the directories. + /// + /// The source path. + /// The destination path. + /// The search pattern. + /// The TaskFactory used to create the Task. + /// The TaskCreationOptions value that controls the behavior of the + /// created Task. + /// The TaskScheduler + /// that is used to schedule the task that executes the end method. + /// + /// A list of uploaded files. + /// + Task> SynchronizeDirectoriesAsync(string sourcePath, string destinationPath, string searchPattern, TaskFactory>? factory = null, TaskCreationOptions creationOptions = default, TaskScheduler? scheduler = null); + /// /// Uploads stream into remote file. /// @@ -1073,6 +1113,21 @@ public interface ISftpClient : IBaseClient /// void UploadFile(Stream input, string path, bool canOverride, Action? uploadCallback = null); + /// + /// Asynchronously upload the stream into the remote file. + /// + /// Data input stream. + /// Remote file path. + /// if set to then existing file will be overwritten. + /// The upload callback. + /// The TaskFactory used to create the Task. + /// The TaskCreationOptions value that controls the behavior of the + /// created Task. + /// The TaskScheduler + /// that is used to schedule the task that executes the end method. + /// A representing the asynchronous operation. + Task UploadFileAsync(Stream input, string path, bool canOverride = false, Action? uploadCallback = null, TaskFactory? factory = null, TaskCreationOptions creationOptions = default, TaskScheduler? scheduler = null); + /// /// 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 4a5d7dbe7..631729b81 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -916,6 +916,39 @@ public void DownloadFile(string path, Stream output, Action? downloadCall InternalDownloadFile(path, output, asyncResult: null, downloadCallback); } + /// + /// Asynchronously downloads remote file specified by the path into the stream. + /// + /// File to download. + /// Stream to write the file into. + /// The download callback. + /// The TaskFactory used to create the Task. + /// The TaskCreationOptions value that controls the behavior of the + /// created Task. + /// The TaskScheduler + /// that is used to schedule the task that executes the end method. + /// is . + /// is 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. + /// + /// A representing the asynchronous operation. + public Task DownloadFileAsync(string path, Stream output, Action? downloadCallback = null, TaskFactory? factory = null, TaskCreationOptions creationOptions = default, TaskScheduler? scheduler = null) + { + var taskFactory = factory ?? Task.Factory; + + return taskFactory.FromAsync( + BeginDownloadFile(path, output, asyncCallback: null, state: null, downloadCallback), + EndDownloadFile, + creationOptions, + scheduler ?? taskFactory.Scheduler ?? TaskScheduler.Current); + } + /// /// Begins an asynchronous file downloading into the stream. /// @@ -1077,6 +1110,30 @@ public void UploadFile(Stream input, string path, bool canOverride, Action + /// Asynchronously upload the stream into the remote file. + /// + /// Data input stream. + /// Remote file path. + /// if set to then existing file will be overwritten. + /// The upload callback. + /// The TaskFactory used to create the Task. + /// The TaskCreationOptions value that controls the behavior of the + /// created Task. + /// The TaskScheduler + /// that is used to schedule the task that executes the end method. + /// A representing the asynchronous operation. + public Task UploadFileAsync(Stream input, string path, bool canOverride = false, Action? uploadCallback = null, TaskFactory? factory = null, TaskCreationOptions creationOptions = default, TaskScheduler? scheduler = null) + { + var taskFactory = factory ?? Task.Factory; + + return taskFactory.FromAsync( + BeginUploadFile(input, path, canOverride, asyncCallback: null, state: null, uploadCallback), + EndUploadFile, + creationOptions, + scheduler ?? taskFactory.Scheduler ?? TaskScheduler.Current); + } + /// /// Begins an asynchronous uploading the stream into remote file. /// @@ -2167,6 +2224,31 @@ public IEnumerable SynchronizeDirectories(string sourcePath, string de return InternalSynchronizeDirectories(sourcePath, destinationPath, searchPattern, asynchResult: null); } + /// + /// Asynchronously synchronizes the directories. + /// + /// The source path. + /// The destination path. + /// The search pattern. + /// The TaskFactory used to create the Task. + /// The TaskCreationOptions value that controls the behavior of the + /// created Task. + /// The TaskScheduler + /// that is used to schedule the task that executes the end method. + /// + /// A list of uploaded files. + /// + public Task> SynchronizeDirectoriesAsync(string sourcePath, string destinationPath, string searchPattern, TaskFactory>? factory = null, TaskCreationOptions creationOptions = default, TaskScheduler? scheduler = null) + { + var taskFactory = factory ?? Task>.Factory; + + return taskFactory.FromAsync( + BeginSynchronizeDirectories(sourcePath, destinationPath, searchPattern, asyncCallback: null, state: null), + EndSynchronizeDirectories, + creationOptions, + scheduler ?? taskFactory?.Scheduler ?? TaskScheduler.Current); + } + /// /// Begins the synchronize directories. /// diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs index ec9fb8c76..bc817b792 100644 --- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs +++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs @@ -123,7 +123,27 @@ public async Task Test_Get_FileAsync() { sftp.Connect(); +#pragma warning disable S6966 // Tests sync version sftp.UploadFile(new MemoryStream(), "abc.txt"); +#pragma warning restore S6966 // Test sync version + + var file = await sftp.GetAsync("abc.txt", default).ConfigureAwait(false); + + Assert.AreEqual("/home/sshnet/abc.txt", file.FullName); + Assert.IsTrue(file.IsRegularFile); + Assert.IsFalse(file.IsDirectory); + } + } + + [TestMethod] + [TestCategory("Sftp")] + public async Task Test_GetAsync_UploadFileAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + sftp.Connect(); + + await sftp.UploadFileAsync(new MemoryStream(), "abc.txt"); var file = await sftp.GetAsync("abc.txt", default).ConfigureAwait(false); @@ -157,7 +177,7 @@ public async Task Test_Get_International_FileAsync() { sftp.Connect(); - sftp.UploadFile(new MemoryStream(), "test-üöä-"); + await sftp.UploadFileAsync(new MemoryStream(), "test-üöä-"); var file = await sftp.GetAsync("test-üöä-", default).ConfigureAwait(false); diff --git a/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs b/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs index 951fdf666..385f08061 100644 --- a/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs +++ b/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs @@ -65,7 +65,7 @@ public async Task Create_directory_with_contents_and_list_it_async() // Upload file and check if it exists using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent)); - _sftpClient.UploadFile(fileStream, testFilePath); + await _sftpClient.UploadFileAsync(fileStream, testFilePath); Assert.IsTrue(await _sftpClient.ExistsAsync(testFilePath)); // Check if ListDirectory works @@ -124,7 +124,7 @@ public async Task Create_directory_with_contents_and_delete_contents_then_direct // Upload file and check if it exists using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent)); - _sftpClient.UploadFile(fileStream, testFilePath); + await _sftpClient.UploadFileAsync(fileStream, testFilePath); Assert.IsTrue(await _sftpClient.ExistsAsync(testFilePath).ConfigureAwait(false)); await _sftpClient.DeleteFileAsync(testFilePath, CancellationToken.None).ConfigureAwait(false); @@ -159,7 +159,7 @@ public async Task Create_file_and_delete_using_DeleteAsync() // Upload file and check if it exists using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent)); - _sftpClient.UploadFile(fileStream, testFileName); + await _sftpClient.UploadFileAsync(fileStream, testFileName); Assert.IsTrue(await _sftpClient.ExistsAsync(testFileName).ConfigureAwait(false)); await _sftpClient.DeleteAsync(testFileName, CancellationToken.None).ConfigureAwait(false); diff --git a/test/Renci.SshNet.IntegrationTests/SftpTests.cs b/test/Renci.SshNet.IntegrationTests/SftpTests.cs index f03ee6a2c..998849727 100644 --- a/test/Renci.SshNet.IntegrationTests/SftpTests.cs +++ b/test/Renci.SshNet.IntegrationTests/SftpTests.cs @@ -4115,7 +4115,7 @@ public async Task Sftp_ChangeDirectory_DirectoryExistsAsync() { uploadStream.Position = 0; - client.UploadFile(uploadStream, "gert.txt"); + await client.UploadFileAsync(uploadStream, "gert.txt"); uploadStream.Position = 0; From b9002ac8af5a75b437587aca6e751a4e0b1a490f Mon Sep 17 00:00:00 2001 From: Havunen Date: Tue, 8 Oct 2024 21:33:55 +0300 Subject: [PATCH 2/3] fixed unit tests --- .../OldIntegrationTests/SftpFileTest.cs | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs index bc817b792..0437a67af 100644 --- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs +++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs @@ -123,27 +123,9 @@ public async Task Test_Get_FileAsync() { sftp.Connect(); -#pragma warning disable S6966 // Tests sync version +#pragma warning disable S6966 sftp.UploadFile(new MemoryStream(), "abc.txt"); -#pragma warning restore S6966 // Test sync version - - var file = await sftp.GetAsync("abc.txt", default).ConfigureAwait(false); - - Assert.AreEqual("/home/sshnet/abc.txt", file.FullName); - Assert.IsTrue(file.IsRegularFile); - Assert.IsFalse(file.IsDirectory); - } - } - - [TestMethod] - [TestCategory("Sftp")] - public async Task Test_GetAsync_UploadFileAsync() - { - using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) - { - sftp.Connect(); - - await sftp.UploadFileAsync(new MemoryStream(), "abc.txt"); +#pragma warning restore S6966 var file = await sftp.GetAsync("abc.txt", default).ConfigureAwait(false); @@ -177,7 +159,9 @@ public async Task Test_Get_International_FileAsync() { sftp.Connect(); - await sftp.UploadFileAsync(new MemoryStream(), "test-üöä-"); +#pragma warning disable S6966 + sftp.UploadFile(new MemoryStream(), "test-üöä-"); +#pragma warning restore S6966 var file = await sftp.GetAsync("test-üöä-", default).ConfigureAwait(false); From 06c25f50e993d01dcbae804277e674788ab9292f Mon Sep 17 00:00:00 2001 From: Havunen Date: Sat, 19 Oct 2024 13:07:39 +0300 Subject: [PATCH 3/3] revert the change to intergration test --- test/Renci.SshNet.IntegrationTests/SftpTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Renci.SshNet.IntegrationTests/SftpTests.cs b/test/Renci.SshNet.IntegrationTests/SftpTests.cs index 998849727..955cb7589 100644 --- a/test/Renci.SshNet.IntegrationTests/SftpTests.cs +++ b/test/Renci.SshNet.IntegrationTests/SftpTests.cs @@ -4114,9 +4114,9 @@ public async Task Sftp_ChangeDirectory_DirectoryExistsAsync() using (var uploadStream = CreateMemoryStream(100)) { uploadStream.Position = 0; - - await client.UploadFileAsync(uploadStream, "gert.txt"); - +#pragma warning disable S6966 + client.UploadFile(uploadStream, "gert.txt"); +#pragma warning restore S6966 uploadStream.Position = 0; using (var downloadStream = client.OpenRead(remoteDirectory + "/gert.txt"))