-
-
Notifications
You must be signed in to change notification settings - Fork 952
Add IAsyncEnumerable<SftpFile> to enumerate remote files #907
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -705,7 +705,6 @@ public interface ISftpClient : IDisposable | |
IEnumerable<ISftpFile> ListDirectory(string path, Action<int> listCallback = null); | ||
|
||
#if FEATURE_TAP | ||
|
||
/// <summary> | ||
/// Asynchronously retrieves list of files in remote directory. | ||
/// </summary> | ||
|
@@ -720,7 +719,29 @@ public interface ISftpClient : IDisposable | |
/// <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> | ||
#if NETSTANDARD2_1_OR_GREATER | ||
[Obsolete("Use EnumerateDirectoryAsync()")] | ||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] | ||
#endif | ||
Task<IEnumerable<SftpFile>> ListDirectoryAsync(string path, CancellationToken cancellationToken); | ||
|
||
#if NETSTANDARD2_1_OR_GREATER | ||
/// <summary> | ||
/// Asynchronously enumerates the files in remote directory. | ||
/// </summary> | ||
/// <param name="path">The path.</param> | ||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param> | ||
/// <returns> | ||
/// An <see cref="IAsyncEnumerable{SftpFile}"/> that represents the asynchronous enumeration operation. | ||
/// The enumeration contains an async stream of <see cref="SftpFile"/> for the files in the directory specified by <paramref name="path" />. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <b>null</b>.</exception> | ||
/// <exception cref="SshConnectionException">Client is not connected.</exception> | ||
/// <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> | ||
IAsyncEnumerable<SftpFile> EnumerateDirectoryAsync(string path, CancellationToken cancellationToken); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should return ISftpFile instead of the concrete SftpFile. |
||
#endif | ||
#endif | ||
|
||
/// <summary> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,17 +5,15 @@ | |
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> | ||
<AssemblyName>Renci.SshNet</AssemblyName> | ||
<AssemblyOriginatorKeyFile>../Renci.SshNet.snk</AssemblyOriginatorKeyFile> | ||
<LangVersion>6</LangVersion> | ||
<LangVersion>8.0</LangVersion> | ||
<SignAssembly>true</SignAssembly> | ||
<TargetFrameworks>net35;net40;net472;netstandard1.3;netstandard2.0</TargetFrameworks> | ||
<TargetFrameworks>net35;net40;net472;netstandard1.3;netstandard2.1</TargetFrameworks> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to keep these (and other) changes to have a .NET Standard 2.1 flavor in this PR, or separate them out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO it would be more clean if we first remove old targets and move to .NET Standard 2.0. We should probably also add .NET Standard 2.1 target in that PR, even without using 2.1 features. Later we can implement new 2.1 features in separate PRs (like this one) one feature at a time. |
||
</PropertyGroup> | ||
|
||
<!-- | ||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '16.0' "> | ||
<TargetFrameworks>net35;net40;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks> | ||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '15.0' "> | ||
<TargetFrameworks>net35;net40;net472;netstandard1.3;netstandard2.0</TargetFrameworks> | ||
</PropertyGroup> | ||
--> | ||
|
||
|
||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> | ||
<PackageReference Include="SshNet.Security.Cryptography" Version="[1.3.0]" /> | ||
<PackageReference Include="System.Diagnostics.TraceSource" Version="4.3.0" /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
using Renci.SshNet.Common; | ||
using Renci.SshNet.Sftp; | ||
#if FEATURE_TAP | ||
using System.Runtime.CompilerServices; | ||
using System.Threading.Tasks; | ||
#endif | ||
|
||
|
@@ -537,7 +538,6 @@ public IEnumerable<ISftpFile> ListDirectory(string path, Action<int> listCallbac | |
} | ||
|
||
#if FEATURE_TAP | ||
|
||
/// <summary> | ||
/// Asynchronously retrieves list of files in remote directory. | ||
/// </summary> | ||
|
@@ -552,11 +552,15 @@ public IEnumerable<ISftpFile> ListDirectory(string path, Action<int> listCallbac | |
/// <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> | ||
#if NETSTANDARD2_1_OR_GREATER | ||
[Obsolete("Use EnumerateDirectoryAsync()")] | ||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] | ||
#endif | ||
public async Task<IEnumerable<SftpFile>> ListDirectoryAsync(string path, CancellationToken cancellationToken) | ||
{ | ||
base.CheckDisposed(); | ||
if (path == null) | ||
throw new ArgumentNullException("path"); | ||
throw new ArgumentNullException(nameof(path)); | ||
if (_sftpSession == null) | ||
throw new SshConnectionException("Client not connected."); | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
@@ -594,6 +598,59 @@ public async Task<IEnumerable<SftpFile>> ListDirectoryAsync(string path, Cancell | |
return result; | ||
} | ||
|
||
#if NETSTANDARD2_1_OR_GREATER | ||
/// <summary> | ||
/// Asynchronously enumerates the files in remote directory. | ||
/// </summary> | ||
/// <param name="path">The path.</param> | ||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param> | ||
/// <returns> | ||
/// An <see cref="IAsyncEnumerable{SftpFile}"/> that represents the asynchronous enumeration operation. | ||
/// The enumeration contains an async stream of <see cref="SftpFile"/> for the files in the directory specified by <paramref name="path" />. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <b>null</b>.</exception> | ||
/// <exception cref="SshConnectionException">Client is not connected.</exception> | ||
/// <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 IAsyncEnumerable<SftpFile> EnumerateDirectoryAsync(string path, [EnumeratorCancellation]CancellationToken cancellationToken) | ||
{ | ||
base.CheckDisposed(); | ||
if (path == null) | ||
throw new ArgumentNullException(nameof(path)); | ||
if (_sftpSession == null) | ||
throw new SshConnectionException("Client not connected."); | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose we perform this check in all async methods that we invoke here. Do we want this check here as well? |
||
|
||
var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false); | ||
|
||
var handle = await _sftpSession.RequestOpenDirAsync(fullPath, cancellationToken).ConfigureAwait(false); | ||
try | ||
{ | ||
var basePath = (fullPath[fullPath.Length - 1] == '/') ? | ||
fullPath : | ||
fullPath + '/'; | ||
|
||
while (true) | ||
{ | ||
var files = await _sftpSession.RequestReadDirAsync(handle, cancellationToken).ConfigureAwait(false); | ||
if (files == null) | ||
{ | ||
break; | ||
} | ||
|
||
foreach (var file in files) | ||
{ | ||
yield return new SftpFile(_sftpSession, basePath + file.Key, file.Value); | ||
} | ||
} | ||
} | ||
finally | ||
{ | ||
await _sftpSession.RequestCloseAsync(handle, cancellationToken).ConfigureAwait(false); | ||
} | ||
} | ||
#endif | ||
#endif | ||
|
||
/// <summary> | ||
|
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'd prefer to only expose the IAsyncEnumerable version (with ListDirectoryAsync as name), even if that means it's .NET Standard 2.1 (or higher) only.
Is the NETSTANDARD2_1_OR_GREATER symbol also defined for .NET 5.0 or 6.0?
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.
If we reuse the same name with a different signature, this means a breaking change when the caller changes its target framework. If we do a "soft" transition with Obsoletes and a new name then we give callers time to transition to the new method at their own pace. But we do end up with a new name...
The symbol NETSTANDARD* is unfortunately not defined when targeting net5.0 or net6.0, so this check would need to be changed to
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
.