Dieses Beispiel zeigt euch wie man ausschließlich mit Hilfe der .NET FTP Klassen einen rekursiven FTP Download realisieren kann.

 

Im Internet liest man zwar oft dass die .NET FTP Klassen nicht das Gelbe vom Ei sind ;-)
Allerdings lässt es sich, wie dieses Beispiel zeigt, sehr gut damit realisieren.

 

Lizenz

Diese Software unterliegt der GNU Lesser General Public License (LGPL).

 

Wie ist es dazu gekommen?

Grund dafür war dass ich für mich persönlich ein kleines FTP Backup Tool benötigte.
Außerdem sagte mir jemand der es bereits mit .NET versucht hatte das sowas nicht möglich wäre. Das war doch eine Kampfansage, oder?

 

Downloads und Links

Das vollständige Codesample (Visual Studio 2005 Projekt) könnt ihr hier herunterladen:
ChrischFtpRecursive.zip

 

Das Demoprogramm (ausführbare EXE, Konsolenanwendung) gibt es hier:
ChrischFtpRecursiveDemoApp.zip

 

Dieses Code Beispiel wird demnächst auch bei CodeProject veröffentlicht.

 

ToDo

·         Ladet man Dateien von einem Linux-Server herunter so kann es sein dass dort Dateien/Ordner vorhanden sind welche sich nur in der Groß-/ Kleinschreibung unterscheiden. Dies ist denke ich allerdings ein allgemeines Problem bei FTP Downloads unter Windows von einem Linux Server. Sollte hier jemand einen Lösungsvorschlag haben würde ich mich sehr darüber freuen!

 

Sourcecode

 

private static void ListFtpDir(string strHost, string strUser, string strPass,

                            string strDir, int iMaxTries, int iTimeout, out List<string> strDirs,

                            out List<string> strFiles)

{

    if (strDir.EndsWith("/"))

    {

        strDir = strDir.Substring(0, strDir.Length - 1);

    }

 

    strDirs = new List<string>();

    strFiles = new List<string>();

 

    //Try to get the directory listing for <iTry> times

    for (int iTry = 1; iTry <= iMaxTries; iTry++)

    {

        try

        {

            Console.WriteLine(string.Format("Reading directory \"{0}\" (Try #{1})", strDir, iTry));

 

            //Reset the content of the lists if previous tries have failed

            strDirs = new List<string>();

            strFiles = new List<string>();

 

            //Create the FTP request

            FtpWebRequest ftpReq = WebRequest.Create("ftp://" + strHost + strDir) as FtpWebRequest;

            //Setting KeepAlive true makes .NET using the same connection

            ftpReq.KeepAlive = true;

            //Get the directory listing instead of files

            ftpReq.Method = WebRequestMethods.Ftp.ListDirectoryDetails;

            //Set authentication

            ftpReq.Credentials = new NetworkCredential(strUser, strPass);

            //Set timeout (milliseconds).

            ftpReq.Timeout = iTimeout;

 

            FtpWebResponse ftpResp = ftpReq.GetResponse() as FtpWebResponse;

 

            Stream s = ftpResp.GetResponseStream();

            //I set encoding to "iso-8859-1" because i will get german umlauts in file and directory names

            //Feel free to change this to an appropriate encoding

            //Maybe someone know a way to get the correct encoding from the server?

            StreamReader sr = new StreamReader(s, Encoding.GetEncoding("iso-8859-1"));

 

            while (!sr.EndOfStream)

            {

                string strLine = sr.ReadLine();

 

                //Skip empty lines (should not happen)

                if (strLine.Trim().Length == 0)

                {

                    continue;

                }

 

                //If the line starts with character 'd' then it is a directory

                //(first part of the directory listing are the file/directory attributes)

                bool bIsDir = (strLine[0] == 'd');

 

                //Because we just need the file/directory name we remove all other informations

                //We start to parse the line from the beginning because a file/directory name

                //can contain trailing spaces

                //Feel free to parse some of these informations (the comments may help you)

 

                //Remove attributes

                strLine = strLine.Substring(10).TrimStart(null);

 

                //Remove number of sub elements

                strLine = strLine.Substring(strLine.IndexOf(' ')).TrimStart(null);

 

                //Remove owner

                strLine = strLine.Substring(strLine.IndexOf(' ')).TrimStart(null);

 

                //Remove group

                strLine = strLine.Substring(strLine.IndexOf(' ')).TrimStart(null);

 

                //Remove size

                strLine = strLine.Substring(strLine.IndexOf(' ')).TrimStart(null);

 

                //Remove month

                strLine = strLine.Substring(strLine.IndexOf(' ')).TrimStart(null);

 

                //Remove day

                strLine = strLine.Substring(strLine.IndexOf(' ')).TrimStart(null);

 

                //Remove year or time

                strLine = strLine.Substring(strLine.IndexOf(' ')).TrimStart(null);

 

                //The remaining line is the file/directory name

                string strFileName = strLine;

 

                //If the filename is a dot or double dot then skip this

                if (strFileName == "." || strFileName == "..")

                {

                    continue;

                }

 

                //Add file/directory to the list

                if (bIsDir)

                {

                    strDirs.Add(strFileName);

                }

                else

                {

                    strFiles.Add(strFileName);

                }

            }

 

            sr.Close();

            sr.Dispose();

            sr = null;

 

            s.Close();

            s.Dispose();

            s = null;

 

            //If KeepAlive is set to true the connection remains open anyway

            ftpResp.Close();

 

            strDirs.Sort();

            strFiles.Sort();

 

            break;

        }

        catch (Exception ex)

        {

            //If we have reached the maximum try count rethrow the exception

            if (iTry == iMaxTries)

            {

                throw ex;

            }

 

            Thread.Sleep(5000);

        }

    }

}

 

