Files
NewsCrawler/FluentFTP/Client/FtpClient_Listing.cs
2018-01-02 01:11:53 +09:00

1072 lines
39 KiB
C#

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Globalization;
using System.Security.Authentication;
using System.Net;
using FluentFTP.Proxy;
#if !CORE
using System.Web;
#endif
#if (CORE || NETFX)
using System.Threading;
#endif
#if ASYNC
using System.Threading.Tasks;
#endif
namespace FluentFTP {
/// <summary>
/// FTP Control Connection. Speaks the FTP protocol with the server and
/// provides facilities for performing transactions.
///
/// Debugging problems with FTP transactions is much easier to do when
/// you can see exactly what is sent to the server and the reply
/// FluentFTP gets in return. Please review the Debug example
/// below for information on how to add <see cref="System.Diagnostics.TraceListener"/>s for capturing
/// the conversation between FluentFTP and the server.
/// </summary>
/// <example>The following example illustrates how to assist in debugging
/// FluentFTP by getting a transaction log from the server.
/// <code source="..\Examples\Debug.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates adding a custom file
/// listing parser in the event that you encounter a list format
/// not already supported.
/// <code source="..\Examples\CustomParser.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to validate
/// a SSL certificate when using SSL/TLS.
/// <code source="..\Examples\ValidateCertificate.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to download a file.
/// <code source="..\Examples\OpenRead.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to download a file
/// using a URI object.
/// <code source="..\Examples\OpenReadURI.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to upload a file.
/// <code source="..\Examples\OpenWrite.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to upload a file
/// using a URI object.
/// <code source="..\Examples\OpenWriteURI.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to append to a file.
/// <code source="..\Examples\OpenAppend.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to append to a file
/// using a URI object.
/// <code source="..\Examples\OpenAppendURI.cs" lang="cs" />
/// </example>
/// <example>The following example demonstrates how to get a file
/// listing from the server.
/// <code source="..\Examples\GetListing.cs" lang="cs" />
/// </example>
public partial class FtpClient : IDisposable {
#region Properties
private FtpParser m_parser = FtpParser.Auto;
/// <summary>
/// File listing parser to be used.
/// Automatically calculated based on the type of the server, unless changed.
/// </summary>
public FtpParser ListingParser {
get { return m_parser; }
set {
m_parser = value;
// configure parser
m_listParser.parser = value;
m_listParser.parserConfirmed = false;
}
}
private CultureInfo m_parserCulture = CultureInfo.InvariantCulture;
/// <summary>
/// Culture used to parse file listings
/// </summary>
public CultureInfo ListingCulture {
get { return m_parserCulture; }
set {
m_parserCulture = value;
// configure parser
m_listParser.parserCulture = value;
}
}
private double m_timeDiff = 0;
/// <summary>
/// Time difference between server and client, in hours.
/// If the server is located in New York and you are in London then the time difference is -5 hours.
/// </summary>
public double TimeOffset {
get { return m_timeDiff; }
set {
m_timeDiff = value;
// configure parser
int hours = (int)Math.Floor(m_timeDiff);
int mins = (int)Math.Floor((m_timeDiff - Math.Floor(m_timeDiff)) * 60);
m_listParser.timeOffset = new TimeSpan(hours, mins, 0);
m_listParser.hasTimeOffset = m_timeDiff != 0;
}
}
private bool m_recursiveList = true;
/// <summary>
/// Check if your server supports a recursive LIST command (LIST -R).
/// If you know for sure that this is unsupported, set it to false.
/// </summary>
public bool RecursiveList {
get {
if (SystemType.StartsWith("Windows_CE")) {
return false;
}
return m_recursiveList;
}
set {
m_recursiveList = value;
}
}
private bool m_bulkListing = true;
/// <summary>
/// If true, increases performance of GetListing by reading multiple lines
/// of the file listing at once. If false then GetListing will read file
/// listings line-by-line. If GetListing is having issues with your server,
/// set it to false.
///
/// The number of bytes read is based upon <see cref="BulkListingLength"/>.
/// </summary>
public bool BulkListing {
get {
return m_bulkListing;
}
set {
m_bulkListing = value;
}
}
private int m_bulkListingLength = 128;
/// <summary>
/// Bytes to read during GetListing. Only honored if <see cref="BulkListing"/> is true.
/// </summary>
public int BulkListingLength {
get {
return m_bulkListingLength;
}
set {
m_bulkListingLength = value;
}
}
#endregion
#region Get File Info
/// <summary>
/// Returns information about a file system object. Returns null if the server response can't
/// be parsed or the server returns a failure completion code. The error for a failure
/// is logged with FtpTrace. No exception is thrown on error because that would negate
/// the usefulness of this method for checking for the existence of an object.
/// </summary>
/// <param name="path">The path of the file or folder</param>
/// <param name="dateModified">Get the accurate modified date using another MDTM command</param>
/// <returns>A FtpListItem object</returns>
public FtpListItem GetObjectInfo(string path, bool dateModified = false) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("GetObjectInfo", new object[] { path, dateModified });
FtpReply reply;
string[] res;
bool supportsMachineList = (Capabilities & FtpCapability.MLSD) == FtpCapability.MLSD;
FtpListItem result = null;
if (supportsMachineList) {
// USE MACHINE LISTING TO GET INFO FOR A SINGLE FILE
if ((reply = Execute("MLST " + path)).Success) {
res = reply.InfoMessages.Split('\n');
if (res.Length > 1) {
string info = "";
for (int i = 1; i < res.Length; i++) {
info += res[i];
}
result = m_listParser.ParseSingleLine(null, info, m_caps, true);
}
} else {
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " with error " + reply.ErrorMessage);
}
} else {
// USE GETLISTING TO GET ALL FILES IN DIR .. SLOWER BUT AT LEAST IT WORKS
string dirPath = path.GetFtpDirectoryName();
FtpListItem[] dirItems = GetListing(dirPath);
foreach (var dirItem in dirItems) {
if (dirItem.FullName == path) {
result = dirItem;
break;
}
}
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " since MLST not supported and GetListing() fails to list file/folder.");
}
// Get the accurate date modified using another MDTM command
if (result != null && dateModified && HasFeature(FtpCapability.MDTM)) {
result.Modified = GetModifiedTime(path);
}
return result;
}
delegate FtpListItem AsyncGetObjectInfo(string path, bool dateModified);
/// <summary>
/// Begins an asynchronous operation to return information about a remote file system object.
/// </summary>
/// <remarks>
/// You should check the <see cref="Capabilities"/> property for the <see cref="FtpCapability.MLSD"/>
/// flag before calling this method. Failing to do so will result in an InvalidOperationException
/// being thrown when the server does not support machine listings. Returns null if the server response can't
/// be parsed or the server returns a failure completion code. The error for a failure
/// is logged with FtpTrace. No exception is thrown on error because that would negate
/// the usefulness of this method for checking for the existence of an object.
/// </remarks>
/// <param name="path">Path of the file or folder</param>
/// <param name="dateModified">Get the accurate modified date using another MDTM command</param>
/// <param name="callback">Async Callback</param>
/// <param name="state">State object</param>
/// <returns>IAsyncResult</returns>
public IAsyncResult BeginGetObjectInfo(string path, bool dateModified, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncGetObjectInfo func;
ar = (func = new AsyncGetObjectInfo(GetObjectInfo)).BeginInvoke(path, dateModified, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
/// <summary>
/// Ends a call to <see cref="BeginGetObjectInfo"/>
/// </summary>
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetObjectInfo"/></param>
/// <returns>A <see cref="FtpListItem"/> if the command succeeded, or null if there was a problem.</returns>
public FtpListItem EndGetObjectInfo(IAsyncResult ar) {
return GetAsyncDelegate<AsyncGetObjectInfo>(ar).EndInvoke(ar);
}
#if ASYNC
/// <summary>
/// Return information about a remote file system object asynchronously.
/// </summary>
/// <remarks>
/// You should check the <see cref="Capabilities"/> property for the <see cref="FtpCapability.MLSD"/>
/// flag before calling this method. Failing to do so will result in an InvalidOperationException
/// being thrown when the server does not support machine listings. Returns null if the server response can't
/// be parsed or the server returns a failure completion code. The error for a failure
/// is logged with FtpTrace. No exception is thrown on error because that would negate
/// the usefulness of this method for checking for the existence of an object.</remarks>
/// <param name="path">Path of the item to retrieve information about</param>
/// <param name="dateModified">Get the accurate modified date using another MDTM command</param>
/// <exception cref="InvalidOperationException">Thrown if the server does not support this Capability</exception>
/// <returns>A <see cref="FtpListItem"/> if the command succeeded, or null if there was a problem.</returns>
public async Task<FtpListItem> GetObjectInfoAsync(string path, bool dateModified = false) {
//TODO: Rewrite as true async method with cancellation support
return await Task.Factory.FromAsync<string, bool, FtpListItem>(
(p, dm, ac, s) => BeginGetObjectInfo(p, dm, ac, s),
ar => EndGetObjectInfo(ar),
path, dateModified, null);
}
#endif
#endregion
#region Get Listing
/// <summary>
/// Gets a file listing from the server from the current working directory. Each <see cref="FtpListItem"/> object returned
/// contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <returns>An array of FtpListItem objects</returns>
/// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example>
public FtpListItem[] GetListing() {
return GetListing(null);
}
/// <summary>
/// Gets a file listing from the server. Each <see cref="FtpListItem"/> object returned
/// contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <param name="path">The path of the directory to list</param>
/// <returns>An array of FtpListItem objects</returns>
/// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example>
public FtpListItem[] GetListing(string path) {
return GetListing(path, 0);
}
/// <summary>
/// Gets a file listing from the server. Each <see cref="FtpListItem"/> object returned
/// contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <param name="path">The path of the directory to list</param>
/// <param name="options">Options that dictacte how a list is performed and what information is gathered.</param>
/// <returns>An array of FtpListItem objects</returns>
/// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example>
public FtpListItem[] GetListing(string path, FtpListOption options) {
FtpTrace.WriteFunc("GetListing", new object[] { path, options });
FtpListItem item = null;
List<FtpListItem> lst = new List<FtpListItem>();
List<string> rawlisting = new List<string>();
string listcmd = null;
string buf = null;
// read flags
bool isIncludeSelf = (options & FtpListOption.IncludeSelfAndParent) == FtpListOption.IncludeSelfAndParent;
bool isForceList = (options & FtpListOption.ForceList) == FtpListOption.ForceList;
bool isNoPath = (options & FtpListOption.NoPath) == FtpListOption.NoPath;
bool isNameList = (options & FtpListOption.NameList) == FtpListOption.NameList;
bool isUseLS = (options & FtpListOption.UseLS) == FtpListOption.UseLS;
bool isAllFiles = (options & FtpListOption.AllFiles) == FtpListOption.AllFiles;
bool isRecursive = (options & FtpListOption.Recursive) == FtpListOption.Recursive && RecursiveList;
bool isDerefLinks = (options & FtpListOption.DerefLinks) == FtpListOption.DerefLinks;
bool isGetModified = (options & FtpListOption.Modify) == FtpListOption.Modify;
bool isGetSize = (options & FtpListOption.Size) == FtpListOption.Size;
// calc path to request
path = GetAbsolutePath(path);
// MLSD provides a machine readable format with 100% accurate information
// so always prefer MLSD over LIST unless the caller of this method overrides it with the ForceList option
bool machineList = false;
if ((!isForceList || m_parser == FtpParser.Machine) && HasFeature(FtpCapability.MLSD)) {
listcmd = "MLSD";
machineList = true;
} else {
if (isUseLS) {
listcmd = "LS";
} else if (isNameList) {
listcmd = "NLST";
} else {
string listopts = "";
listcmd = "LIST";
if (isAllFiles)
listopts += "a";
if (isRecursive)
listopts += "R";
if (listopts.Length > 0)
listcmd += " -" + listopts;
}
}
if (!isNoPath) {
listcmd = (listcmd + " " + path.GetFtpPath());
}
#if !CORE14
lock (m_lock) {
#endif
Execute("TYPE I");
// read in raw file listing
using (FtpDataStream stream = OpenDataStream(listcmd, 0)) {
try {
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+");
if (this.BulkListing) {
// increases performance of GetListing by reading multiple lines of the file listing at once
foreach (var line in stream.ReadAllLines(Encoding, this.BulkListingLength)) {
if (!FtpExtensions.IsNullOrWhiteSpace(line)) {
rawlisting.Add(line);
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + line);
}
}
} else {
// GetListing will read file listings line-by-line (actually byte-by-byte)
while ((buf = stream.ReadLine(Encoding)) != null) {
if (buf.Length > 0) {
rawlisting.Add(buf);
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + buf);
}
}
}
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------");
} finally {
stream.Close();
}
}
#if !CORE14
}
#endif
for (int i = 0; i < rawlisting.Count; i++) {
buf = rawlisting[i];
if (isNameList) {
// if NLST was used we only have a file name so
// there is nothing to parse.
item = new FtpListItem() {
FullName = buf
};
if (DirectoryExists(item.FullName))
item.Type = FtpFileSystemObjectType.Directory;
else
item.Type = FtpFileSystemObjectType.File;
lst.Add(item);
} else {
// if this is a result of LIST -R then the path will be spit out
// before each block of objects
if (listcmd.StartsWith("LIST") && isRecursive) {
if (buf.StartsWith("/") && buf.EndsWith(":")) {
path = buf.TrimEnd(':');
continue;
}
}
// if the next line in the listing starts with spaces
// it is assumed to be a continuation of the current line
if (i + 1 < rawlisting.Count && (rawlisting[i + 1].StartsWith("\t") || rawlisting[i + 1].StartsWith(" ")))
buf += rawlisting[++i];
try {
item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList);
} catch (FtpListParser.CriticalListParseException) {
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Restarting parsing from first entry in list");
i = -1;
lst.Clear();
continue;
}
// FtpListItem.Parse() returns null if the line
// could not be parsed
if (item != null) {
if (isIncludeSelf || !(item.Name == "." || item.Name == "..")) {
lst.Add(item);
} else {
//FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped self or parent item: " + item.Name);
}
} else {
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse file listing: " + buf);
}
}
// load extended information that wasn't available if the list options flags say to do so.
if (item != null) {
// try to dereference symbolic links if the appropriate list
// option was passed
if (item.Type == FtpFileSystemObjectType.Link && isDerefLinks) {
item.LinkObject = DereferenceLink(item);
}
// if need to get file modified date
if (isGetModified && HasFeature(FtpCapability.MDTM)) {
// if the modified date was not loaded or the modified date is more than a day in the future
// and the server supports the MDTM command, load the modified date.
// most servers do not support retrieving the modified date
// of a directory but we try any way.
if (item.Modified == DateTime.MinValue || listcmd.StartsWith("LIST")) {
DateTime modify;
if (item.Type == FtpFileSystemObjectType.Directory)
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Trying to retrieve modification time of a directory, some servers don't like this...");
if ((modify = GetModifiedTime(item.FullName)) != DateTime.MinValue)
item.Modified = modify;
}
}
// if need to get file size
if (isGetSize && HasFeature(FtpCapability.SIZE)) {
// if no size was parsed, the object is a file and the server
// supports the SIZE command, then load the file size
if (item.Size == -1) {
if (item.Type != FtpFileSystemObjectType.Directory) {
item.Size = GetFileSize(item.FullName);
} else {
item.Size = 0;
}
}
}
}
}
return lst.ToArray();
}
#if !CORE
/// <summary>
/// Begins an asynchronous operation to get a file listing from the server.
/// Each <see cref="FtpListItem"/> object returned contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <param name="callback">AsyncCallback method</param>
/// <param name="state">State object</param>
/// <returns>IAsyncResult</returns>
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
public IAsyncResult BeginGetListing(AsyncCallback callback, Object state) {
return BeginGetListing(null, callback, state);
}
/// <summary>
/// Begins an asynchronous operation to get a file listing from the server.
/// Each <see cref="FtpListItem"/> object returned contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <param name="path">The path to list</param>
/// <param name="callback">AsyncCallback method</param>
/// <param name="state">State object</param>
/// <returns>IAsyncResult</returns>
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
public IAsyncResult BeginGetListing(string path, AsyncCallback callback, Object state) {
return BeginGetListing(path, FtpListOption.Modify | FtpListOption.Size, callback, state);
}
delegate FtpListItem[] AsyncGetListing(string path, FtpListOption options);
/// <summary>
/// Gets a file listing from the server asynchronously
/// </summary>
/// <param name="path">The path to list</param>
/// <param name="options">Options that dictate how the list operation is performed</param>
/// <param name="callback">AsyncCallback method</param>
/// <param name="state">State object</param>
/// <returns>IAsyncResult</returns>
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
public IAsyncResult BeginGetListing(string path, FtpListOption options, AsyncCallback callback, Object state) {
IAsyncResult ar;
AsyncGetListing func;
ar = (func = new AsyncGetListing(GetListing)).BeginInvoke(path, options, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
/// <summary>
/// Ends a call to <see cref="o:BeginGetListing"/>
/// </summary>
/// <param name="ar">IAsyncResult return from <see cref="o:BeginGetListing"/></param>
/// <returns>An array of items retrieved in the listing</returns>
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
public FtpListItem[] EndGetListing(IAsyncResult ar) {
return GetAsyncDelegate<AsyncGetListing>(ar).EndInvoke(ar);
}
#endif
#if ASYNC
/// <summary>
/// Gets a file listing from the server asynchronously. Each <see cref="FtpListItem"/> object returned
/// contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <param name="path">The path to list</param>
/// <param name="options">Options that dictate how the list operation is performed</param>
/// <returns>An array of items retrieved in the listing</returns>
public async Task<FtpListItem[]> GetListingAsync(string path, FtpListOption options)
{
//TODO: Add cancellation support
FtpTrace.WriteFunc(nameof(GetListingAsync), new object[] { path, options });
FtpListItem item = null;
List<FtpListItem> lst = new List<FtpListItem>();
List<string> rawlisting = new List<string>();
string listcmd = null;
string buf = null;
// read flags
bool isIncludeSelf = (options & FtpListOption.IncludeSelfAndParent) == FtpListOption.IncludeSelfAndParent;
bool isForceList = (options & FtpListOption.ForceList) == FtpListOption.ForceList;
bool isNoPath = (options & FtpListOption.NoPath) == FtpListOption.NoPath;
bool isNameList = (options & FtpListOption.NameList) == FtpListOption.NameList;
bool isUseLS = (options & FtpListOption.UseLS) == FtpListOption.UseLS;
bool isAllFiles = (options & FtpListOption.AllFiles) == FtpListOption.AllFiles;
bool isRecursive = (options & FtpListOption.Recursive) == FtpListOption.Recursive && RecursiveList;
bool isDerefLinks = (options & FtpListOption.DerefLinks) == FtpListOption.DerefLinks;
bool isGetModified = (options & FtpListOption.Modify) == FtpListOption.Modify;
bool isGetSize = (options & FtpListOption.Size) == FtpListOption.Size;
// calc path to request
path = await GetAbsolutePathAsync(path);
// MLSD provides a machine readable format with 100% accurate information
// so always prefer MLSD over LIST unless the caller of this method overrides it with the ForceList option
bool machineList = false;
if ((!isForceList || m_parser == FtpParser.Machine) && HasFeature(FtpCapability.MLSD))
{
listcmd = "MLSD";
machineList = true;
}
else
{
if (isUseLS)
{
listcmd = "LS";
}
else if (isNameList)
{
listcmd = "NLST";
}
else
{
string listopts = "";
listcmd = "LIST";
if (isAllFiles)
listopts += "a";
if (isRecursive)
listopts += "R";
if (listopts.Length > 0)
listcmd += " -" + listopts;
}
}
if (!isNoPath)
{
listcmd = (listcmd + " " + path.GetFtpPath());
}
await ExecuteAsync("TYPE I");
// read in raw file listing
using (FtpDataStream stream = await OpenDataStreamAsync(listcmd, 0))
{
try
{
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+");
if (this.BulkListing)
{
// increases performance of GetListing by reading multiple lines of the file listing at once
foreach (var line in await stream.ReadAllLinesAsync(Encoding, this.BulkListingLength))
{
if (!FtpExtensions.IsNullOrWhiteSpace(line))
{
rawlisting.Add(line);
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + line);
}
}
}
else
{
// GetListing will read file listings line-by-line (actually byte-by-byte)
while ((buf = await stream.ReadLineAsync(Encoding)) != null)
{
if (buf.Length > 0)
{
rawlisting.Add(buf);
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + buf);
}
}
}
FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------");
}
finally
{
stream.Close();
}
}
for (int i = 0; i < rawlisting.Count; i++)
{
buf = rawlisting[i];
if (isNameList)
{
// if NLST was used we only have a file name so
// there is nothing to parse.
item = new FtpListItem()
{
FullName = buf
};
if (await DirectoryExistsAsync(item.FullName))
item.Type = FtpFileSystemObjectType.Directory;
else
item.Type = FtpFileSystemObjectType.File;
lst.Add(item);
}
else
{
// if this is a result of LIST -R then the path will be spit out
// before each block of objects
if (listcmd.StartsWith("LIST") && isRecursive)
{
if (buf.StartsWith("/") && buf.EndsWith(":"))
{
path = buf.TrimEnd(':');
continue;
}
}
// if the next line in the listing starts with spaces
// it is assumed to be a continuation of the current line
if (i + 1 < rawlisting.Count && (rawlisting[i + 1].StartsWith("\t") || rawlisting[i + 1].StartsWith(" ")))
buf += rawlisting[++i];
try
{
item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList);
}
catch (FtpListParser.CriticalListParseException)
{
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Restarting parsing from first entry in list");
i = -1;
lst.Clear();
continue;
}
// FtpListItem.Parse() returns null if the line
// could not be parsed
if (item != null)
{
if (isIncludeSelf || !(item.Name == "." || item.Name == ".."))
{
lst.Add(item);
}
else
{
//FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped self or parent item: " + item.Name);
}
}
else
{
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse file listing: " + buf);
}
}
// load extended information that wasn't available if the list options flags say to do so.
if (item != null)
{
// try to dereference symbolic links if the appropriate list
// option was passed
if (item.Type == FtpFileSystemObjectType.Link && isDerefLinks)
{
item.LinkObject = await DereferenceLinkAsync(item);
}
// if need to get file modified date
if (isGetModified && HasFeature(FtpCapability.MDTM))
{
// if the modified date was not loaded or the modified date is more than a day in the future
// and the server supports the MDTM command, load the modified date.
// most servers do not support retrieving the modified date
// of a directory but we try any way.
if (item.Modified == DateTime.MinValue || listcmd.StartsWith("LIST"))
{
DateTime modify;
if (item.Type == FtpFileSystemObjectType.Directory)
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Trying to retrieve modification time of a directory, some servers don't like this...");
if ((modify = await GetModifiedTimeAsync(item.FullName)) != DateTime.MinValue)
item.Modified = modify;
}
}
// if need to get file size
if (isGetSize && HasFeature(FtpCapability.SIZE))
{
// if no size was parsed, the object is a file and the server
// supports the SIZE command, then load the file size
if (item.Size == -1)
{
if (item.Type != FtpFileSystemObjectType.Directory)
{
item.Size = await GetFileSizeAsync(item.FullName);
}
else
{
item.Size = 0;
}
}
}
}
}
return lst.ToArray();
}
/// <summary>
/// Gets a file listing from the server asynchronously. Each <see cref="FtpListItem"/> object returned
/// contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <param name="path">The path to list</param>
/// <returns>An array of items retrieved in the listing</returns>
public Task<FtpListItem[]> GetListingAsync(string path) {
//TODO: Add cancellation support
return GetListingAsync(path, 0);
}
/// <summary>
/// Gets a file listing from the server asynchronously. Each <see cref="FtpListItem"/> object returned
/// contains information about the file that was able to be retrieved.
/// </summary>
/// <remarks>
/// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the
/// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property
/// is equal to 0, then it means the size of the object could also not
/// be retrieved.
/// </remarks>
/// <returns>An array of items retrieved in the listing</returns>
public Task<FtpListItem[]> GetListingAsync() {
//TODO: Add cancellation support
return GetListingAsync(null);
}
#endif
#endregion
#region Get Name Listing
/// <summary>
/// Returns a file/directory listing using the NLST command.
/// </summary>
/// <returns>A string array of file and directory names if any were returned.</returns>
public string[] GetNameListing() {
return GetNameListing(null);
}
/// <summary>
/// Returns a file/directory listing using the NLST command.
/// </summary>
/// <param name="path">The path of the directory to list</param>
/// <returns>A string array of file and directory names if any were returned.</returns>
/// <example><code source="..\Examples\GetNameListing.cs" lang="cs" /></example>
public string[] GetNameListing(string path) {
FtpTrace.WriteFunc("GetNameListing", new object[] { path });
List<string> listing = new List<string>();
// calc path to request
path = GetAbsolutePath(path);
#if !CORE14
lock (m_lock) {
#endif
// always get the file listing in binary
// to avoid any potential character translation
// problems that would happen if in ASCII.
Execute("TYPE I");
using (FtpDataStream stream = OpenDataStream(("NLST " + path.GetFtpPath()), 0)) {
string buf;
try {
while ((buf = stream.ReadLine(Encoding)) != null)
listing.Add(buf);
} finally {
stream.Close();
}
}
#if !CORE14
}
#endif
return listing.ToArray();
}
#if !CORE
delegate string[] AsyncGetNameListing(string path);
/// <summary>
/// Begin an asynchronous operation to return a file/directory listing using the NLST command.
/// </summary>
/// <param name="path">The path of the directory to list</param>
/// <param name="callback">Async Callback</param>
/// <param name="state">State object</param>
/// <returns>IAsyncResult</returns>
/// <example><code source="..\Examples\BeginGetNameListing.cs" lang="cs" /></example>
public IAsyncResult BeginGetNameListing(string path, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncGetNameListing func;
ar = (func = new AsyncGetNameListing(GetNameListing)).BeginInvoke(path, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
/// <summary>
/// Begin an asynchronous operation to return a file/directory listing using the NLST command.
/// </summary>
/// <param name="callback">Async Callback</param>
/// <param name="state">State object</param>
/// <returns>IAsyncResult</returns>
/// <example><code source="..\Examples\BeginGetNameListing.cs" lang="cs" /></example>
public IAsyncResult BeginGetNameListing(AsyncCallback callback, object state) {
return BeginGetNameListing(null, callback, state);
}
/// <summary>
/// Ends a call to <see cref="o:BeginGetNameListing"/>
/// </summary>
/// <param name="ar">IAsyncResult object returned from <see cref="o:BeginGetNameListing"/></param>
/// <returns>An array of file and directory names if any were returned.</returns>
/// <example><code source="..\Examples\BeginGetNameListing.cs" lang="cs" /></example>
public string[] EndGetNameListing(IAsyncResult ar) {
return GetAsyncDelegate<AsyncGetNameListing>(ar).EndInvoke(ar);
}
#endif
#if ASYNC
/// <summary>
/// Returns a file/directory listing using the NLST command asynchronously
/// </summary>
/// <param name="path">The path of the directory to list</param>
/// <returns>An array of file and directory names if any were returned.</returns>
public async Task<string[]> GetNameListingAsync(string path)
{
//TODO: Add cancellation support
FtpTrace.WriteFunc(nameof(GetNameListingAsync), new object[] { path });
List<string> listing = new List<string>();
// calc path to request
path = await GetAbsolutePathAsync(path);
// always get the file listing in binary
// to avoid any potential character translation
// problems that would happen if in ASCII.
await ExecuteAsync("TYPE I");
using (FtpDataStream stream = await OpenDataStreamAsync(("NLST " + path.GetFtpPath()), 0))
{
string buf;
try
{
while ((buf = await stream.ReadLineAsync(Encoding)) != null)
listing.Add(buf);
}
finally
{
stream.Close();
}
}
return listing.ToArray();
}
/// <summary>
/// Returns a file/directory listing using the NLST command asynchronously
/// </summary>
/// <returns>An array of file and directory names if any were returned.</returns>
public Task<string[]> GetNameListingAsync() {
//TODO: Add cancellation support
return GetNameListingAsync(null);
}
#endif
#endregion
}
}