-
-
Notifications
You must be signed in to change notification settings - Fork 969
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
Conversation
| { | ||
| throw; | ||
| } | ||
| catch (Exception) |
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.
Should the generic class Exception be caught here? Or something more specific?
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.
Possibly, but I only reused the logic of sync versions of the functions at this time. When/if this is released, practice will show if any changes need to be made.
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.
When FileMode is Create, I think we should just use Flags.CreateNewOrOpen | Flags.Truncate.
Let me run some checks later.
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 can confirm that it's ok to just add Flags.CreateNewOrOpen | Flags.Truncate when mode is FileMode.Create.
| if (count < 0) | ||
| throw new ArgumentOutOfRangeException("count"); | ||
| if ((buffer.Length - offset) < count) | ||
| throw new ArgumentException("Invalid array range."); |
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.
Specify second parameter to ArgumentException, nameof(buffer).
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 not sure about that, any of the three could be wrong, depending on the caller's intention. Anyhow, this is the same as the sync version, so we either need to change both or none...
| /// <exception cref="SftpPermissionDeniedException">Permission to list the contents of 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<IEnumerable<SftpFile>> ListDirectoryAsync(string path, CancellationToken cancellationToken) |
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.
Consider returning ICollection or IReadOnlyCollection rather than IEnumerable as per the Microsoft Guidelines for Collections.
DO use Collection or a subclass of Collection for properties or return values representing read/write collections.
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.
This should be IAsyncEnumerable<SftpFile>. It's only supported on .NET 4.6.1 or higher.
For .NET 4.6.1 and .NET Standard 2.0, you need to add a reference to Microsoft.Bcl.AsyncInterfaces.
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.
Then we need to remove FEATURE_TAP from netstandard1.3. Also, we take two "external" dependencies (AsyncInterfaces and Task.Extensions).
What if we leave this one as it is and add IAsyncEnumerable<SftpFile> EnumerateDirectoryAsync(...) for netstandard2.1+ ?
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 won't let this block this merge request, but I still strongly consider changing this method to return IAsyncEnumerable<SftpFile>. I also don't like bringing in additional dependencies, but the alternative is not better.
|
Any chance this could also target .net5 or .net6 as well |
|
The library already targets netstandard2.0, so you can reference it from net5 or net6 projects. |
|
I did not connect those dots. Thank you for clarifying 👍 |
|
This addition looks like it has a great deal of value. Is there a timetable or estimate for when the PR will be completed? |
|
This PR if functionally complete IMO. It should also be stable, I have been running it in production since July. Now we need @drieseng to review and merge it. |
|
@IgorMilavec Thanks, I'll try to find time in the coming days. |
|
Figured this is the last chance to modify UploadFileAsync signature before this becomes a breaking change... :) |
|
I believe I have addressed all the trivial items. @drieseng can you please re-review the code and my comments? |
src/Renci.SshNet/Renci.SshNet.csproj
Outdated
| <LangVersion>5</LangVersion> | ||
| <SignAssembly>true</SignAssembly> | ||
| <TargetFrameworks>net35;net40;netstandard1.3;netstandard2.0</TargetFrameworks> | ||
| <TargetFrameworks>net35;net40;net46;netstandard1.3;netstandard2.0</TargetFrameworks> |
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.
@IgorMilavec, is it ok for you to target .NET Framework 4.7.2 (or 4.8) instead?
| { | ||
| throw; | ||
| } | ||
| catch (Exception) |
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 can confirm that it's ok to just add Flags.CreateNewOrOpen | Flags.Truncate when mode is FileMode.Create.
|
@IgorMilavec Thanks for hanging on. I'll do my best to finish the review some time this week. |
|
We're still lacking (unit & integration) test coverage, but I'm ok with adding these as a follow PR. That said: Thx !!!!!!!! |
|
Working on a large project at work and just ran into this being a problem, that's awesome that hours before I (realized I) needed it, there was already a PR merged I'll be pulling this down and trying it tomorrow, nice work, and thanks |
|
Thanks @drieseng ! |
|
@IgorMilavec Thanks for taking this up. I'm ok with having a second method for ISftpClient.ListDirectoryAsync(...) as POC. |
|
When we review this PR, it looks like these methods are not actually implemented:
Are we just mis-understanding the PR itself? |
|
These functions were initially implemented as part of this PR, but during the code review we decided that these functions are not really necessary, as they can be easily implemented in the consuming code with a couple of lines of code. If you have no special requirements, you can use this extension class to get these two functions: namespace Renci.SshNet
{
public static class SftpClientExtensions
{
public static async Task DownloadFileAsync(this SftpClient sftpClient, string path, Stream output, CancellationToken cancellationToken)
{
using (Stream remoteStream = await sftpClient.OpenAsync(path, FileMode.Open, FileAccess.Read, cancellationToken).ConfigureAwait(false))
{
await remoteStream.CopyToAsync(output, 81920, cancellationToken).ConfigureAwait(false);
}
}
public static async Task UploadFileAsync(this SftpClient sftpClient, Stream input, string path, FileMode createMode, CancellationToken cancellationToken)
{
using (Stream remoteStream = await sftpClient.OpenAsync(path, createMode, FileAccess.Write, cancellationToken).ConfigureAwait(false))
{
await input.CopyToAsync(remoteStream, 81920, cancellationToken).ConfigureAwait(false);
}
}
}
} |
|
@IgorMilavec Thank you so much for your quick and thoughtful reply. We will take a look at this asap. |
|
Are these methods in official nuget? Not seeing any async methods on the sftpclient... |
|
I think the To be honest, I would like to see the non-async methods get removed as well as the legacy Begin* and End* async functions. |
|
Version 2023.0.0 has been published https://www.nuget.org/packages/SSH.NET/2023.0.0 |
|
Something that just bit me is that the OpenAsync method expects an absolute path while the Begin/End async methods for Upload/Download operate relative to the working directory. Not sure what the best way to handle this would be, but it might be less likely to make a mistake if there were replacement methods on the SftpClient (DownloadAsync/UploadAsync) that behaved in the same way as the Begin/End methods (Relative to working directory), while OpenAsync remains absolute as to not break existing code. |
namespace Renci.SshNet
{
public static class SftpClientExtensions
{
public static async Task DownloadFileAsync(this SftpClient sftpClient, string path, Stream output, CancellationToken cancellationToken)
{
using (Stream remoteStream = await sftpClient.OpenAsync(path, FileMode.Open, FileAccess.Read, cancellationToken).ConfigureAwait(false))
{
await remoteStream.CopyToAsync(output, 81920, cancellationToken).ConfigureAwait(false);
}
}
public static async Task UploadFileAsync(this SftpClient sftpClient, Stream input, string path, FileMode createMode, CancellationToken cancellationToken)
{
using (Stream remoteStream = await sftpClient.OpenAsync(path, createMode, FileAccess.Write, cancellationToken).ConfigureAwait(false))
{
await input.CopyToAsync(remoteStream, 81920, cancellationToken).ConfigureAwait(false);
}
}
}
}@IgorMilavec the issue here is that we cannot moq this code since we can't create a SftpFileStream (as it's not an interface and it's private). Whereas we can moq UploadFile (void return) unless OpenAsync returned a generic Stream instead of a wrapped Stream |
Core async support requested by #153.
Added async support to BaseClient: (update 2021-05-11)
Added async support to SftpClient:
Added async support to SftpFileStream:
Added net46 target to use async in .NET Framework.
This PR is intended to resolve #153.
This PR is intended to supersede #300 and #661.