private static void DownloadFtpDirRecursive(string strHost, string strUser,

                                            string strPass, string strDir,

                                            string strLocalDir, int iMaxTries,

                                            int iTimeout)

{

    List<string> strDirs;

    List<string> strFiles;

 

    if (strDir.EndsWith("/"))

    {

        strDir = strDir.Substring(0, strDir.Length - 1);

    }

 

    //If the target directory doesn't exist create it

    if (!Directory.Exists(strLocalDir))

    {

        Directory.CreateDirectory(strLocalDir);

    }

 

    //Get files and directories in current directory

    ListFtpDir(strHost, strUser, strPass, strDir, iMaxTries, iTimeout, out strDirs, out strFiles);

 

    //Buffer for retrieving data

    byte[] byBuffer = new byte[4096];

    int iRead;

 

    foreach (string strFile in strFiles)

    {

        //Try to get the file for <iTry> times

        for (int iTry = 1; iTry <= iMaxTries; iTry++)

        {

            try

            {

                Console.WriteLine(string.Format("Downloading file \"{0}\" (Try #{1})", strDir + "/" + strFile, iTry));

 

                //Create the FTP request

                FtpWebRequest ftpReq = WebRequest.Create("ftp://" + strHost + strDir + "/" + strFile) as FtpWebRequest;

                //Setting KeepAlive true makes .NET using the same connection

                ftpReq.KeepAlive = true;

                //Get the file

                ftpReq.Method = WebRequestMethods.Ftp.DownloadFile;

                //Set authentication

                ftpReq.Credentials = new NetworkCredential(strUser, strPass);

                //Use binary transfer - it's the safest way

                ftpReq.UseBinary = true;

                //Set timeout (milliseconds).

                ftpReq.Timeout = iTimeout;

 

                FtpWebResponse ftpResp = ftpReq.GetResponse() as FtpWebResponse;

 

                //Create the target file

                FileStream fs = new FileStream(Path.Combine(strLocalDir, strFile), FileMode.Create, FileAccess.Write, FileShare.None);

 

                Stream s = ftpResp.GetResponseStream();

 

                //Read from the response stream and write it into the target file

                while (true)

                {

                    iRead = s.Read(byBuffer, 0, byBuffer.Length);

 

                    if (iRead < 1)

                    {

                        break;

                    }

 

                    fs.Write(byBuffer, 0, iRead);

                }

 

                s.Close();

                s.Dispose();

                s = null;

 

                fs.Close();

                fs.Dispose();

                fs = null;

 

                //If KeepAlive is set to true the connection remains open anyway

                ftpResp.Close();

 

                break;

            }

            catch (Exception ex)

            {

                //If we have reached the maximum try count rethrow the exception

                if (iTry == iMaxTries)

                {

                    throw ex;

                }

 

                Thread.Sleep(5000);

            }

        }

    }

 

    //Follow the subdirectories recursively

    foreach (string strSubDir in strDirs)

    {

        DownloadFtpDirRecursive(strHost, strUser, strPass, strDir + "/" + strSubDir, Path.Combine(strLocalDir, strSubDir), iMaxTries, iTimeout);

    }

}