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