web-dev-qa-db-fra.com

C # Télécharger tous les fichiers et sous-répertoires via FTP

Informations générales
Je suis toujours en train d'apprendre le C #. Pour m'aider, j'essaie de créer un programme qui synchronisera automatiquement tous mes projets locaux avec un dossier sur mon serveur FTP. Ceci afin que, que je sois à l'école ou à la maison, j'ai toujours les mêmes projets à ma disposition.

Je sais qu'il existe des programmes comme Dropbox qui le font déjà pour moi, mais j'ai pensé que créer quelque chose comme ça m'apprendrait beaucoup en cours de route.

Le problème
Ma première étape vers mon objectif était de simplement télécharger tous les fichiers, sous-répertoires et sous-fichiers de mon serveur FTP. J'ai réussi à télécharger tous les fichiers d'un répertoire avec le code ci-dessous. Cependant, mon code répertorie uniquement les noms de dossier et les fichiers dans le répertoire principal. Les sous-dossiers et sous-fichiers ne sont jamais retournés et jamais téléchargés. En dehors de cela, le serveur renvoie une erreur 550 car j'essaie de télécharger les dossiers comme s'il s'agissait de fichiers. Je travaille depuis plus de 4 heures maintenant, mais je ne trouve rien sur la façon de résoudre ces problèmes et de le faire fonctionner. J'espère donc que vous m'aiderez :)

Code

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

FtpWebRequest ne prend pas explicitement en charge les opérations de fichiers récursifs (y compris les téléchargements). Vous devez implémenter la récursivité vous-même:

  • Liste le répertoire distant
  • Itérer les entrées, télécharger des fichiers et revenir dans des sous-répertoires (les répertorier à nouveau, etc.)

La partie délicate consiste à identifier les fichiers des sous-répertoires. Il n'y a aucun moyen de le faire de manière portable avec le FtpWebRequest. La FtpWebRequest ne prend malheureusement pas en charge la commande MLSD, qui est le seul moyen portable de récupérer la liste des répertoires avec des attributs de fichier dans le protocole FTP. Voir aussi Vérification si l'objet sur le serveur FTP est un fichier ou un répertoire .

Vos options sont:

  • Effectuez une opération sur un nom de fichier qui échouera certainement pour le fichier et réussira pour les répertoires (ou vice versa). C'est à dire. vous pouvez essayer de télécharger le "nom". Si cela réussit, c'est un fichier, si cela échoue, c'est un répertoire.
  • Vous avez peut-être de la chance et dans votre cas particulier, vous pouvez distinguer un fichier d'un répertoire par un nom de fichier (c'est-à-dire que tous vos fichiers ont une extension, tandis que les sous-répertoires n'en ont pas)
  • Vous utilisez une longue liste de répertoires (LIST command = ListDirectoryDetails méthode) et essayez d'analyser une liste spécifique au serveur. De nombreux serveurs FTP utilisent la liste de style * nix, où vous identifiez un répertoire par le d au tout début de l'entrée. Mais de nombreux serveurs utilisent un format différent. L'exemple suivant utilise cette approche (en supposant le format * 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);
                }
            }
        }
    }
}

Utilisez la fonction comme:

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

Si vous souhaitez éviter les problèmes liés à l'analyse des formats de liste de répertoires spécifiques au serveur, utilisez une bibliothèque tierce qui prend en charge la commande MLSD et/ou l'analyse de divers formats de liste LIST; et téléchargements récursifs.

Par exemple avec WinSCP .NET Assembly vous pouvez télécharger le répertoire entier avec un seul appel à 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();
}

En interne, WinSCP utilise la commande MLSD, si elle est prise en charge par le serveur. Sinon, il utilise la commande LIST et prend en charge des dizaines de formats de liste différents.

Le Session.GetFiles méthode est récursif par défaut.

(je suis l'auteur de WinSCP)

32
Martin Prikryl