C # Baixe todos os arquivos e subdiretórios através do FTP

Informações gerais
Eu ainda estou no processo de aprender C #. Para me ajudar, estou tentando criar um programa que sincronize automaticamente todos os meus projetos locais com uma pasta no meu servidor FTP. Isso para que, seja na escola ou em casa, eu sempre tenha os mesmos projetos disponíveis para mim.

Eu sei que existem programas como o Dropbox que já fazem isso para mim, mas imaginei que criar algo assim me ensinaria muito ao longo do caminho.

O problema
Meu primeiro passo em direção ao meu objective era apenas baixar todos os arquivos, subdiretórios e subarquivos do meu servidor FTP. Eu consegui baixar todos os arquivos de um diretório com o código abaixo. No entanto, meu código lista apenas os nomes das pastas e os arquivos no diretório principal. Subpastas e subarquivos nunca são retornados e nunca são baixados. Além disso, o servidor retorna um erro 550 porque estou tentando baixar as pastas como se fossem arquivos. Eu estive nisso por mais de 4 horas agora, mas eu não consigo encontrar nada sobre como corrigir esses problemas e fazê-lo funcionar. Por isso, estou esperando que vocês me ajudem 🙂

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"); } } 

O FtpWebRequest não possui nenhum suporte explícito para operações recursivas de arquivos (incluindo downloads). Você tem que implementar a recursion você mesmo:

  • Listar o diretório remoto
  • Iterar as inputs, baixar arquivos e recursar em subdiretórios (listá-los novamente, etc.)

Parte complicada é identificar arquivos de subdiretórios. Não há como fazer isso de maneira portátil com o FtpWebRequest . Infelizmente, o FtpWebRequest não suporta o comando MLSD , que é a única maneira portátil de recuperar listview de diretórios com atributos de arquivo no protocolo FTP. Consulte também Verificando se o object no servidor FTP é um arquivo ou diretório .

Suas opções são:

  • Faça uma operação em um nome de arquivo que certamente falhará no arquivo e será bem-sucedido nos diretórios (ou vice-versa). Ou seja, você pode tentar baixar o “nome”. Se isso for bem sucedido, é um arquivo, se isso falhar, é um diretório.
  • Você pode ter sorte e no seu caso específico, você pode dizer um arquivo de um diretório por um nome de arquivo (ou seja, todos os seus arquivos têm uma extensão, enquanto subdiretórios não)
  • Você usa uma longa listview de diretórios (comando LIST = método ListDirectoryDetails ) e tenta analisar uma listview específica do servidor. Muitos servidores FTP usam a listview de estilo * nix, onde você identifica um diretório pelo d no início da input. Mas muitos servidores usam um formato diferente. O exemplo a seguir usa essa abordagem (assumindo o 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 a function como:

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

Se você quiser evitar problemas ao analisar os formatos de listview de diretórios específicos do servidor, use uma biblioteca de terceiros que suporte o comando MLSD e / ou MLSD vários formatos de listview LIST ; e downloads recursivos.

Por exemplo, com o WinSCP .NET assembly você pode baixar o diretório inteiro com uma única chamada para o 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, o WinSCP usa o comando MLSD , se suportado pelo servidor. Se não, ele usa o comando LIST e suporta dezenas de diferentes formatos de listview.

O método Session.GetFiles é recursivo por padrão.

(Eu sou o autor do WinSCP)