[C#] Pobieranie listy katalogów z serwera FTP

@Fiołek

Coś musiałeś źle zaimplementować. FtpWebRequest nie zachowuje informacji o stanie, a więc za każdym razem (tzn. przy okazji wysyłania każdego polecenia) tworzone jest nowe połączenie z serwerem (co trwa stosunkowo długo). Implementując własne rozwiązanie możesz tworzyć jedno połączenie i w obrębie tego połączenia wysyłać polecenie CWD, służące do zmiany aktywnego katalogu (o czym zresztą już pisałem), co powinno być (choć faktycznie może się okazać, że wcale nie jest ) szybsze. Nie zauważysz jednak znaczących różnic na tak niedużej strukturze katalogów, na której testowałeś. Spróbuj pobrać strukturę katalogów z np. tego serwera ftp://ftp.mozilla.org/. Choć oczywiście to już teraz tylko rozważania teoretyczne, bo wydaje mi się, że zaprezentowane przeze mnie powyżej rozwiązanie jest wystarczające (jeśli się mylę to oczywiście napisz).

@eureka 170

Zmieniłem trochę kod kontrolki, tzn. udostępniłem kilka dodatkowych właściwości na zewnątrz (nic poza tym). Udało Ci się uruchomić całość?

FTPTreeView.cs

using System;

using System.Windows.Forms;

using System.ComponentModel;

using FTPDemo.Core;


namespace FTPDemo.UI

{

    [DefaultProperty("UserName")]

    public class FTPTreeView : TreeView

    {

        private const string _CATEGORY_NAME = "FTP Settings";

        private const string _DEFAULT_USER_NAME = "";

        private const string _DEFAULT_PASSWORD = "";

        private const bool _DEFAULT_KEEP_ALIVE = true;

        private const bool _DEFAULT_USE_PASSIVE = true;

        private const bool _DEFAULT_ENABLE_SSL = false;

        private const int _DEFAULT_TIMEOUT = System.Threading.Timeout.Infinite;


        private FTPClient client;

        private bool branchOnly;


        public FTPTreeView()

            : base()

        {

            client = new FTPClient(_DEFAULT_USER_NAME, _DEFAULT_PASSWORD);

            branchOnly = false;

        }


        [Bindable(true)]

        [Category(_CATEGORY_NAME)]

        [Description("")]

        [DefaultValue(_DEFAULT_USER_NAME)]

        [Localizable(true)]

        public string UserName

        {

            get { return client.UserName; }

            set { client.UserName = value; }

        }


        [Bindable(true)]

        [Category(_CATEGORY_NAME)]

        [Description("")]

        [DefaultValue(_DEFAULT_PASSWORD)]

        [Localizable(true)]

        public string Password

        {

            get { return client.Password; }

            set { client.Password = value; }

        }


        [Bindable(true)]

        [Category(_CATEGORY_NAME)]

        [Description("")]

        [DefaultValue(_DEFAULT_KEEP_ALIVE)]

        [Localizable(true)]

        public bool KeepAlive 

        {

            get { return client.KeepAlive; }

            set { client.KeepAlive = value; } 

        }


        [Bindable(true)]

        [Category(_CATEGORY_NAME)]

        [Description("")]

        [DefaultValue(_DEFAULT_USE_PASSIVE)]

        [Localizable(true)]

        public bool UsePassive

        {

            get { return client.UsePassive; }

            set { client.UsePassive = value; }

        }


        [Bindable(true)]

        [Category(_CATEGORY_NAME)]

        [Description("")]

        [DefaultValue(_DEFAULT_ENABLE_SSL)]

        [Localizable(true)]

        public bool EnableSsl

        {

            get { return client.EnableSsl; }

            set { client.EnableSsl = value; }

        }


        [Bindable(true)]

        [Category(_CATEGORY_NAME)]

        [Description("")]

        [DefaultValue(_DEFAULT_TIMEOUT)]

        [Localizable(true)]

        public int Timeout

        {

            get { return client.Timeout; }

            set { client.Timeout = value; }

        }


        protected override void OnBeforeExpand(TreeViewCancelEventArgs e)

        {

            FTPTreeNode node = (FTPTreeNode)e.Node;

            if (node != null && !node.ChildrenLoaded)

            {

                node.Nodes.Clear();

                Load(node, node.Element.Url);

                node.ChildrenLoaded = true;

            }


            base.OnBeforeExpand(e);

        }


        public void Clear()

        {

            Nodes.Clear();

        }


        public void Load(string url, bool branchOnly)

        {

            this.branchOnly = branchOnly;

            Load(null, url);

        }


        private void Load(TreeNode parentNode, string url)

        {

            FTPElement[] list = client.GetDirectoryDetailsList(url);

            if (list != null)

            {

                foreach (FTPElement element in list)

                {

                    FTPTreeNode node = new FTPTreeNode(element);

                    if (parentNode != null)

                        parentNode.Nodes.Add(node);

                    else

                        Nodes.Add(node);


                    if (element.IsDirectory)

                    {

                        if (!branchOnly)

                        {

                            Load(node, node.Element.Url);

                            node.ChildrenLoaded = true;

                        }

                        else

                        {

                            FTPTreeNode emptyNode = new FTPTreeNode();

                            node.Nodes.Add(emptyNode);

                            node.ChildrenLoaded = false;

                        }

                    }

                }

            }

        }

    }

}

Wszystko jest dobrze(dla testów) zaimplementowane, naprawdę :wink: FtpWebRequest, jeśli ustawimy KeepAlive na true, ZACHOWUJE połączenie z serwerem, nawet gdy zmieniamy Uri(tworząc nowe FtpWebRequest). Niestety, nie ma dostępu do kodów źródłowych System.Net, więc nie można tego prosto sprawdzić. Polecam jednak dość prosty test - dwa polecenia przez FtpWebRequest - raz wysyłamy je z KeepAlive=true, potem z false i sprawdzamy w logach co się działo.

EDIT:

@down: tak, wiem, że można to zrobić bez dostępu do oficjalnego kodu źródłowego, lecz mimo wszystko wydaje mi się, że sposób z logami jest prostszy :wink:

@Fiołek

Zasadniczo nie potrzeba dostępu do kodu źródłowego System.Net, żeby to sprawdzić. Ale masz rację co do tego że, połączenie z serwerem FTP w sytuacji ustawienia KeepAlive na true jest utrzymywane (przepraszam za błąd). IMO opis odnośnie tej właściwości w dokumentacji msdn jest nie dość szczegółowy. Wracając do sedna sprawy … skoro połączenie jest utrzymywane, to nie ma potrzeby wykonywania własnej implementacji. Wzrost wydajności w porównaniu do rozwiązania opartego o FtpWebRequest mógł być widoczny właśnie tylko dzięki temu, że utrzymywane by było stałe połączenie z serwerem i można by było wykonywać polecenia CWD, ale jak widać jest to już robione za nas.

Oto krótki log dla potomnych z KeepAlive ustawionym na true (to był dobry pomysł z analizą tych logów):

System.Net Information: 0 : [4988] FtpWebRequest#7746814::.ctor(ftp://127.0.0.1/)

System.Net Information: 0 : [4988] FtpWebRequest#7746814:GetResponse(Metoda=LIST.)

System.Net Information: 0 : [4988] Bieżącym typem instalacji systemu operacyjnego jest „Client”.

System.Net Information: 0 : [4988] Serwer RAS obsługiwany: True

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Utworzono połączenie od 127.0.0.1:6383 do 127.0.0.1:21.

System.Net Information: 0 : [4988] Associating FtpWebRequest#7746814 with FtpControlStream#63840421

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [220 Microsoft FTP Service]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [USER anonymous]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [331 Anonymous access allowed, send identity (e-mail name) as password.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [PASS ********]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [230 User logged in.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [OPTS utf8 on]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [200 OPTS UTF8 command successful - UTF8 encoding now ON.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [PWD]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [257 "/" is current directory.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [TYPE I]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [200 Type set to I.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [PASV]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [227 Entering Passive Mode (127,0,0,1,24,240).]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia 
[list]
System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [125 Data connection already open; Transfer starting.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [226 Transfer complete.]

System.Net Information: 0 : [4988] FtpWebRequest#7746814::(Zwalnianie połączenia FTP #63840421.)

System.Net Information: 0 : [4988] FtpWebRequest#25181126::.ctor(ftp://127.0.0.1/sól)

System.Net Information: 0 : [4988] FtpWebRequest#25181126::GetResponse(Metoda=LIST.)

System.Net Information: 0 : [4988] Associating FtpWebRequest#25181126 with FtpControlStream#63840421

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [PASV]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [227 Entering Passive Mode (127,0,0,1,24,242).]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [LIST sól]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [125 Data connection already open; Transfer starting.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [226 Transfer complete.]

System.Net Information: 0 : [4988] FtpWebRequest#25181126::(Zwalnianie połączenia FTP #63840421.)

System.Net Information: 0 : [4988] FtpWebRequest#56152722::.ctor(ftp://127.0.0.1/ffdfd)

System.Net Information: 0 : [4988] FtpWebRequest#56152722::GetResponse(Metoda=LIST.)

System.Net Information: 0 : [4988] Associating FtpWebRequest#56152722 with FtpControlStream#63840421

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [PASV]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [227 Entering Passive Mode (127,0,0,1,24,244).]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [LIST ffdfd]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [125 Data connection already open; Transfer starting.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [226 Transfer complete.]

System.Net Information: 0 : [4988] FtpWebRequest#56152722::(Zwalnianie połączenia FTP #63840421.)

System.Net Information: 0 : [4988] FtpWebRequest#26847985::.ctor(ftp://127.0.0.1/ffdfd/fefefe)

System.Net Information: 0 : [4988] FtpWebRequest#26847985::GetResponse(Metoda=LIST.)

System.Net Information: 0 : [4988] Associating FtpWebRequest#26847985 with FtpControlStream#63840421

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [CWD ffdfd]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [250 CWD command successful.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [PASV]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [227 Entering Passive Mode (127,0,0,1,24,246).]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [LIST fefefe]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [125 Data connection already open; Transfer starting.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [226 Transfer complete.]

System.Net Information: 0 : [4988] FtpWebRequest#26847985::(Zwalnianie połączenia FTP #63840421.)

System.Net Information: 0 : [4988] FtpWebRequest#8990007::.ctor(ftp://127.0.0.1/ffdfd/fefefe/testowy)

System.Net Information: 0 : [4988] FtpWebRequest#8990007::GetResponse(Metoda=LIST.)

System.Net Information: 0 : [4988] Associating FtpWebRequest#8990007 with FtpControlStream#63840421

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [CWD /]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [250 CWD command successful.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [CWD ffdfd/fefefe]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [250 CWD command successful.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [PASV]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [227 Entering Passive Mode (127,0,0,1,24,248).]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Wysyłanie polecenia [LIST testowy]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [125 Data connection already open; Transfer starting.]

System.Net Information: 0 : [4988] FtpControlStream#63840421 - Odebrano odpowiedź [226 Transfer complete.]

System.Net Information: 0 : [4988] FtpWebRequest#8990007::(Zwalnianie połączenia FTP #63840421.)

W sytuacji ustawienia KeepAlive na false połączenie jest zamykane za pomocą polecenia QUIT w momencie, gdy wywołania zostanie metoda Close klasy FtpWebResponse.

Wiedziałem o tym. Sprawdziłem wydajność curl vs WebClient i różnica była zależna jedynie od fluktuacji przepustowości sieci w momencie uruchamiania testu. :wink: Dlatego tak parłem na ten temat.

@Ryan

Przypuszczenia Fiołka okazały się nieprawdziwe, bo założenia od początku były błędne (z czego sam nie zdawałem sobie sprawy).

Niestety nie mogłem uruchomić tego programu: Oto błędy: http://imageshack.us/photo/my-images/43 … d01qs.png/

Tak jest kiedy usunę “Using Ude”, ktore znajduje się w FTPClient. A kiedy go nie usuwam mam błąd:

Error 1 The type or namespace name ‘Ude’ could not be found (are you missing a using directive or an assembly reference?)

Nie no usuwać tego nie możesz :slight_smile: Nie bez powodu dodałem tą przestrzeń nazw. Pisałem, żebyś ściągnął sobie tamten projekt ze strony google i skompilował. Anyway tu masz tą bibliotekę http://www.speedyshare.com/files/29488958/Ude.dll. Dodaj ją do referencji projektu, a kod zostaw tak jak był.

Hmm… ściągnąłem tą bibliotekę, wkleiłem kod i wyświetla mi się błąd:

Error 1 ‘System.IO.Stream’ does not contain a definition for ‘CopyTo’ and no extension method ‘CopyTo’ accepting a first argument of type ‘System.IO.Stream’ could be found (are you missing a using directive or an assembly reference?)

Dotyczy to klasy FTPClient.cs. Poza tym w Form1.cs musiałem uworzyć obiekt typu FTPTreeView, bo w Twoim kodzie nie wiadomo skąd bierze się ftpTreeView i kompilator przez to też zwraca błąd

Miałeś sobie wrzucić na formę kontrolkę FTPTreeView zamiast TreeView. Po wrzuceniu na formę zmień jej nazwę (właściwość Name) na ftpTreeView i będziesz to miał. Ta kontrolka FTPTreeView powinna się pojawić w Toolbox-ie (na samej górze) w sekcji Components.

Co do tego komunikatu błędu … Projekt kompilujesz przy użyciu .NET Framework 4? A jeśli nie, to czy możesz zmienić na .NET Framework 4?

Niestety nie mam, bo mam Visual Studio 2008, a tam jest .NET Framework 3.5. Jak na razie nie mam Visual Studio 2010.

W porządku, to zamień kod tej metody na ten poniższy i zobacz, czy całość ruszy

private Encoding DetectEncoding(Stream stream, out byte[] bytes, out float confidence)

{

    byte[] buffer = new byte[16 * 1024];

    using (MemoryStream memoryStream = new MemoryStream())

    {

        int bytesRead = 0;

        while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)

            memoryStream.Write(buffer, 0, bytesRead);

        bytes = memoryStream.ToArray();

    }


    return DetectEncoding(bytes, out confidence);

}

Ok, udało mi się uruchomić, jest jeszcze szybciej, teraz pobiera mi to w czasie poniżej 10s.

Mam jeszcze problem… jak napisać funkcję podwójnego kliknięcia na TreeView - to jeszcze dotyczy mojego starego programu

Znalazłem stronę http://www.dotnetperls.com/treeview . Próbuję to zrobić to tak jak oni, ale “To add the MouseDoubleClick event handler, right-click the TreeView in the designer and select Properties. Then, select “MouseDoubleClick” and click twice on that entry”. Gdzie w Properties jest opcja MouseDoubleClick, bo ja tego nie widzę?

15rij42.png

To zainstaluj 4.0: http://www.dobreprogramy.pl/NET-Framewo … 17635.html

@somekind

A czy w Visual Studio < 2010 można kompilować projekty przy użyciu .NET Framework 4? (wydaje mi się, że nie można, ale mogę się mylić)