2016-05-04 36 views
12

Informazioni generali
Sono ancora in fase di apprendimento C#. Per aiutare me stesso, sto cercando di creare un programma che sincronizzerà automaticamente tutti i miei progetti locali con una cartella sul mio server FTP. Questo perché sia ​​a scuola che a casa, ho sempre a disposizione gli stessi progetti.C# Scarica tutti i file e sottodirectory tramite FTP

So che ci sono programmi come Dropbox che già fanno questo per me, ma ho pensato di creare qualcosa di simile che mi insegnerà molto lungo la strada.

Il problema
Il mio primo passo verso il mio obiettivo era quello di scaricare solo tutti i file, sottodirectory e file correlati dal mio server FTP. Sono riuscito a scaricare tutti i file da una directory con il seguente codice. Tuttavia, il mio codice elenca solo i nomi delle cartelle e i file nella directory principale. Sottocartelle e sottofile non vengono mai restituite e non vengono mai scaricate. A parte questo, il server restituisce un errore 550 perché sto cercando di scaricare le cartelle come se fossero dei file. Sono stato su questo per 4+ ore, ma non riesco a trovare nulla su come risolvere questi problemi e farlo funzionare. Perciò spero che voi ragazzi mi saranno d'aiuto :)

Codice

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

risposta

19

Il FtpWebRequest non dispone di alcun supporto esplicito per le operazioni sui file ricorsive (compresi i download). È necessario implementare la ricorsione da soli:

  • Lista directory remota
  • Iterate le voci, il download di file e recursing in sottodirectory (messa in vendita di nuovo, ecc)

parte difficile è quello di identificare file da sottodirectory. Non c'è modo di farlo in modo portatile con lo FtpWebRequest. Il FtpWebRequest purtroppo non supporta il comando MLSD, che è l'unico modo portatile per recuperare l'elenco di directory con gli attributi di file nel protocollo FTP. Vedi anche Checking if object on FTP server is file or directory.

Le opzioni disponibili sono:

  • eseguire un'operazione su un nome di file che è certo di non riuscire per il file e riesce per le directory (o viceversa). Cioè puoi provare a scaricare il "nome". Se questo succede, è un file, se fallisce, è una directory.
  • Si può essere fortunati e nel tuo caso specifico, si può dire di un file da una directory con un nome di file (ad esempio tutti i file hanno l'estensione, mentre sottodirectory NON)
  • si utilizza un lungo elenco di directory (LIST comando = ListDirectoryDetails metodo) e tenta di analizzare un elenco specifico del server. Molti server FTP utilizzano l'elenco * nix-style, in cui si identifica una directory tramite lo d all'inizio della voce. Ma molti server usano un formato diverso.L'esempio seguente utilizza questo approccio (supponendo che il 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<string> lines = new List<string>(); 

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

Utilizzare la funzione come:

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

Se si vuole evitare problemi con l'analisi del server- formati di elenchi di directory specifici, utilizzare una libreria di terze parti che supporti il ​​comando MLSD e/o l'analisi di vari formati di elenchi LIST; e download ricorsivi.

Ad esempio, con WinSCP .NET assembly è possibile scaricare tutta la directory con una sola chiamata al 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 il comando MLSD, se supportato dal server. In caso contrario, utilizza il comando LIST e supporta dozzine di formati di elenchi diversi.

Il Session.GetFiles method è ricorsivo per impostazione predefinita.

(Io sono l'autore di WinSCP)

+2

ho cambiato la tua risposta alla risposta accettata come ho imparato molto da lui e per tutta l'Efford che hai messo in esso! Grazie ancora! – icecub

+0

Salve, sto cercando di usare la tua libreria ma ho bisogno di SSL/TLS sulla mia connessione. Sto facendo questo tramite richiesta standard FTP usando EnableSsl = true può essere impostato nelle opzioni della sessione? – Jay

+0

Sono stato in grado di collegarlo impostando FtpMode e FTPSecure rispettivamente a Passive ed Explicit, ottima libreria la caratteristica ricorsiva è eccellente, una domanda è una caratteristica che cattura i file che non esistono nella nuova cartella quindi essenzialmente afferrando solo il nuovo file che sono già stati copiati – Jay