Skip to content

Commit 54026bb

Browse files
committed
SftpClient: add DownloadFileAsync overload with downloadCallback
fixes #1765
1 parent 4a6f7fd commit 54026bb

3 files changed

Lines changed: 53 additions & 1 deletion

File tree

src/Renci.SshNet/ISftpClient.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,23 @@ public interface ISftpClient : IBaseClient
578578
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
579579
Task DownloadFileAsync(string path, Stream output, CancellationToken cancellationToken = default);
580580

581+
/// <summary>
582+
/// Asynchronously downloads a remote file into a <see cref="Stream"/>.
583+
/// </summary>
584+
/// <param name="path">The path to the remote file.</param>
585+
/// <param name="output">The <see cref="Stream"/> to write the file into.</param>
586+
/// <param name="downloadCallback">The download callback.</param>
587+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
588+
/// <returns>A <see cref="Task"/> that represents the asynchronous download operation.</returns>
589+
/// <exception cref="ArgumentNullException"><paramref name="output"/> or <paramref name="path"/> is <see langword="null"/>.</exception>
590+
/// <exception cref="ArgumentException"><paramref name="path"/> is empty or contains only whitespace characters.</exception>
591+
/// <exception cref="SshConnectionException">Client is not connected.</exception>
592+
/// <exception cref="SftpPermissionDeniedException">Permission to perform the operation was denied by the remote host. <para>-or-</para> An SSH command was denied by the server.</exception>
593+
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
594+
/// <exception cref="SshException">An SSH error where <see cref="Exception.Message" /> is the message from the remote host.</exception>
595+
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
596+
Task DownloadFileAsync(string path, Stream output, Action<ulong>? downloadCallback, CancellationToken cancellationToken = default);
597+
581598
/// <summary>
582599
/// Ends an asynchronous file downloading into the stream.
583600
/// </summary>

src/Renci.SshNet/SftpClient.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,12 @@ public void DownloadFile(string path, Stream output, Action<ulong>? downloadCall
912912

913913
/// <inheritdoc />
914914
public Task DownloadFileAsync(string path, Stream output, CancellationToken cancellationToken = default)
915+
{
916+
return DownloadFileAsync(path, output, downloadCallback: null, cancellationToken);
917+
}
918+
919+
/// <inheritdoc />
920+
public Task DownloadFileAsync(string path, Stream output, Action<ulong>? downloadCallback, CancellationToken cancellationToken = default)
915921
{
916922
ArgumentException.ThrowIfNullOrWhiteSpace(path);
917923
ArgumentNullException.ThrowIfNull(output);
@@ -921,7 +927,7 @@ public Task DownloadFileAsync(string path, Stream output, CancellationToken canc
921927
path,
922928
output,
923929
asyncResult: null,
924-
downloadCallback: null,
930+
downloadCallback: downloadCallback,
925931
isAsync: true,
926932
cancellationToken);
927933
}

test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Download.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,5 +124,34 @@ public void Test_Sftp_EndDownloadFile_Invalid_Async_Handle()
124124
Assert.ThrowsExactly<ArgumentException>(() => sftp.EndDownloadFile(async1));
125125
}
126126
}
127+
128+
[TestMethod]
129+
[TestCategory("Sftp")]
130+
public async Task Test_Sftp_DownloadFileAsync_DownloadCallback()
131+
{
132+
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
133+
{
134+
await sftp.ConnectAsync(CancellationToken.None);
135+
var filename = Path.GetTempFileName();
136+
int testFileSizeMB = 1;
137+
CreateTestFile(filename, testFileSizeMB);
138+
await sftp.UploadFileAsync(File.OpenRead(filename), "test123");
139+
using ManualResetEventSlim finalCallbackCalledEvent = new();
140+
141+
void Callback(ulong totalBytesRead)
142+
{
143+
if ((int)totalBytesRead == testFileSizeMB * 1024 * 1024)
144+
{
145+
finalCallbackCalledEvent.Set();
146+
}
147+
}
148+
149+
await sftp.DownloadFileAsync("test123", new MemoryStream(), Callback, CancellationToken.None);
150+
151+
// since the callback is queued to the thread pool, wait for the event.
152+
bool callbackCalled = finalCallbackCalledEvent.Wait(5000);
153+
Assert.IsTrue(callbackCalled);
154+
}
155+
}
127156
}
128157
}

0 commit comments

Comments
 (0)