[C#] FTP Ordner Asynchron downloaden / runterladen (rekursiv)

Drucken
( 6 Votes )
Hauptkategorie: Programmieren Kategorie: C#
Erstellt am 20.02.2012 Zuletzt aktualisiert am 23.04.2012 Geschrieben von Jonny132
In diesem Tutorial zeige ich euch wie man einen FTP Ordner asynchron runterlädt und dies rekursiv.
Dazu benützen wir die FtpWebRequest-Klasse, WebClient-Klasse und diverse Hilfen aus dem System-IO Namespace für Dateiaktionen / hilfen.
Zur besseren lesbarkeit des Codes, solltet ihr deshalb 2 Usingseinbinden:
using System.IO;
using System.Net;
Erstellt zunächst eine neue Windows-Forms Anwendung auf dieser ihr folgende Elemente erstellt:
Label (Name: lblStatus; Text: "")
ProgressBar (Name: pbDownloadProgress)
Button (Name: bnStartDownload; Text: Starte Download)

Wechselt nun in die Codeansicht.

Um die korrekte Speicherung des Ordners und der darin befindlichen Dateien zu gewährleisten, erstellen wir zunächst eine Hilfsklasse namens FtpData
    private class FtpData
    {
      public string RequestUriString;
      public string FileName;
      public DirectoryInfo Directory;
      public int DownloadAttemps;

      public FtpData(string requestUriString, string fileName, DirectoryInfo directory)
      {
        RequestUriString = requestUriString;
        FileName = fileName;
        Directory = directory;
        DownloadAttemps = 0;
      }
    }
Diese Hilfsklasse bietet uns die Möglichkeit die RequestUrl, den Dateinamen, das Verzeichnis und die aktuellen Downloadversuche zwischenzuspeichern.
Zudem erstellen wir zur Vereinfachung einen Konstruktor dem wir die Werte direkt übergeben können.

Der nächste und Wahrscheinlich auch aufwändigste Schritt ist eine Hilfsfunktion in der wir rekursiv über alle Ordner und Dateien auf dem FTP-Server iterieren und sie in einer Liste zwischenspeichern bzw. die Ordner anlegen. Sinngemäß benennen wir die Funktion GetFilesAndDirectioriesToDownlaod().
Dieser übergeben wir als Parameter wiederrum die RequestUrl, das aktuelle Verzeichnis in dem wir uns befinden und eine Boolsche Variable ob rekursiv durchsucht werden soll.

    private List<FtpData> GetFilesAndDirectoriesToDownload(string requestUriString, DirectoryInfo workingDirectory, bool recursive)
    {
      List<FtpData> files = new List<FtpData>();
      List<FtpData> directories = new List<FtpData>();
      try
      {
        FtpWebRequest req = (FtpWebRequest)FtpWebRequest.Create(requestUriString);

        req.Proxy = new WebProxy();
        req.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
        req.KeepAlive = false;

        using (FtpWebResponse response = (FtpWebResponse)req.GetResponse())
        {
          using (Stream dataStream = response.GetResponseStream())
          {
            using (StreamReader reader = new StreamReader(dataStream))
            {
              while (!reader.EndOfStream)
              {
                string entry = reader.ReadLine();
                if (entry.StartsWith("d"))
                {
                  string directoryName = GetEntryName(entry);
                  if (directoryName != "." && directoryName != ".." && recursive)
                  {
                    DirectoryInfo subDir = Directory.CreateDirectory(workingDirectory.FullName + "\\" + directoryName);
                    directories.Add(new FtpData(requestUriString + "/" + directoryName, "", subDir));
                  }
                }
                else
                {
                  files.Add(new FtpData(requestUriString + "/" + GetEntryName(entry), GetEntryName(entry), workingDirectory));
                }
              }

            }
          }
        }

        foreach (FtpData dir in directories)
        {
          files.AddRange(GetFilesAndDirectoriesToDownload(dir.RequestUriString, dir.Directory, true));
        }
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.ToString());
      }
      return files;
    }

Zunächst initialisieren wir Listen zur zwischenspeicherung der Dateien und Ordner auf dem FTP-Server.
Alles übrige in der Funktion packen wir in einen Try-Catch Block. Nun kommen wir zum eigentlichen Code der Funktion.
Zuerst wird der Request (die Anfrage) an den FTP-Server erstellt. Als Methode geben wir ihr ListDirectoryDetails, somit bekommen wir alle nötigen Infos. Desweiteren Stellen wir KeppAlive auf false, damit der Request nach abarbeiten direkt geschlossen wird. Dies brauchen wir, weil die meisten FTP's die Anzahl an Connections limitiert.
Für die die Klassen FtpWebResponse, Stream und StreamReader verwenden wir jeweils ein Using Statement, damit wir uns nicht um das Aufräumen kümmern müssen.
Danach iterieren wir über die Antwort vom FTP-Server. Wir lesen in jedem Durchlauf die aktuelle Zeile aus. Eine Zeile könnte wie folgt aussehen: "drwxr-xr-x   4 ftpusr ftpgroup     4096 Jan 18 16:30 sampledata". Zu anfang etwas kryptisch zugegeben. Für uns sind jedoch nur 2 Details wichtig. Das erste Detail ist der erste Buchstabe dieser Zeile. Ist es ein "d" so ist der Eintrag ein Verzeichnis, andernfalls eine Datei. Das zweite Detail das für uns wichtig ist, ist die Position des Doppelpunkts, da wir von diesem aus das nächste Lehrzeichen Suchen müssen. Somit haben wir den Datei -/ Ordnernamen. Vielleicht denkt ihr euch wieso nicht gleich nach dem letzten Lehrzeichen suchen. Das würde nur funktionieren wenn es keine Dateien mit Lehrzeichen gibt. Da es diese jedoch geben kann ABER keine mit einem Doppelpunkt, suchen wir nach dem Doppelpunkt. Und genau für diese Suche erstellen wir uns eine kleine Funktion die uns den reinen Datei -/ Ordnernamen zurückgibt
    private string GetEntryName(string entry)
    {
      string result = entry.Substring(entry.LastIndexOf(":"));
      result = result.Substring(result.IndexOf(" ") + 1);
      return result;
    }

