diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.EnumerateDirectory.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.EnumerateDirectory.cs
new file mode 100644
index 000000000..ef4b3d2ae
--- /dev/null
+++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest.EnumerateDirectory.cs
@@ -0,0 +1,209 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using Renci.SshNet.Tests.Properties;
+using System;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Renci.SshNet.Tests.Classes
+{
+ ///
+ /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+ ///
+ public partial class SftpClientTest : TestBase
+ {
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [ExpectedException(typeof(SshConnectionException))]
+ public void Test_Sftp_EnumerateDirectory_Without_Connecting()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ var files = sftp.EnumerateDirectory(".");
+ foreach (var file in files)
+ {
+ Debug.WriteLine(file.FullName);
+ }
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [TestCategory("integration")]
+ [ExpectedException(typeof(SftpPermissionDeniedException))]
+ public void Test_Sftp_EnumerateDirectory_Permission_Denied()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ sftp.Connect();
+
+ var files = sftp.EnumerateDirectory("/root");
+ foreach (var file in files)
+ {
+ Debug.WriteLine(file.FullName);
+ }
+
+ sftp.Disconnect();
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [TestCategory("integration")]
+ [ExpectedException(typeof(SftpPathNotFoundException))]
+ public void Test_Sftp_EnumerateDirectory_Not_Exists()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ sftp.Connect();
+
+ var files = sftp.EnumerateDirectory("/asdfgh");
+ foreach (var file in files)
+ {
+ Debug.WriteLine(file.FullName);
+ }
+
+ sftp.Disconnect();
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [TestCategory("integration")]
+ public void Test_Sftp_EnumerateDirectory_Current()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ sftp.Connect();
+
+ var files = sftp.EnumerateDirectory(".");
+
+ Assert.IsTrue(files.Count() > 0);
+
+ foreach (var file in files)
+ {
+ Debug.WriteLine(file.FullName);
+ }
+
+ sftp.Disconnect();
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [TestCategory("integration")]
+ public void Test_Sftp_EnumerateDirectory_Empty()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ sftp.Connect();
+
+ var files = sftp.EnumerateDirectory(string.Empty);
+
+ Assert.IsTrue(files.Count() > 0);
+
+ foreach (var file in files)
+ {
+ Debug.WriteLine(file.FullName);
+ }
+
+ sftp.Disconnect();
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [TestCategory("integration")]
+ [Description("Test passing null to EnumerateDirectory.")]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public void Test_Sftp_EnumerateDirectory_Null()
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ sftp.Connect();
+
+ var files = sftp.EnumerateDirectory(null);
+
+ Assert.IsTrue(files.Count() > 0);
+
+ foreach (var file in files)
+ {
+ Debug.WriteLine(file.FullName);
+ }
+
+ sftp.Disconnect();
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [TestCategory("integration")]
+ public void Test_Sftp_EnumerateDirectory_HugeDirectory()
+ {
+ var stopwatch = Stopwatch.StartNew();
+ try
+ {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ sftp.Connect();
+ sftp.ChangeDirectory("/home/" + Resources.USERNAME);
+
+ var count = 10000;
+ // Create 10000 directory items
+ for (int i = 0; i < count; i++)
+ {
+ sftp.CreateDirectory(string.Format("test_{0}", i));
+ }
+ Debug.WriteLine(string.Format("Created {0} directories within {1} seconds", count, stopwatch.Elapsed.TotalSeconds));
+
+ stopwatch.Reset(); stopwatch.Start();
+ var files = sftp.EnumerateDirectory(".");
+ Debug.WriteLine(string.Format("Listed {0} directories within {1} seconds", count, stopwatch.Elapsed.TotalSeconds));
+
+ // Ensure that directory has at least 10000 items
+ stopwatch.Reset(); stopwatch.Start();
+ var actualCount = files.Count();
+ Assert.IsTrue(actualCount >= count);
+ Debug.WriteLine(string.Format("Used {0} items within {1} seconds", actualCount, stopwatch.Elapsed.TotalSeconds));
+
+ sftp.Disconnect();
+ }
+ }
+ finally
+ {
+ stopwatch.Reset(); stopwatch.Start();
+ RemoveAllFiles();
+ stopwatch.Stop();
+ Debug.WriteLine(string.Format("Removed all files within {0} seconds", stopwatch.Elapsed.TotalSeconds));
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Sftp")]
+ [TestCategory("integration")]
+ [ExpectedException(typeof(SshConnectionException))]
+ public void Test_Sftp_EnumerateDirectory_After_Disconnected()
+ {
+ try {
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ {
+ sftp.Connect();
+
+ sftp.CreateDirectory("test_at_dsiposed");
+
+ var files = sftp.EnumerateDirectory(".").Take(1);
+
+ sftp.Disconnect();
+
+ // Must fail on disconnected session.
+ var count = files.Count();
+ }
+ }
+ finally
+ {
+ RemoveAllFiles();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs
index 69f90aefe..360c29bab 100644
--- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs
+++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest.ListDirectory.cs
@@ -140,26 +140,42 @@ public void Test_Sftp_ListDirectory_Null()
[TestCategory("integration")]
public void Test_Sftp_ListDirectory_HugeDirectory()
{
- using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+ var stopwatch = Stopwatch.StartNew();
+ try
{
- sftp.Connect();
-
- // Create 10000 directory items
- for (int i = 0; i < 10000; i++)
+ using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
- sftp.CreateDirectory(string.Format("test_{0}", i));
- Debug.WriteLine("Created " + i);
+ sftp.Connect();
+ sftp.ChangeDirectory("/home/" + Resources.USERNAME);
+
+ var count = 10000;
+ // Create 10000 directory items
+ for (int i = 0; i < count; i++)
+ {
+ sftp.CreateDirectory(string.Format("test_{0}", i));
+ }
+ Debug.WriteLine(string.Format("Created {0} directories within {1} seconds", count, stopwatch.Elapsed.TotalSeconds));
+
+ stopwatch.Reset(); stopwatch.Start();
+ var files = sftp.ListDirectory(".");
+ Debug.WriteLine(string.Format("Listed {0} directories within {1} seconds", count, stopwatch.Elapsed.TotalSeconds));
+
+ // Ensure that directory has at least 10000 items
+ stopwatch.Reset(); stopwatch.Start();
+ var actualCount = files.Count();
+ Assert.IsTrue(actualCount >= count);
+ Debug.WriteLine(string.Format("Used {0} items within {1} seconds", actualCount, stopwatch.Elapsed.TotalSeconds));
+
+ sftp.Disconnect();
}
-
- var files = sftp.ListDirectory(".");
-
- // Ensure that directory has at least 10000 items
- Assert.IsTrue(files.Count() > 10000);
-
- sftp.Disconnect();
}
-
- RemoveAllFiles();
+ finally
+ {
+ stopwatch.Reset(); stopwatch.Start();
+ RemoveAllFiles();
+ stopwatch.Stop();
+ Debug.WriteLine(string.Format("Removed all files within {0} seconds", stopwatch.Elapsed.TotalSeconds));
+ }
}
[TestMethod]
diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs
index b1212cfea..8954793a4 100644
--- a/src/Renci.SshNet/ISftpClient.cs
+++ b/src/Renci.SshNet/ISftpClient.cs
@@ -668,6 +668,28 @@ public interface ISftpClient
/// The method was called after the client was disposed.
IEnumerable ListDirectory(string path, Action listCallback = null);
+ ///
+ /// Enumerates files and directories in remote directory.
+ ///
+ ///
+ /// This method differs to in the way how the items are returned.
+ /// It yields the items to the last moment for the enumerator to decide if it needs to continue or stop enumerating the items.
+ /// It is handy in case of really huge directory contents at remote server - meaning really huge 65 thousand files and more.
+ /// It also decrease the memory footprint and avoids LOH allocation as happen per call to method.
+ /// There aren't asynchronous counterpart methods to this because enumerating should happen in your specific asynchronous block.
+ ///
+ /// The path.
+ /// The list callback.
+ ///
+ /// An of files and directories ready to be enumerated.
+ ///
+ /// is null.
+ /// Client is not connected.
+ /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server.
+ /// A SSH error where is the message from the remote host.
+ /// The method was called after the client was disposed.
+ IEnumerable EnumerateDirectory(string path, Action listCallback = null);
+
///
/// Opens a on the specified path with read/write access.
///
diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs
index 3c22290d0..c9a480d19 100644
--- a/src/Renci.SshNet/SftpClient.cs
+++ b/src/Renci.SshNet/SftpClient.cs
@@ -537,6 +537,33 @@ public IEnumerable EndListDirectory(IAsyncResult asyncResult)
return ar.EndInvoke();
}
+ ///
+ /// Enumerates files and directories in remote directory.
+ ///
+ ///
+ /// This method differs to in the way how the items are returned.
+ /// It yields the items to the last moment for the enumerator to decide if it needs to continue or stop enumerating the items.
+ /// It is handy in case of really huge directory contents at remote server - meaning really huge 65 thousand files and more.
+ /// It also decrease the memory footprint and avoids LOH allocation as happen per call to method.
+ /// There aren't asynchronous counterpart methods to this because enumerating should happen in your specific asynchronous block.
+ ///
+ /// The path.
+ /// The list callback.
+ ///
+ /// An of files and directories ready to be enumerated.
+ ///
+ /// is null.
+ /// Client is not connected.
+ /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server.
+ /// A SSH error where is the message from the remote host.
+ /// The method was called after the client was disposed.
+ public IEnumerable EnumerateDirectory(string path, Action listCallback = null)
+ {
+ CheckDisposed();
+
+ return InternalEnumerateDirectory(path, listCallback);
+ }
+
///
/// Gets reference to remote file or directory.
///
@@ -1974,7 +2001,7 @@ private IEnumerable InternalSynchronizeDirectories(string sourcePath,
return uploadedFiles;
}
- #endregion
+#endregion
///
/// Internals the list directory.
@@ -1982,7 +2009,7 @@ private IEnumerable InternalSynchronizeDirectories(string sourcePath,
/// The path.
/// The list callback.
///
- /// A list of files in the specfied directory.
+ /// A list of files in the specified directory.
///
/// is null.
/// Client not connected.
@@ -2027,6 +2054,61 @@ private IEnumerable InternalListDirectory(string path, Action lis
return result;
}
+ ///
+ /// Internals the list directory.
+ ///
+ /// The path.
+ /// The list callback.
+ ///
+ /// A list of files in the specified directory.
+ ///
+ /// is null.
+ /// Client not connected.
+ private IEnumerable InternalEnumerateDirectory(string path, Action listCallback)
+ {
+ if (path == null)
+ throw new ArgumentNullException("path");
+
+ if (_sftpSession == null)
+ throw new SshConnectionException("Client not connected.");
+
+ var fullPath = _sftpSession.GetCanonicalPath(path);
+
+ var handle = _sftpSession.RequestOpenDir(fullPath);
+
+ var basePath = fullPath;
+
+ if (!basePath.EndsWith("/"))
+ basePath = string.Format("{0}/", fullPath);
+
+ try
+ {
+ int count = 0;
+ var files = _sftpSession.RequestReadDir(handle);
+
+ while (files != null)
+ {
+ count += files.Length;
+ // Call callback to report number of files read
+ if (listCallback != null)
+ {
+ // Execute callback on different thread
+ ThreadAbstraction.ExecuteThread(() => listCallback(count));
+ }
+ foreach (var file in files)
+ {
+ var fullName = string.Format(CultureInfo.InvariantCulture, "{0}{1}", basePath, file.Key);
+ yield return new SftpFile(_sftpSession, fullName, file.Value);
+ }
+ files = _sftpSession.RequestReadDir(handle);
+ }
+ }
+ finally
+ {
+ _sftpSession.RequestClose(handle);
+ }
+ }
+
///
/// Internals the download file.
///