Add UploadFileAsync and DownloadFileAsync methods (#1634)

* add interface methods

* add internal file methods

* impl interface methods

* tweak buffer size usage

* swap tests with async upload

* swap more upload file references

* add async download tests

* add upload/download integration async test

* check if net48

* silence not await warning

* try tweaking test init

* remove request close from upload

* dispose already closes, remove dup call

* remove extra upload overload

* inherit doc

* configure await

* remove excess util functions

* add cancel throws

* add cancellation tests

* missed one configure await

* use default buffer size

* private ctor

* docs

---------

Co-authored-by: Rob Hague <rob.hague00@gmail.com>
This commit is contained in:
Noah Dela Rosa
2025-05-05 16:03:10 -05:00
committed by GitHub
parent 7c07b10e4a
commit d08c4aaa8a
7 changed files with 245 additions and 100 deletions
+54 -32
View File
@@ -556,23 +556,36 @@ namespace Renci.SshNet
Task DeleteFileAsync(string path, CancellationToken cancellationToken);
/// <summary>
/// Downloads remote file specified by the path into the stream.
/// Downloads a remote file into a <see cref="Stream"/>.
/// </summary>
/// <param name="path">File to download.</param>
/// <param name="output">Stream to write the file into.</param>
/// <param name="path">The path to the remote file.</param>
/// <param name="output">The <see cref="Stream"/> to write the file into.</param>
/// <param name="downloadCallback">The download callback.</param>
/// <exception cref="ArgumentNullException"><paramref name="output" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="ArgumentNullException"><paramref name="output"/> or <paramref name="path"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path"/> is empty or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to perform the operation was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>///
/// <exception cref="SshException">A SSH error where <see cref="Exception.Message" /> is the message from the remote host.</exception>
/// <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>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="SshException">An 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>
/// <remarks>
/// Method calls made by this method to <paramref name="output" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
void DownloadFile(string path, Stream output, Action<ulong>? downloadCallback = null);
/// <summary>
/// Asynchronously downloads a remote file into a <see cref="Stream"/>.
/// </summary>
/// <param name="path">The path to the remote file.</param>
/// <param name="output">The <see cref="Stream"/> to write the file into.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous download operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="output"/> or <paramref name="path"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path"/> is empty or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <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>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="SshException">An 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>
Task DownloadFileAsync(string path, Stream output, CancellationToken cancellationToken = default);
/// <summary>
/// Ends an asynchronous file downloading into the stream.
/// </summary>
@@ -1070,40 +1083,49 @@ namespace Renci.SshNet
IEnumerable<FileInfo> SynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern);
/// <summary>
/// Uploads stream into remote file.
/// Uploads a <see cref="Stream"/> to a remote file path.
/// </summary>
/// <param name="input">Data input stream.</param>
/// <param name="path">Remote file path.</param>
/// <param name="input">The <see cref="Stream"/> to write to the remote path.</param>
/// <param name="path">The remote file path to write to.</param>
/// <param name="uploadCallback">The upload callback.</param>
/// <exception cref="ArgumentNullException"><paramref name="input" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="ArgumentNullException"><paramref name="input" /> or <paramref name="path" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is empty or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to upload the file 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="SftpPermissionDeniedException">Permission to upload the file was denied by the remote host. <para>-or-</para> An SSH command was denied by the server.</exception>
/// <exception cref="SshException">An 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>
/// <remarks>
/// Method calls made by this method to <paramref name="input" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
void UploadFile(Stream input, string path, Action<ulong>? uploadCallback = null);
/// <summary>
/// Uploads stream into remote file.
/// Uploads a <see cref="Stream"/> to a remote file path.
/// </summary>
/// <param name="input">Data input stream.</param>
/// <param name="path">Remote file path.</param>
/// <param name="canOverride">if set to <see langword="true"/> then existing file will be overwritten.</param>
/// <param name="input">The <see cref="Stream"/> to write to the remote path.</param>
/// <param name="path">The remote file path to write to.</param>
/// <param name="canOverride">Whether the remote file can be overwritten if it already exists.</param>
/// <param name="uploadCallback">The upload callback.</param>
/// <exception cref="ArgumentNullException"><paramref name="input" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="ArgumentNullException"><paramref name="input" /> or <paramref name="path" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is empty or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to upload the file 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="SftpPermissionDeniedException">Permission to upload the file was denied by the remote host. <para>-or-</para> An SSH command was denied by the server.</exception>
/// <exception cref="SshException">An 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>
/// <remarks>
/// Method calls made by this method to <paramref name="input" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
void UploadFile(Stream input, string path, bool canOverride, Action<ulong>? uploadCallback = null);
/// <summary>
/// Asynchronously uploads a <see cref="Stream"/> to a remote file path.
/// </summary>
/// <param name="input">The <see cref="Stream"/> to write to the remote path.</param>
/// <param name="path">The remote file path to write to.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous upload operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="input"/> or <paramref name="path"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is empty or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to upload the file was denied by the remote host. <para>-or-</para> An SSH command was denied by the server.</exception>
/// <exception cref="SshException">An 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>
Task UploadFileAsync(Stream input, string path, CancellationToken cancellationToken = default);
/// <summary>
/// Writes the specified byte array to the specified file, and closes the file.
/// </summary>
+61 -47
View File
@@ -893,22 +893,7 @@ namespace Renci.SshNet
}
}
/// <summary>
/// Downloads remote file specified by the path into the stream.
/// </summary>
/// <param name="path">File to download.</param>
/// <param name="output">Stream to write the file into.</param>
/// <param name="downloadCallback">The download callback.</param>
/// <exception cref="ArgumentNullException"><paramref name="output" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to perform the operation was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</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>
/// <remarks>
/// Method calls made by this method to <paramref name="output" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
/// <inheritdoc />
public void DownloadFile(string path, Stream output, Action<ulong>? downloadCallback = null)
{
CheckDisposed();
@@ -916,6 +901,14 @@ namespace Renci.SshNet
InternalDownloadFile(path, output, asyncResult: null, downloadCallback);
}
/// <inheritdoc />
public Task DownloadFileAsync(string path, Stream output, CancellationToken cancellationToken = default)
{
CheckDisposed();
return InternalDownloadFileAsync(path, output, cancellationToken);
}
/// <summary>
/// Begins an asynchronous file downloading into the stream.
/// </summary>
@@ -1023,42 +1016,13 @@ namespace Renci.SshNet
ar.EndInvoke();
}
/// <summary>
/// Uploads stream into remote file.
/// </summary>
/// <param name="input">Data input stream.</param>
/// <param name="path">Remote file path.</param>
/// <param name="uploadCallback">The upload callback.</param>
/// <exception cref="ArgumentNullException"><paramref name="input" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to upload the file 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>
/// <remarks>
/// Method calls made by this method to <paramref name="input" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
/// <inheritdoc/>
public void UploadFile(Stream input, string path, Action<ulong>? uploadCallback = null)
{
UploadFile(input, path, canOverride: true, uploadCallback);
}
/// <summary>
/// Uploads stream into remote file.
/// </summary>
/// <param name="input">Data input stream.</param>
/// <param name="path">Remote file path.</param>
/// <param name="canOverride">if set to <see langword="true"/> then existing file will be overwritten.</param>
/// <param name="uploadCallback">The upload callback.</param>
/// <exception cref="ArgumentNullException"><paramref name="input" /> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="path" /> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to upload the file 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>
/// <remarks>
/// Method calls made by this method to <paramref name="input" />, may under certain conditions result in exceptions thrown by the stream.
/// </remarks>
/// <inheritdoc/>
public void UploadFile(Stream input, string path, bool canOverride, Action<ulong>? uploadCallback = null)
{
CheckDisposed();
@@ -1077,6 +1041,14 @@ namespace Renci.SshNet
InternalUploadFile(input, path, flags, asyncResult: null, uploadCallback);
}
/// <inheritdoc />
public Task UploadFileAsync(Stream input, string path, CancellationToken cancellationToken = default)
{
CheckDisposed();
return InternalUploadFileAsync(input, path, cancellationToken);
}
/// <summary>
/// Begins an asynchronous uploading the stream into remote file.
/// </summary>
@@ -2433,6 +2405,27 @@ namespace Renci.SshNet
}
}
private async Task InternalDownloadFileAsync(string path, Stream output, CancellationToken cancellationToken)
{
ThrowHelper.ThrowIfNull(output);
ThrowHelper.ThrowIfNullOrWhiteSpace(path);
if (_sftpSession is null)
{
throw new SshConnectionException("Client not connected.");
}
cancellationToken.ThrowIfCancellationRequested();
var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);
var openStreamTask = SftpFileStream.OpenAsync(_sftpSession, fullPath, FileMode.Open, FileAccess.Read, (int)_bufferSize, cancellationToken);
using (var input = await openStreamTask.ConfigureAwait(false))
{
await input.CopyToAsync(output, 81920, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Internals the upload file.
/// </summary>
@@ -2515,6 +2508,27 @@ namespace Renci.SshNet
responseReceivedWaitHandle.Dispose();
}
private async Task InternalUploadFileAsync(Stream input, string path, CancellationToken cancellationToken)
{
ThrowHelper.ThrowIfNull(input);
ThrowHelper.ThrowIfNullOrWhiteSpace(path);
if (_sftpSession is null)
{
throw new SshConnectionException("Client not connected.");
}
cancellationToken.ThrowIfCancellationRequested();
var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);
var openStreamTask = SftpFileStream.OpenAsync(_sftpSession, fullPath, FileMode.Create, FileAccess.Write, (int)_bufferSize, cancellationToken);
using (var output = await openStreamTask.ConfigureAwait(false))
{
await input.CopyToAsync(output, 81920, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Called when client is connected to the server.
/// </summary>
@@ -31,6 +31,44 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
}
}
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_DownloadAsync_Forbidden()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password))
{
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
await Assert.ThrowsExceptionAsync<SftpPermissionDeniedException>(() => sftp.DownloadFileAsync("/root/.profile", Stream.Null));
}
}
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_DownloadAsync_File_Not_Exists()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
await Assert.ThrowsExceptionAsync<SftpPathNotFoundException>(() => sftp.DownloadFileAsync("/xxx/eee/yyy", Stream.Null));
}
}
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_DownloadAsync_Cancellation_Requested()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
var cancelledToken = new CancellationToken(true);
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => sftp.DownloadFileAsync("/xxx/eee/yyy", Stream.Null, cancelledToken));
}
}
[TestMethod]
[TestCategory("Sftp")]
[Description("Test passing null to BeginDownloadFile")]
@@ -51,6 +51,49 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
}
}
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_Upload_And_Download_Async_1MB_File()
{
RemoveAllFiles();
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
var uploadedFileName = Path.GetTempFileName();
var remoteFileName = Path.GetRandomFileName();
CreateTestFile(uploadedFileName, 1);
// Calculate has value
var uploadedHash = CalculateMD5(uploadedFileName);
using (var file = File.OpenRead(uploadedFileName))
{
await sftp.UploadFileAsync(file, remoteFileName).ConfigureAwait(false);
}
var downloadedFileName = Path.GetTempFileName();
using (var file = File.OpenWrite(downloadedFileName))
{
await sftp.DownloadFileAsync(remoteFileName, file).ConfigureAwait(false);
}
var downloadedHash = CalculateMD5(downloadedFileName);
await sftp.DeleteFileAsync(remoteFileName, CancellationToken.None).ConfigureAwait(false);
File.Delete(uploadedFileName);
File.Delete(downloadedFileName);
sftp.Disconnect();
Assert.AreEqual(uploadedHash, downloadedHash);
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_Upload_Forbidden()
@@ -73,6 +116,28 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
}
}
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_UploadAsync_Cancellation_Requested()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
await sftp.ConnectAsync(CancellationToken.None);
var uploadedFileName = Path.GetTempFileName();
var remoteFileName = "/root/1";
CreateTestFile(uploadedFileName, 1);
var cancelledToken = new CancellationToken(true);
using (var file = File.OpenRead(uploadedFileName))
{
await Assert.ThrowsAsync<OperationCanceledException>(() => sftp.UploadFileAsync(file, remoteFileName, cancelledToken));
}
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each()
@@ -88,7 +88,7 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
var directory = await sftp.GetAsync("/", default).ConfigureAwait(false);
Assert.AreEqual("/", directory.FullName);
@@ -103,7 +103,7 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
await Assert.ThrowsExceptionAsync<SftpPathNotFoundException>(() => sftp.GetAsync("/xyz", default));
}
@@ -115,9 +115,9 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
sftp.UploadFile(new MemoryStream(), "abc.txt");
await sftp.UploadFileAsync(new MemoryStream(), "abc.txt").ConfigureAwait(false);
var file = await sftp.GetAsync("abc.txt", default).ConfigureAwait(false);
@@ -133,7 +133,7 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => sftp.GetAsync(null, default));
}
@@ -145,9 +145,9 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
sftp.UploadFile(new MemoryStream(), "test-üöä-");
await sftp.UploadFileAsync(new MemoryStream(), "test-üöä-").ConfigureAwait(false);
var file = await sftp.GetAsync("test-üöä-", default).ConfigureAwait(false);
@@ -6,14 +6,26 @@ namespace Renci.SshNet.IntegrationTests
/// The SFTP client integration tests
/// </summary>
[TestClass]
public class SftpClientTests : IntegrationTestBase, IDisposable
public class SftpClientTests : IntegrationTestBase
{
private readonly SftpClient _sftpClient;
public SftpClientTests()
{
_sftpClient = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
_sftpClient.Connect();
}
[TestInitialize]
public async Task InitializeAsync()
{
await _sftpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
}
[TestCleanup]
public void Cleanup()
{
_sftpClient.Disconnect();
_sftpClient.Dispose();
}
[TestMethod]
@@ -65,7 +77,7 @@ namespace Renci.SshNet.IntegrationTests
// 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).ConfigureAwait(false);
Assert.IsTrue(await _sftpClient.ExistsAsync(testFilePath));
// Check if ListDirectory works
@@ -118,12 +130,12 @@ namespace Renci.SshNet.IntegrationTests
var testContent = "file content";
// Create new directory and check if it exists
await _sftpClient.CreateDirectoryAsync(testDirectory);
await _sftpClient.CreateDirectoryAsync(testDirectory).ConfigureAwait(false);
Assert.IsTrue(await _sftpClient.ExistsAsync(testDirectory).ConfigureAwait(false));
// 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).ConfigureAwait(false);
Assert.IsTrue(await _sftpClient.ExistsAsync(testFilePath).ConfigureAwait(false));
await _sftpClient.DeleteFileAsync(testFilePath, CancellationToken.None).ConfigureAwait(false);
@@ -142,7 +154,7 @@ namespace Renci.SshNet.IntegrationTests
var testDirectory = "/home/sshnet/sshnet-test";
// Create new directory and check if it exists
await _sftpClient.CreateDirectoryAsync(testDirectory);
await _sftpClient.CreateDirectoryAsync(testDirectory).ConfigureAwait(false);
Assert.IsTrue(await _sftpClient.ExistsAsync(testDirectory).ConfigureAwait(false));
await _sftpClient.DeleteAsync(testDirectory, CancellationToken.None).ConfigureAwait(false);
@@ -158,18 +170,12 @@ namespace Renci.SshNet.IntegrationTests
// 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).ConfigureAwait(false);
Assert.IsTrue(await _sftpClient.ExistsAsync(testFileName).ConfigureAwait(false));
await _sftpClient.DeleteAsync(testFileName, CancellationToken.None).ConfigureAwait(false);
Assert.IsFalse(await _sftpClient.ExistsAsync(testFileName).ConfigureAwait(false));
}
public void Dispose()
{
_sftpClient.Disconnect();
_sftpClient.Dispose();
}
}
}
@@ -4115,7 +4115,7 @@ namespace Renci.SshNet.IntegrationTests
{
uploadStream.Position = 0;
client.UploadFile(uploadStream, "gert.txt");
await client.UploadFileAsync(uploadStream, "gert.txt").ConfigureAwait(false);
uploadStream.Position = 0;