C # Descargar todos los archivos y subdirectorios a través de FTP

Información general
Todavía estoy en el proceso de aprender C #. Para ayudarme a mí mismo, bash crear un progtwig que sincronice automáticamente todos mis proyectos locales con una carpeta en mi servidor FTP. Esto para que ya sea que esté en la escuela o en casa, siempre tengo los mismos proyectos disponibles para mí.

Sé que hay progtwigs como Dropbox que ya hacen esto por mí, pero pensé que crear algo así me enseñaría mucho en el camino.

El problema
Mi primer paso hacia mi objective fue simplemente descargar todos los archivos, subdirectorios y subarchivos de mi servidor FTP. Pude descargar todos los archivos de un directorio con el siguiente código. Sin embargo, mi código solo muestra los nombres de las carpetas y los archivos en el directorio principal. Las subcarpetas y subarchivos nunca se devuelven y nunca se descargan. Aparte de eso, el servidor devuelve un error de 550 porque estoy tratando de descargar las carpetas como si fueran archivos. Llevo más de 4 horas aquí, pero no encuentro nada sobre cómo solucionar estos problemas y hacer que funcione. Por lo tanto, espero que ustedes me ayuden 🙂

Código

public string[] GetFileList() { string[] downloadFiles; StringBuilder result = new StringBuilder(); WebResponse response = null; StreamReader reader = null; try { FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url); request.UseBinary = true; request.Method = WebRequestMethods.Ftp.ListDirectory; request.Credentials = new NetworkCredential(ftpUserName, ftpPassWord); request.KeepAlive = false; request.UsePassive = false; response = request.GetResponse(); reader = new StreamReader(response.GetResponseStream()); string line = reader.ReadLine(); while (line != null) { result.Append(line); result.Append("\n"); line = reader.ReadLine(); } result.Remove(result.ToString().LastIndexOf('\n'), 1); return result.ToString().Split('\n'); } catch (Exception ex) { if (reader != null) { reader.Close(); } if (response != null) { response.Close(); } downloadFiles = null; return downloadFiles; } } private void Download(string file) { try { string uri = url + "/" + file; Uri serverUri = new Uri(uri); if (serverUri.Scheme != Uri.UriSchemeFtp) { return; } FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url + "/" + file); request.UseBinary = true; request.Method = WebRequestMethods.Ftp.DownloadFile; request.Credentials = new NetworkCredential(ftpUserName, ftpPassWord); request.KeepAlive = false; request.UsePassive = false; FtpWebResponse response = (FtpWebResponse)request.GetResponse(); Stream responseStream = response.GetResponseStream(); FileStream writeStream = new FileStream(localDestnDir + "\\" + file, FileMode.Create); int Length = 2048; Byte[] buffer = new Byte[Length]; int bytesRead = responseStream.Read(buffer, 0, Length); while (bytesRead > 0) { writeStream.Write(buffer, 0, bytesRead); bytesRead = responseStream.Read(buffer, 0, Length); } writeStream.Close(); response.Close(); } catch (WebException wEx) { MessageBox.Show(wEx.Message, "Download Error"); } catch (Exception ex) { MessageBox.Show(ex.Message, "Download Error"); } } 

FtpWebRequest no tiene ningún soporte explícito para las operaciones recursivas de archivos (incluidas las descargas). Tienes que implementar la recursión tú mismo:

  • Lista el directorio remoto
  • Iterar las entradas, descargar archivos y recurrir a subdirectorios (volver a enumerarlos, etc.)

La parte difícil es identificar los archivos de los subdirectorios. No hay forma de hacerlo de forma portátil con FtpWebRequest . Desafortunadamente, FtpWebRequest no es compatible con el comando MLSD , que es la única forma portátil de recuperar la lista de directorios con atributos de archivos en el protocolo FTP. Consulte también Comprobar si el objeto en el servidor FTP es un archivo o directorio .

Tus opciones son:

  • Realice una operación en un nombre de archivo que seguramente fallará para el archivo y tendrá éxito para los directorios (o viceversa). Es decir, puede intentar descargar el “nombre”. Si eso tiene éxito, es un archivo, si eso falla, es un directorio.
  • Puede tener suerte y, en su caso específico, puede distinguir un archivo de un directorio por un nombre de archivo (es decir, todos sus archivos tienen una extensión, mientras que los subdirectorios no).
  • Utiliza una larga lista de directorios (comando LIST = método ListDirectoryDetails ) e intenta analizar una lista específica del servidor. Muchos servidores FTP usan la lista * nix-style, donde identifica un directorio por la d al comienzo de la entrada. Pero muchos servidores usan un formato diferente. El siguiente ejemplo usa este enfoque (asumiendo el formato * nix)
 void DownloadFtpDirectory(string url, NetworkCredential credentials, string localPath) { FtpWebRequest listRequest = (FtpWebRequest)WebRequest.Create(url); listRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails; listRequest.Credentials = credentials; List lines = new List(); using (FtpWebResponse listResponse = (FtpWebResponse)listRequest.GetResponse()) using (Stream listStream = listResponse.GetResponseStream()) using (StreamReader listReader = new StreamReader(listStream)) { while (!listReader.EndOfStream) { lines.Add(listReader.ReadLine()); } } foreach (string line in lines) { string[] tokens = line.Split(new[] { ' ' }, 9, StringSplitOptions.RemoveEmptyEntries); string name = tokens[8]; string permissions = tokens[0]; string localFilePath = Path.Combine(localPath, name); string fileUrl = url + name; if (permissions[0] == 'd') { if (!Directory.Exists(localFilePath)) { Directory.CreateDirectory(localFilePath); } DownloadFtpDirectory(fileUrl + "/", credentials, localFilePath); } else { FtpWebRequest downloadRequest = (FtpWebRequest)WebRequest.Create(fileUrl); downloadRequest.Method = WebRequestMethods.Ftp.DownloadFile; downloadRequest.Credentials = credentials; using (FtpWebResponse downloadResponse = (FtpWebResponse)downloadRequest.GetResponse()) using (Stream sourceStream = downloadResponse.GetResponseStream()) using (Stream targetStream = File.Create(localFilePath)) { byte[] buffer = new byte[10240]; int read; while ((read = sourceStream.Read(buffer, 0, buffer.Length)) > 0) { targetStream.Write(buffer, 0, read); } } } } } 

Use la función como:

 NetworkCredential credentials = new NetworkCredential("user", "mypassword"); string url = "ftp://ftp.example.com/directory/to/download/"; DownloadFtpDirectory(url, credentials, @"C:\target\directory"); 

Si desea evitar problemas al analizar los formatos de listado de directorios específicos del servidor, use una biblioteca de terceros que admita el comando MLSD y / o analice varios formatos de listas LIST ; y descargas recursivas.

Por ejemplo, con el ensamblado WinSCP .NET , puede descargar todo el directorio con una sola llamada a Session.GetFiles :

 // Setup session options SessionOptions sessionOptions = new SessionOptions { Protocol = Protocol.Ftp, HostName = "ftp.example.com", UserName = "user", Password = "mypassword", }; using (Session session = new Session()) { // Connect session.Open(sessionOptions); // Download files session.GetFiles("/directory/to/download/*", @"C:\target\directory\*").Check(); } 

Internamente, WinSCP usa el comando MLSD , si es compatible con el servidor. Si no, usa el comando LIST y admite docenas de formatos de listado diferentes.

El método Session.GetFiles es recursivo por defecto.

(Soy el autor de WinSCP)