-
-
Notifications
You must be signed in to change notification settings - Fork 946
Add UploadFileAsync and DownloadFileAsync methods #1634
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
base: develop
Are you sure you want to change the base?
Add UploadFileAsync and DownloadFileAsync methods #1634
Conversation
actions don't seem to be triggering, gonna try closing and reopening |
Hi, I will make time to review most likely at some point next week. FYI for first time contributors the CI needs to be run manually (some kind of github security measure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. I think it could do with testing cancellation - i.e. that it throws OperationCanceledException
when the token is cancelled.
It would probably not be very reliable to test cancelling during a file download (at least not in an integration test), but we could at least have a test for when a cancelled token is supplied
@@ -191,7 +191,7 @@ public TimeSpan Timeout | |||
} | |||
} | |||
|
|||
private SftpFileStream(ISftpSession session, string path, FileAccess access, int bufferSize, byte[] handle, long position) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use SftpFileStream.OpenAsync
(line 331) instead of this constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm sticking with using the ctor directly instead of using the OpenAsync
method mainly because calculating the optimal write length needs the direct file handle, which is used for the copy buffer size
i might be misusing the intended use case for the CalculateOptimalWriteLength
method, and if so i can just rely of the default buffer size of 81920
and swap out the call to OpenAsync
if need be
test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.cs
Outdated
Show resolved
Hide resolved
test/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs
Outdated
Show resolved
Hide resolved
var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false); | ||
var handle = await _sftpSession.RequestOpenAsync(fullPath, Flags.Write | flags, cancellationToken).ConfigureAwait(false); | ||
|
||
using (var output = new SftpFileStream(_sftpSession, fullPath, FileAccess.Write, (int)_bufferSize, handle, 0L)) | ||
{ | ||
var bufferSize = (int)_sftpSession.CalculateOptimalWriteLength(_bufferSize, handle); | ||
await input.CopyToAsync(output, bufferSize, cancellationToken).ConfigureAwait(false); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(moving the conversation here)
i might be misusing the intended use case for the CalculateOptimalWriteLength method, and if so i can just rely of the default buffer size of 81920 and swap out the call to OpenAsync if need be
I think I would prefer that. I have doubts that CalculateOptimalWriteLength
lives up to its name, so if we can reuse existing code and simplify this bit then that sounds good.
It does raise an interesting point that here you are canonicalizing the path while SftpFileStream
currently does not. I've just spent far too long looking at it but I think it would be fine to add to SftpFileStream
. I think it should make this (failing) test pass:
diff --git a/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs b/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
index 67b3b28c..e90cf704 100644
--- a/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/SftpClientTests.cs
@@ -162,6 +162,46 @@ public async Task Create_directory_and_delete_it_using_DeleteAsync()
Assert.IsFalse(await _sftpClient.ExistsAsync(testDirectory).ConfigureAwait(false));
}
+ [TestMethod]
+ public async Task Open_PathCanonicalized()
+ {
+ var testDirectory = "/home/sshnet/sshnet-test";
+
+ // Create new directory and check if it exists
+ await _sftpClient.CreateDirectoryAsync(testDirectory).ConfigureAwait(false);
+
+ await _sftpClient.ChangeDirectoryAsync(testDirectory).ConfigureAwait(false);
+
+ Assert.AreEqual(testDirectory, _sftpClient.WorkingDirectory);
+
+ Assert.IsFalse(await _sftpClient.ExistsAsync("/home/sshnet/tempasync.txt").ConfigureAwait(false));
+ Assert.IsFalse(await _sftpClient.ExistsAsync("/home/sshnet/tempsync.txt").ConfigureAwait(false));
+
+ using (var stream = await _sftpClient.OpenAsync("../tempasync.txt", FileMode.Create, FileAccess.ReadWrite, default).ConfigureAwait(false))
+ {
+ await stream.WriteAsync(Encoding.ASCII.GetBytes("test"), 0, 4).ConfigureAwait(false);
+ }
+
+ using (var stream = _sftpClient.Open("../tempsync.txt", FileMode.Create, FileAccess.ReadWrite))
+ {
+ await stream.WriteAsync(Encoding.ASCII.GetBytes("test"), 0, 4).ConfigureAwait(false);
+ }
+
+ Assert.IsTrue(await _sftpClient.ExistsAsync("/home/sshnet/tempasync.txt").ConfigureAwait(false));
+ Assert.IsTrue(await _sftpClient.ExistsAsync("/home/sshnet/tempsync.txt").ConfigureAwait(false));
+
+ await _sftpClient.DeleteAsync("../tempasync.txt", CancellationToken.None).ConfigureAwait(false);
+ await _sftpClient.DeleteAsync("../tempsync.txt", CancellationToken.None).ConfigureAwait(false);
+
+ await _sftpClient.ChangeDirectoryAsync("../").ConfigureAwait(false);
+
+ await _sftpClient.DeleteAsync("sshnet-test", CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsFalse(await _sftpClient.ExistsAsync(testDirectory).ConfigureAwait(false));
+ Assert.IsFalse(await _sftpClient.ExistsAsync("/home/sshnet/tempasync.txt").ConfigureAwait(false));
+ Assert.IsFalse(await _sftpClient.ExistsAsync("/home/sshnet/tempsync.txt").ConfigureAwait(false));
+ }
+
[TestMethod]
public async Task Create_file_and_delete_using_DeleteAsync()
{
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
canonicalizing the path in SftpFileStream
will probably cause failures in some painfully strict unit tests, in which case it can be left for later
Adds TAP methods for
UploadFile
andDownloadFile
toISftpClient
.Other Solutions
While there are other options to define both the
UploadFileAsync
andDownloadFileAsync
methods, such as the extension methods approach and the previously proposed pull request, #1515 , this pull request has the following benefits over the other solutions:UploadFile
andDownloadFile
methodsBeginUploadFile
, which pretty much runs the sync UploadFile method, but just in a different thread