Zurück zum Code der Hilfsfunktion GetFilesAndDirectoriesToDownload().
Ist der Eintrag also ein Ordner, erstellen wir diesen per CreateDirectory() lokal und fügen ihn der directories-Liste hinzu. Zudem schliessen wir die Einträge "." und ".." aus, da diese keine 'echten' Ordner sind.
Wenn der Eintrag eine Datei ist, fügen wir diesen wiederum der files-Liste hinzu.

Am Ende der Funktion iterieren wir nun über alle gefundenen Ordner und rufen die selbe Funktion nochmals auf. Somit haben wir eine Rekursivität. Falls ihr euch fragt wieso das nicht direkt nach dem If-Statement gemacht wird, ist die Antwort wiederrum: Weil die FTP-Server meistens die Anzahl der Connections begrenzen. Indem wir es an den Schluss setzen und nach allen Using-Statements, stellen wir sicher, dass der vorige Request bereits geschlossen wurde.
Nach dem Catch Block geben wir noch die Liste der Dateien zurück. In dieser Liste sind nun alle Dateien die sich in dem Ordner + Unterordner (falls rekursiv-Flag gesetzt) befinden.

Somit hätten wir nun alle Informationen um mit dem runterladen der Dateien zu beginnen. Hierfür habe ich eine weitere Hilfsfunktionerstellt:
    private void DownloadFileAsynchron(FtpData file)
    {
      using (WebClient wc = new WebClient())
      {
        wc.Proxy = new WebProxy();
        wc.DownloadDataCompleted += new DownloadDataCompletedEventHandler(wc_DownloadDataCompleted);
        file.DownloadAttemps += 1;
        wc.DownloadDataAsync(new Uri(file.RequestUriString), file);
      }
    }
Dieser übergeben wir eine instanz unserer Hilfsklasse und erstellen einen WebClient in dem wir das DownloadDataCompleted-Ereignis abbonieren, die Anzahl der versuchten Downloads der Datei um 1 erhöhen und den Asynchronen download der Datei starten.

Dies bringt uns nun zum zuvor registrierten DownloadDataComplete-Ereignis:
    void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
    {
      FtpData file = (FtpData)e.UserState;
      if (e.Error == null)
      {
        try
        {
          File.WriteAllBytes(file.Directory.FullName + @"\" + file.FileName, e.Result);
          pbDownloadProgress.Value += 1;
        }
        catch (Exception ex)
        {
          MessageBox.Show(ex.ToString());
        }
      }
      else
      {
        if (file.DownloadAttemps <= 3)
          DownloadFileAsynchron(file);
        else
          MessageBox.Show("File " + file.FileName + " konnte nicht heruntergeladen werden.");
      }
    }
Da wir dem Asynchronen Download als Userstate die Dateiinformationen übergeben haben, können wir diese auch wieder herausholen. Falls das Event keinen Error geworfen hat (null), schreiben wir die Datei mittels WriteAllBytes auf die lokale Platte und erhöhen den Wert der ProgressBar um 1. Falls die Datei nicht korrekt runtergeladen werden konnte (aufgrund zuvieler Connections o.ä.), versuchen wir es einfach nocheinmal. Kann die Datei mehr als 3 mal nicht Runtergeladen werden, geben wir eine Fehlermeldung aus.

Kommen wir nun zur Zielgerade. Somit haben wir eigentlich alles was wir brauchen um einen kompletten FTP-Ordner rekursiv runterzuladen. Fehlt nurnoch der Start des Downloadprozesses. Dieser befindet sich im Klick-Ereignisdes Buttons: 
    private void bnStartDownload_Click(object sender, EventArgs e)
    {
      lblStatus.Text = "Hole Dateien von FTP";
      Application.DoEvents();
      List<FtpData> filesToDownload = GetFilesAndDirectoriesToDownload("ftp://user:password@ftpserver/dir1", new DirectoryInfo(@"C:\temp\ftptest\"), true);
      lblStatus.Text = "Hole Dateien von FTP fertig";
      Application.DoEvents();
      System.Threading.Thread.Sleep(2000);
      lblStatus.Text = "Starte Asynchroner Download von " + filesToDownload.Count.ToString() + " Dateien";
      pbDownloadProgress.Maximum = filesToDownload.Count;
      pbDownloadProgress.Value = 0;

      foreach (FtpData file in filesToDownload)
      {
        DownloadFileAsynchron(file);
      }
    }
Bei Klick auf diesen Button setzen wir zuerst das Status-Label und holen dann die Dateien vom FTP-Server. Das DoEvents brauchen wir, weil das lesen der Dateien NICHT asynchron ist und somit der Text des Labels nicht upgedatet werden würde. Danach setzen wir das Maximum der Progressbar auf die Anzahl an Dateien die runtergeladen werden sollen und den aktuellen Wert auf 0.
Danach iterieren wir nurnoch über alle Dateien und starten den Asynchronen Download für die Datei.



Hier das ganze Projekt zum Download

Dieses Tutorial könnte ebenfalls interessant für Sie sein: [C#] Ordner per FTP uploaden / hochladen

    Veröffentlichen Sie ihre Kommentare ...