diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs index a87a6a713..7122ccdff 100644 --- a/src/Renci.SshNet/ISftpClient.cs +++ b/src/Renci.SshNet/ISftpClient.cs @@ -358,6 +358,20 @@ public interface ISftpClient : IBaseClient /// The method was called after the client was disposed. void ChangeDirectory(string path); + /// + /// Asynchronously requests to change the current working directory to the specified path. + /// + /// The new working directory. + /// The token to monitor for cancellation requests. + /// A that tracks the asynchronous change working directory request. + /// is . + /// Client is not connected. + /// Permission to change directory denied by remote host. -or- A SSH command was denied by the server. + /// was not found on the remote host. + /// A SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default); + /// /// Changes permissions of file(s) to specified mode. /// diff --git a/src/Renci.SshNet/Sftp/ISftpSession.cs b/src/Renci.SshNet/Sftp/ISftpSession.cs index ec7e77802..1c028efdb 100644 --- a/src/Renci.SshNet/Sftp/ISftpSession.cs +++ b/src/Renci.SshNet/Sftp/ISftpSession.cs @@ -34,6 +34,14 @@ internal interface ISftpSession : ISubsystemSession /// The new working directory. void ChangeDirectory(string path); + /// + /// Asynchronously requests to change the current working directory to the specified path. + /// + /// The new working directory. + /// The token to monitor for cancellation requests. + /// A that tracks the asynchronous change working directory request. + Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default); + /// /// Resolves a given path into an absolute path on the server. /// diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs index a397d66ba..5b70a7f1f 100644 --- a/src/Renci.SshNet/Sftp/SftpSession.cs +++ b/src/Renci.SshNet/Sftp/SftpSession.cs @@ -82,6 +82,24 @@ public void ChangeDirectory(string path) WorkingDirectory = fullPath; } + /// + /// Asynchronously requests to change the current working directory to the specified path. + /// + /// The new working directory. + /// The token to monitor for cancellation requests. + /// A that tracks the asynchronous change working directory request. + public async Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var fullPath = await GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false); + var handle = await RequestOpenDirAsync(fullPath, cancellationToken).ConfigureAwait(false); + + await RequestCloseAsync(handle, cancellationToken).ConfigureAwait(false); + + WorkingDirectory = fullPath; + } + internal void SendMessage(SftpMessage sftpMessage) { var data = sftpMessage.GetBytes(); diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index 59fe2377e..d36b96c00 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -305,6 +305,33 @@ public void ChangeDirectory(string path) _sftpSession.ChangeDirectory(path); } + /// + /// Asynchronously requests to change the current working directory to the specified path. + /// + /// The new working directory. + /// The token to monitor for cancellation requests. + /// A that tracks the asynchronous change working directory request. + /// is . + /// Client is not connected. + /// Permission to change directory denied by remote host. -or- A SSH command was denied by the server. + /// was not found on the remote host. + /// A SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + public Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default) + { + CheckDisposed(); + ThrowHelper.ThrowIfNull(path); + + if (_sftpSession is null) + { + throw new SshConnectionException("Client not connected."); + } + + cancellationToken.ThrowIfCancellationRequested(); + + return _sftpSession.ChangeDirectoryAsync(path, cancellationToken); + } + /// /// Changes permissions of file(s) to specified mode. /// diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs index 6fb518506..4076c6b01 100644 --- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs +++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs @@ -19,6 +19,18 @@ public void Test_Sftp_ChangeDirectory_Root_Dont_Exists() } } + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public async Task Test_Sftp_ChangeDirectory_Root_Dont_ExistsAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + await sftp.ChangeDirectoryAsync("/asdasd", CancellationToken.None).ConfigureAwait(false); + } + } + [TestMethod] [TestCategory("Sftp")] [ExpectedException(typeof(SftpPathNotFoundException))] @@ -31,6 +43,18 @@ public void Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_Exists() } } + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public async Task Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_ExistsAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + await sftp.ChangeDirectoryAsync("/asdasd/", CancellationToken.None).ConfigureAwait(false); + } + } + [TestMethod] [TestCategory("Sftp")] [ExpectedException(typeof(SftpPathNotFoundException))] @@ -43,6 +67,18 @@ public void Test_Sftp_ChangeDirectory_Subfolder_Dont_Exists() } } + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public async Task Test_Sftp_ChangeDirectory_Subfolder_Dont_ExistsAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + await sftp.ChangeDirectoryAsync("/asdasd/sssddds", CancellationToken.None).ConfigureAwait(false); + } + } + [TestMethod] [TestCategory("Sftp")] [ExpectedException(typeof(SftpPathNotFoundException))] @@ -55,6 +91,18 @@ public void Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_Exists() } } + [TestMethod] + [TestCategory("Sftp")] + [ExpectedException(typeof(SftpPathNotFoundException))] + public async Task Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_ExistsAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + await sftp.ChangeDirectoryAsync("/asdasd/sssddds/", CancellationToken.None).ConfigureAwait(false); + } + } + [TestMethod] [TestCategory("Sftp")] public void Test_Sftp_ChangeDirectory_Which_Exists() @@ -67,6 +115,18 @@ public void Test_Sftp_ChangeDirectory_Which_Exists() } } + [TestMethod] + [TestCategory("Sftp")] + public async Task Test_Sftp_ChangeDirectory_Which_ExistsAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + await sftp.ChangeDirectoryAsync("/usr", CancellationToken.None).ConfigureAwait(false); + Assert.AreEqual("/usr", sftp.WorkingDirectory); + } + } + [TestMethod] [TestCategory("Sftp")] public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash() @@ -78,5 +138,17 @@ public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash() Assert.AreEqual("/usr", sftp.WorkingDirectory); } } + + [TestMethod] + [TestCategory("Sftp")] + public async Task Test_Sftp_ChangeDirectory_Which_Exists_With_SlashAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + await sftp.ChangeDirectoryAsync("/usr/", CancellationToken.None).ConfigureAwait(false); + Assert.AreEqual("/usr", sftp.WorkingDirectory); + } + } } } diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs index 86e02b2ca..59fefb480 100644 --- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs +++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs @@ -229,6 +229,75 @@ public void Test_Sftp_Change_Directory() RemoveAllFiles(); } + [TestMethod] + [TestCategory("Sftp")] + public async Task Test_Sftp_Change_DirectoryAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet"); + + sftp.CreateDirectory("test1"); + + await sftp.ChangeDirectoryAsync("test1", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1"); + + sftp.CreateDirectory("test1_1"); + sftp.CreateDirectory("test1_2"); + sftp.CreateDirectory("test1_3"); + + var files = sftp.ListDirectory("."); + + Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}", sftp.WorkingDirectory))); + + await sftp.ChangeDirectoryAsync("test1_1", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1"); + + await sftp.ChangeDirectoryAsync("../test1_2", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2"); + + await sftp.ChangeDirectoryAsync("..", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1"); + + await sftp.ChangeDirectoryAsync("..", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet"); + + files = sftp.ListDirectory("test1/test1_1"); + + Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}/test1/test1_1", sftp.WorkingDirectory))); + + await sftp.ChangeDirectoryAsync("test1/test1_1", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1"); + + await sftp.ChangeDirectoryAsync("/home/sshnet/test1/test1_1", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1"); + + await sftp.ChangeDirectoryAsync("/home/sshnet/test1/test1_1/../test1_2", CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2"); + + await sftp.ChangeDirectoryAsync("../../", CancellationToken.None).ConfigureAwait(false); + + sftp.DeleteDirectory("test1/test1_1"); + sftp.DeleteDirectory("test1/test1_2"); + sftp.DeleteDirectory("test1/test1_3"); + sftp.DeleteDirectory("test1"); + + sftp.Disconnect(); + } + + RemoveAllFiles(); + } + [TestMethod] [TestCategory("Sftp")] [Description("Test passing null to ChangeDirectory.")] @@ -245,6 +314,22 @@ public void Test_Sftp_ChangeDirectory_Null() } } + [TestMethod] + [TestCategory("Sftp")] + [Description("Test passing null to ChangeDirectory.")] + [ExpectedException(typeof(ArgumentNullException))] + public async Task Test_Sftp_ChangeDirectory_NullAsync() + { + using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + { + await sftp.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + + await sftp.ChangeDirectoryAsync(null, CancellationToken.None).ConfigureAwait(false); + + sftp.Disconnect(); + } + } + [TestMethod] [TestCategory("Sftp")] [Description("Test calling EndListDirectory method more then once.")] diff --git a/test/Renci.SshNet.IntegrationTests/SftpTests.cs b/test/Renci.SshNet.IntegrationTests/SftpTests.cs index 81e1217e1..f03ee6a2c 100644 --- a/test/Renci.SshNet.IntegrationTests/SftpTests.cs +++ b/test/Renci.SshNet.IntegrationTests/SftpTests.cs @@ -995,7 +995,6 @@ public void Sftp_AppendText_Encoding_ExistingFile() { client.DeleteFile(remoteFile); } - } } } @@ -3993,6 +3992,36 @@ public void Sftp_ChangeDirectory_DirectoryDoesNotExist() } } + [TestMethod] + public async Task Sftp_ChangeDirectory_DirectoryDoesNotExistAsync() + { + const string remoteDirectory = "/home/sshnet/test123"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + } + } + + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + try + { + await client.ChangeDirectoryAsync(remoteDirectory, CancellationToken.None).ConfigureAwait(false); + Assert.Fail(); + } + catch (SftpPathNotFoundException) + { + } + } + } + [TestMethod] public void Sftp_ChangeDirectory_DirectoryExists() { @@ -4052,6 +4081,65 @@ public void Sftp_ChangeDirectory_DirectoryExists() } } + [TestMethod] + public async Task Sftp_ChangeDirectory_DirectoryExistsAsync() + { + const string remoteDirectory = "/home/sshnet/test123"; + + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + } + + using (var command = client.CreateCommand("mkdir -p " + _remotePathTransformation.Transform(remoteDirectory))) + { + await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + } + } + + try + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + + await client.ChangeDirectoryAsync(remoteDirectory, CancellationToken.None).ConfigureAwait(false); + + Assert.AreEqual(remoteDirectory, client.WorkingDirectory); + + using (var uploadStream = CreateMemoryStream(100)) + { + uploadStream.Position = 0; + + client.UploadFile(uploadStream, "gert.txt"); + + uploadStream.Position = 0; + + using (var downloadStream = client.OpenRead(remoteDirectory + "/gert.txt")) + { + Assert.AreEqual(CreateHash(uploadStream), CreateHash(downloadStream)); + } + } + } + } + finally + { + using (var client = new SshClient(_connectionInfoFactory.Create())) + { + await client.ConnectAsync(CancellationToken.None).ConfigureAwait(false); + + using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory))) + { + await command.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + } + } + } + } + [TestMethod] public void Sftp_DownloadFile_MemoryStream() {