Minuta to w sumie wciąż jest dość długo. A z jakiego serwera ftp pobierasz te dane? Z tego helion-a?
Rekurencja musi być użyta, bo to jest jedyny sposób na pobranie tego drzewa katalogów. Szczerze to nie przyglądałem się temu Twojemu kodowi. Odpaliłem go tylko u siebie, żeby zobaczyć, co otrzymam. Wyniki nie do końca były poprawne, więc uznałem, że lepiej będzie napisać wszystko od nowa niż szukać błędu. Co się rzuca w oczy w tym Twoim kodzie to to, że nie zwalniasz zasobów za pomocą metody Close (zobacz co robię w metodzie GetList w sekcji finally) oraz to, że niepotrzebnie używałeś metody Substring i błędnie sprawdzałeś, czy dany element to plik. U Ciebie pętla szła od indeksu 0, a lepszym rozwiązaniem jest puścić pętlę od ostatniego indeksu. Sęk w tym, że to samo robi metoda Path.GetExtension, więc nie ma potrzeby pisać tego samemu.
Wciąż możesz poprawić wydajność tego mojego kodu np. poprzez wywoływanie metody void Fill(TreeNode parentNode, string url) w osobnych wątkach. Jeśli jednak korzystasz z serwera, na którym zgromadzone jest na prawdę dużo plików, to lepszym rozwiązaniem będzie zrobić to o czym wspominał Fiołek, czyli ściągać zawartość podfolderów na żądanie. Nie oznaczałoby to jakoś specjalnie dużo zmian w kodzie. Najlepiej byłoby przygotować do tego celu własną kontrolkę o nazwie powiedzmy FTPTreeView. Każdy node tego treeView przechowywałby pełne informacje o wyświetlanym elemencie. W sumie już to jest robione, bowiem we właściwości Tag każdego node-a umieszczony jest obiekt klasy FTPObject. Ta kontrolka FTPTreeView mogłaby mieć metodę do pobierania całego drzewa (czyli tą metodę, która już jest zaimplementowana) i metodę do pobierania danych na żądanie (przeciążyłbyś metodę OnBeforeExpand).
Dziś zauważyłem bug w tym moim kodzie związany z kodowaniem. Niestety obejście tego to nie jest prosta sprawa. Problem polega na tym, że gdy serwer ftp wysyła dane, to nie jest wiadomo przy użyciu jakiego kodowania te dane zostały zapisane. Szukałem bibliotek, które służą do wykrywania kodowania. Ostatecznie zdecydowałem się na http://code.google.com/p/ude/ (jak znajdziesz lepszą bibliotekę to daj znać). Pobierz sobie ten projekt, skompiluj i do referencji dodaj bibliotekę Ude.dll. Następnie podmień kod klasy FTPClient.cs na
using System;
using System.Text;
using System.IO;
using System.Net;
using Ude;
namespace FTPDemo
{
public class FTPClient
{
public FTPClient(string userName, string password)
{
UserName = string.IsNullOrEmpty(userName) ? string.Empty : userName;
Password = string.IsNullOrEmpty(password) ? string.Empty : password;
KeepAlive = true;
UsePassive = true;
EnableSsl = false;
Timeout = System.Threading.Timeout.Infinite;
}
public string UserName { get; set; }
public string Password { get; set; }
public bool KeepAlive { get; set; }
public bool UsePassive { get; set; }
public bool EnableSsl { get; set; }
public int Timeout { get; set; }
public FTPObject[] GetDirectoryList(string url)
{
try
{
string[] list = GetList(url, WebRequestMethods.Ftp.ListDirectory);
return list != null ? ParseDirectoryList(url, list) : null;
}
catch (Exception ex)
{
throw ex;
}
}
public FTPObject[] GetDirectoryDetailsList(string url)
{
try
{
string[] list = GetList(url, WebRequestMethods.Ftp.ListDirectoryDetails);
return list != null ? ParseDirectoryDetailsList(url, list) : null;
}
catch (Exception ex)
{
throw ex;
}
}
public string CombineUrl(string url1, string url2)
{
if (string.IsNullOrEmpty(url1))
return url2;
if (string.IsNullOrEmpty(url2))
return url1;
url1 = url1.TrimEnd(new char[] { '/' });
url2 = url2.TrimStart(new char[] { '/' });
return string.Format("{0}/{1}", url1, url2);
}
private FtpWebRequest GetRequest(string url)
{
try
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
request.Credentials = new NetworkCredential(UserName, Password);
request.KeepAlive = KeepAlive;
request.UsePassive = UsePassive;
request.EnableSsl = EnableSsl;
request.Timeout = Timeout;
return request;
}
catch (Exception ex)
{
throw ex;
}
}
private string[] GetList(string url, string method)
{
FtpWebResponse response = null;
Stream responseStream = null;
StreamReader reader = null;
try
{
FtpWebRequest request = GetRequest(url);
request.Method = method;
response = (FtpWebResponse)request.GetResponse();
responseStream = response.GetResponseStream();
byte[] bytes;
float confidence;
Encoding encoding = DetectEncoding(responseStream, out bytes, out confidence);
string result = encoding.GetString(bytes);
return !string.IsNullOrEmpty(result) ? result.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) : null;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (reader != null)
reader.Close();
if (responseStream != null)
responseStream.Close();
if (response != null)
response.Close();
}
}
private Encoding DetectEncoding(Stream stream, out byte[] bytes, out float confidence)
{
MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
return DetectEncoding(bytes, out confidence);
}
private Encoding DetectEncoding(byte[] bytes, out float confidence)
{
CharsetDetector charsetDetector = new CharsetDetector();
charsetDetector.Feed(bytes, 0, bytes.Length);
charsetDetector.DataEnd();
Encoding result = Encoding.Default;
confidence = 0f;
if (!string.IsNullOrEmpty(charsetDetector.Charset))
{
result = Encoding.GetEncoding(charsetDetector.Charset);
confidence = charsetDetector.Confidence;
}
return result;
}
private FTPObject[] ParseDirectoryList(string url, string[] list)
{
FTPObject[] result = new FTPObject[list.Length];
for (int i = 0; i < list.Length; i++)
{
FTPObject.FTPObjectType objectType = FTPObject.FTPObjectType.Directory;
if (!string.IsNullOrEmpty(Path.GetExtension(list[i])))
objectType = FTPObject.FTPObjectType.File;
FTPObject ftpObject = new FTPObject(url, list[i], objectType);
result[i] = ftpObject;
}
return result;
}
private FTPObject[] ParseDirectoryDetailsList(string url, string[] list)
{
FTPObject[] result = new FTPObject[list.Length];
for (int i = 0; i < list.Length; i++)
{
string mode = string.Empty;
string links = string.Empty;
string owner = string.Empty;
string group = string.Empty;
string size = string.Empty;
string month = string.Empty;
string dayOfMonth = string.Empty;
string hourOrYear = string.Empty;
string name = string.Empty;
int startIndex = 0;
int length = 0;
int wordCounter = 1;
for (int j = 0; j < list[i].Length; j++)
{
if (!(list[i][j].CompareTo(' ') == 0 || list[i][j].CompareTo('\t') == 0) || wordCounter == 9)
length++;
else if (length > 0)
{
string s = list[i].Substring(startIndex, length);
switch (wordCounter)
{
case 1:
mode = s;
break;
case 2:
links = s;
break;
case 3:
owner = s;
break;
case 4:
group = s;
break;
case 5:
size = s;
break;
case 6:
month = s;
break;
case 7:
dayOfMonth = s;
break;
case 8:
hourOrYear = s;
break;
}
length = 0;
startIndex = j + 1;
wordCounter++;
}
else
startIndex = j + 1;
}
name = list[i].Substring(startIndex, length);
FTPObject.FTPObjectType objectType = FTPObject.FTPObjectType.File;
if (list[i].StartsWith("d"))
objectType = FTPObject.FTPObjectType.Directory;
else if (list[i].StartsWith("l"))
objectType = FTPObject.FTPObjectType.Link;
string hour = string.Empty;
string year = hourOrYear;
if (hourOrYear.Contains(":"))
{
hour = hourOrYear;
year = DateTime.Now.Year.ToString();
}
StringBuilder dateBuilder = new StringBuilder();
dateBuilder.Append(dayOfMonth);
dateBuilder.Append(" ");
dateBuilder.Append(month);
dateBuilder.Append(" ");
dateBuilder.Append(year);
if (!string.IsNullOrEmpty(hour))
{
dateBuilder.Append(" ");
dateBuilder.Append(hour);
}
DateTime lastModified = DateTime.Parse(dateBuilder.ToString());
FTPObject ftpObject = new FTPObject(url, name, objectType, new FTPObjectDetails(int.Parse(size), lastModified));
result[i] = ftpObject;
}
return result;
}
}
}
– Dodane 11.07.2011 (Pn) 3:47 – Doszły kolejne zmiany. Na formę zamiast kontrolki TreeView wrzuć sobie kontrolkę FTPTreeView (będzie widoczna, gdy skompilujesz projekt). Usuń klasy FTPObject oraz FTPObjectDetails, bo zmieniłem ich nazwy i trochę zmieniłem kod. Wszystkie wymagane pliki umieszczam poniżej. BTW zauważ jak skrócił się kod klasy Form1 i sprawdź czy wydajność się poprawiła. FTPClient.cs
using System;
using System.Text;
using System.IO;
using System.Net;
using Ude;
namespace FTPDemo.Core
{
public class FTPClient
{
public FTPClient(string userName, string password)
{
UserName = string.IsNullOrEmpty(userName) ? string.Empty : userName;
Password = string.IsNullOrEmpty(password) ? string.Empty : password;
KeepAlive = true;
UsePassive = true;
EnableSsl = false;
Timeout = System.Threading.Timeout.Infinite;
}
public string UserName { get; set; }
public string Password { get; set; }
public bool KeepAlive { get; set; }
public bool UsePassive { get; set; }
public bool EnableSsl { get; set; }
public int Timeout { get; set; }
public FTPElement[] GetDirectoryList(string url)
{
try
{
string[] list = GetList(url, WebRequestMethods.Ftp.ListDirectory);
return list != null ? ParseDirectoryList(url, list) : null;
}
catch (Exception ex)
{
throw ex;
}
}
public FTPElement[] GetDirectoryDetailsList(string url)
{
try
{
string[] list = GetList(url, WebRequestMethods.Ftp.ListDirectoryDetails);
return list != null ? ParseDirectoryDetailsList(url, list) : null;
}
catch (Exception ex)
{
throw ex;
}
}
public string CombineUrl(string url1, string url2)
{
if (string.IsNullOrEmpty(url1))
return url2;
if (string.IsNullOrEmpty(url2))
return url1;
url1 = url1.TrimEnd(new char[] { '/' });
url2 = url2.TrimStart(new char[] { '/' });
return string.Format("{0}/{1}", url1, url2);
}
private FtpWebRequest GetRequest(string url)
{
try
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
request.Credentials = new NetworkCredential(UserName, Password);
request.KeepAlive = KeepAlive;
request.UsePassive = UsePassive;
request.EnableSsl = EnableSsl;
request.Timeout = Timeout;
return request;
}
catch (Exception ex)
{
throw ex;
}
}
private string[] GetList(string url, string method)
{
FtpWebResponse response = null;
Stream responseStream = null;
StreamReader reader = null;
try
{
FtpWebRequest request = GetRequest(url);
request.Method = method;
response = (FtpWebResponse)request.GetResponse();
responseStream = response.GetResponseStream();
byte[] bytes;
float confidence;
Encoding encoding = DetectEncoding(responseStream, out bytes, out confidence);
string result = encoding.GetString(bytes);
return !string.IsNullOrEmpty(result) ? result.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) : null;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (reader != null)
reader.Close();
if (responseStream != null)
responseStream.Close();
if (response != null)
response.Close();
}
}
private Encoding DetectEncoding(Stream stream, out byte[] bytes, out float confidence)
{
MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
return DetectEncoding(bytes, out confidence);
}
private Encoding DetectEncoding(byte[] bytes, out float confidence)
{
CharsetDetector charsetDetector = new CharsetDetector();
charsetDetector.Feed(bytes, 0, bytes.Length);
charsetDetector.DataEnd();
Encoding result = Encoding.Default;
confidence = 0f;
if (!string.IsNullOrEmpty(charsetDetector.Charset))
{
result = Encoding.GetEncoding(charsetDetector.Charset);
confidence = charsetDetector.Confidence;
}
return result;
}
private FTPElement[] ParseDirectoryList(string url, string[] list)
{
FTPElement[] result = new FTPElement[list.Length];
for (int i = 0; i < list.Length; i++)
{
FTPElement.FTPElementType elementType = FTPElement.FTPElementType.Directory;
if (!string.IsNullOrEmpty(Path.GetExtension(list[i])))
elementType = FTPElement.FTPElementType.File;
FTPElement element = new FTPElement(list[i], CombineUrl(url, list[i]), elementType);
result[i] = element;
}
return result;
}
private FTPElement[] ParseDirectoryDetailsList(string url, string[] list)
{
FTPElement[] result = new FTPElement[list.Length];
for (int i = 0; i < list.Length; i++)
{
string mode = string.Empty;
string links = string.Empty;
string owner = string.Empty;
string group = string.Empty;
string size = string.Empty;
string month = string.Empty;
string dayOfMonth = string.Empty;
string hourOrYear = string.Empty;
string name = string.Empty;
int startIndex = 0;
int length = 0;
int wordCounter = 1;
for (int j = 0; j < list[i].Length; j++)
{
if (!(list[i][j].CompareTo(' ') == 0 || list[i][j].CompareTo('\t') == 0) || wordCounter == 9)
length++;
else if (length > 0)
{
string s = list[i].Substring(startIndex, length);
switch (wordCounter)
{
case 1:
mode = s;
break;
case 2:
links = s;
break;
case 3:
owner = s;
break;
case 4:
group = s;
break;
case 5:
size = s;
break;
case 6:
month = s;
break;
case 7:
dayOfMonth = s;
break;
case 8:
hourOrYear = s;
break;
}
length = 0;
startIndex = j + 1;
wordCounter++;
}
else
startIndex = j + 1;
}
name = list[i].Substring(startIndex, length);
FTPElement.FTPElementType elementType = FTPElement.FTPElementType.File;
if (list[i].StartsWith("d"))
elementType = FTPElement.FTPElementType.Directory;
else if (list[i].StartsWith("l"))
elementType = FTPElement.FTPElementType.Link;
string hour = string.Empty;
string year = hourOrYear;
if (hourOrYear.Contains(":"))
{
hour = hourOrYear;
year = DateTime.Now.Year.ToString();
}
StringBuilder dateBuilder = new StringBuilder();
dateBuilder.Append(dayOfMonth);
dateBuilder.Append(" ");
dateBuilder.Append(month);
dateBuilder.Append(" ");
dateBuilder.Append(year);
if (!string.IsNullOrEmpty(hour))
{
dateBuilder.Append(" ");
dateBuilder.Append(hour);
}
DateTime lastModified = DateTime.Parse(dateBuilder.ToString());
FTPElement element = new FTPElement(name, CombineUrl(url, name), elementType, new FTPElementDetails(int.Parse(size), lastModified));
result[i] = element;
}
return result;
}
}
}
FTPElement.cs
using System;
namespace FTPDemo.Core
{
public class FTPElement
{
public FTPElement(string name, string url, FTPElementType elementType)
{
Name = name;
Url = url;
ElementType = elementType;
ElementDetails = null;
}
public FTPElement(string name, string url, FTPElementType elementType, FTPElementDetails elementDetails)
{
Name = name;
Url = url;
ElementType = elementType;
ElementDetails = elementDetails;
}
public string Url { get; private set; }
public string Name { get; private set; }
public FTPElementType ElementType { get; private set; }
public FTPElementDetails ElementDetails { get; private set; }
public bool IsDirectory
{
get { return (ElementType == FTPElementType.Directory); }
}
public bool IsFile
{
get { return (ElementType == FTPElementType.File); }
}
public bool IsLink
{
get { return (ElementType == FTPElementType.Link); }
}
public enum FTPElementType
{
Directory,
File,
Link
}
}
}
FTPElementDetails.cs
using System;
namespace FTPDemo.Core
{
public class FTPElementDetails
{
public FTPElementDetails(int size, DateTime lastModified)
{
Size = size;
LastModified = lastModified;
}
public int Size { get; private set; }
public DateTime LastModified { get; private set; }
}
}
FTPTreeNode.cs
using System;
using System.Windows.Forms;
using FTPDemo.Core;
namespace FTPDemo.UI
{
public class FTPTreeNode : TreeNode
{
public FTPTreeNode()
: base()
{
Element = null;
ChildrenLoaded = false;
}
public FTPTreeNode(FTPElement element)
: base(element.Name)
{
Element = element;
ChildrenLoaded = false;
}
public FTPTreeNode(FTPElement element, int imageIndex, int selectedImageIndex)
: base(element.Name, imageIndex, selectedImageIndex)
{
Element = element;
ChildrenLoaded = false;
}
public FTPElement Element { get; private set; }
public bool ChildrenLoaded { get; set; }
}
}
FTPTreeView.cs
using System;
using System.Windows.Forms;
using System.ComponentModel;
using FTPDemo.Core;
namespace FTPDemo.UI
{
[DefaultProperty("Url")]
public class FTPTreeView : TreeView
{
private const string _DEFAULT_USER_NAME = "";
private const string _DEFAULT_PASSWORD = "";
private const string _CATEGORY_NAME = "FTP Settings";
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; }
}
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;
}
}
}
}
}
}
}
Form1.cs
using System;
using System.Windows.Forms;
namespace FTPDemo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void refreshButton_Click(object sender, EventArgs e)
{
ftpTreeView.UserName = ftpUserNameTextBox.Text;
ftpTreeView.Password = ftpPasswordTextBox.Text;
ftpTreeView.Clear();
ftpTreeView.Load(ftpUrlTextBox.Text, true);
}
}
}