Список папок и подпапок сервера с помощью C# FtpWebRequest

Я пытаюсь получить полный список файлов, каталогов и подкаталогов в виде дерева, используя StreamReader. Проблема в том, что это заняло слишком много времени и выдает * «Исключение времени ожидания операции» и показывает только один уровень.

Вот мой код

public void getServerSubfolder(TreeNode tv, string parentNode) {
  string ptNode;
  List<string> files = new List<string> ();
  try { 
    FtpWebRequest request = (FtpWebRequest) WebRequest.
    Create(parentNode);
    request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;

    request.Credentials = new NetworkCredential(this.userName, this.Password);
    request.UseBinary = true;
    request.UsePassive = true;
    request.Timeout = 10000;
    request.ReadWriteTimeout = 10000;
    request.KeepAlive = false;
    FtpWebResponse response = (FtpWebResponse) request.GetResponse();
    Stream responseStream = response.GetResponseStream();
    StreamReader reader = new StreamReader(responseStream);
    string fileList;
    string[] fileName;
    //MessageBox.Show(reader.ReadToEnd().ToString());
    while (!reader.EndOfStream) {
      fileList = reader.ReadLine();
      fileName = fileList.Split(' ');
      if (fileName[0] == "drwxr-xr-x") {
        // if it is directory
        TreeNode tnS = new TreeNode(fileName[fileName.Length - 1]);
        tv.Nodes.Add(tnS);
        ptNode = parentNode + "/" + fileName[fileName.Length - 1] + "/";
        getServerSubfolder(tnS, ptNode);
      } else files.Add(fileName[fileName.Length - 1]);
    }
    reader.Close();
    response.Close();
  } catch (Exception ex) {
    MessageBox.Show("Sub--here " + ex.Message + "----" + ex.StackTrace);
  }
}

person Habtamu    schedule 09.04.2013    source источник
comment
Я пробовал это с разными номерами тайм-аута.   -  person Habtamu    schedule 09.04.2013


Ответы (2)


Я делаю похожие вещи, но вместо того, чтобы использовать StreamReader.ReadLine() для каждого, я получаю все сразу с помощью StreamReader.ReadToEnd(). Вам не нужен ReadLine(), чтобы просто получить список каталогов. Ниже приведен весь мой код (все инструкции объясняются в этого руководства):

        FtpWebRequest request=(FtpWebRequest)FtpWebRequest.Create(path);
        request.Method=WebRequestMethods.Ftp.ListDirectoryDetails;
        List<ftpinfo> files=new List<ftpinfo>();

        //request.Proxy = System.Net.WebProxy.GetDefaultProxy();
        //request.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
        request.Credentials = new NetworkCredential(_username, _password);
        Stream rs=(Stream)request.GetResponse().GetResponseStream();

        OnStatusChange("CONNECTED: " + path, 0, 0);

        StreamReader sr = new StreamReader(rs);
        string strList = sr.ReadToEnd();
        string[] lines=null;

        if (strList.Contains("\r\n"))
        {
            lines=strList.Split(new string[] {"\r\n"},StringSplitOptions.None);
        }
        else if (strList.Contains("\n"))
        {
            lines=strList.Split(new string[] {"\n"},StringSplitOptions.None);
        }

        //now decode this string array

        if (lines==null || lines.Length == 0)
            return null;

        foreach(string line in lines)
        {
            if (line.Length==0)
                continue;
            //parse line
            Match m= GetMatchingRegex(line);
            if (m==null) {
                //failed
                throw new ApplicationException("Unable to parse line: " + line);
            }

            ftpinfo item=new ftpinfo();
            item.filename = m.Groups["name"].Value.Trim('\r');
            item.path = path;
            item.size = Convert.ToInt64(m.Groups["size"].Value);
            item.permission = m.Groups["permission"].Value;
            string _dir = m.Groups["dir"].Value;
            if(_dir.Length>0  && _dir != "-")
            {
                item.fileType = directoryEntryTypes.directory;
            } 
            else
            {
                item.fileType = directoryEntryTypes.file;
            }

            try
            {
                item.fileDateTime = DateTime.Parse(m.Groups["timestamp"].Value);
            }
            catch
            {
                item.fileDateTime = DateTime.MinValue; //null;
            }

            files.Add(item);
        }

        return files;
person Prahlad Yeri    schedule 09.04.2013
comment
Большое спасибо за отличный ответ. Сработало, когда я прочитал его один раз до конца. Мне не нужно использовать весь код, так как я думал, что мне нужно включить библиотеку. Спасибо еще раз. - person Habtamu; 10.04.2013

Вы должны прочитать (и кэшировать) весь список перед рекурсией в подкаталоги, иначе запрос верхнего уровня истечет по тайм-ауту до того, как вы завершите список подкаталогов.

Вы можете продолжать использовать ReadLine, не нужно использовать ReadToEnd и самостоятельно разделять строки.

void ListFtpDirectory(
    string url, string rootPath, NetworkCredential credentials, List<string> list)
{
    FtpWebRequest listRequest = (FtpWebRequest)WebRequest.Create(url + rootPath);
    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 filePath = rootPath + name;

        if (permissions[0] == 'd')
        {
            ListFtpDirectory(url, filePath + "/", credentials, list);
        }
        else
        {
            list.Add(filePath);
        }
    }
}

Используйте функцию, например:

List<string> list = new List<string>();
NetworkCredential credentials = new NetworkCredential("user", "mypassword");
string url = "ftp://ftp.example.com/";
ListFtpDirectory(url, "", credentials, list);

Недостаток описанного выше подхода заключается в том, что для получения информации о файлах и папках необходимо анализировать листинг, относящийся к конкретному серверу. Приведенный выше код предполагает обычный листинг в стиле *nix. Но многие серверы используют другой формат.

К сожалению, FtpWebRequest не поддерживает команду MLSD, которая является единственным переносимым способом получения списка каталогов с атрибутами файлов в протоколе FTP.


Если вы хотите избежать проблем с синтаксическим анализом форматов списка каталогов для конкретного сервера, используйте стороннюю библиотеку, которая поддерживает команду MLSD и/или синтаксический анализ различных форматов списка LIST; и рекурсивные загрузки.

Например, с помощью сборки WinSCP .NET вы можете перечислить весь каталог одним вызовом Session.EnumerateRemoteFiles:

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

    // List files
    IEnumerable<string> list =
        session.EnumerateRemoteFiles("/", null, EnumerationOptions.AllDirectories).
        Select(fileInfo => fileInfo.FullName);
}

Внутри WinSCP использует команду MLSD, если она поддерживается сервером. Если нет, он использует команду LIST и поддерживает десятки различных форматов листинга.

(я автор WinSCP)

person Martin Prikryl    schedule 30.11.2017