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.Linq;
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 {
///
/// 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 s for capturing
/// the conversation between FluentFTP and the server.
///
/// The following example illustrates how to assist in debugging
/// FluentFTP by getting a transaction log from the server.
///
///
/// The following example demonstrates adding a custom file
/// listing parser in the event that you encounter a list format
/// not already supported.
///
///
/// The following example demonstrates how to validate
/// a SSL certificate when using SSL/TLS.
///
///
/// The following example demonstrates how to download a file.
///
///
/// The following example demonstrates how to download a file
/// using a URI object.
///
///
/// The following example demonstrates how to upload a file.
///
///
/// The following example demonstrates how to upload a file
/// using a URI object.
///
///
/// The following example demonstrates how to append to a file.
///
///
/// The following example demonstrates how to append to a file
/// using a URI object.
///
///
/// The following example demonstrates how to get a file
/// listing from the server.
///
///
public partial class FtpClient : IFtpClient, IDisposable {
#region Delete File
///
/// Deletes a file on the server
///
/// The full or relative path to the file
///
public void DeleteFile(string path) {
FtpReply reply;
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
#if !CORE14
lock (m_lock) {
#endif
FtpTrace.WriteFunc("DeleteFile", new object[] { path });
if (!(reply = Execute("DELE " + path.GetFtpPath())).Success)
throw new FtpCommandException(reply);
#if !CORE14
}
#endif
}
#if !CORE
delegate void AsyncDeleteFile(string path);
///
/// Begins an asynchronous operation to delete the specified file on the server
///
/// The full or relative path to the file
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginDeleteFile(string path, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncDeleteFile func;
ar = (func = new AsyncDeleteFile(DeleteFile)).BeginInvoke(path, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from BeginDeleteFile
///
public void EndDeleteFile(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Deletes a file from the server asynchronously
///
/// The full or relative path to the file
public async Task DeleteFileAsync(string path)
{
FtpReply reply;
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(DeleteFileAsync), new object[] { path });
if (!(reply = await ExecuteAsync("DELE " + path.GetFtpPath())).Success)
throw new FtpCommandException(reply);
}
#endif
#endregion
#region Delete Directory
///
/// Deletes the specified directory and all its contents.
///
/// The full or relative path of the directory to delete
///
public void DeleteDirectory(string path) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("DeleteDirectory", new object[] { path });
DeleteDirInternal(path, true, FtpListOption.ForceList | FtpListOption.Recursive);
}
///
/// Deletes the specified directory and all its contents.
///
/// The full or relative path of the directory to delete
/// Useful to delete hidden files or dot-files.
///
public void DeleteDirectory(string path, FtpListOption options) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("DeleteDirectory", new object[] { path, options });
DeleteDirInternal(path, true, options);
}
///
/// Deletes the specified directory and all its contents.
///
/// The full or relative path of the directory to delete
/// If the directory is not empty, remove its contents
/// Useful to delete hidden files or dot-files.
///
private void DeleteDirInternal(string path, bool deleteContents, FtpListOption options) {
FtpReply reply;
string ftppath = path.GetFtpPath();
#if !CORE14
lock (m_lock) {
#endif
// DELETE CONTENTS OF THE DIRECTORY
if (deleteContents) {
// when GetListing is called with recursive option, then it does not
// make any sense to call another DeleteDirectory with force flag set.
// however this requires always delete files first.
bool recurse = !WasGetListingRecursive(options);
// items that are deeper in directory tree are listed first,
// then files will be listed before directories. This matters
// only if GetListing was called with recursive option.
FtpListItem[] itemList;
if (recurse) {
itemList = GetListing(path, options);
} else {
itemList = GetListing(path, options).OrderByDescending(x => x.FullName.Count(c => c.Equals('/'))).ThenBy(x => x.Type).ToArray();
}
// delete the item based on the type
foreach (FtpListItem item in itemList) {
switch (item.Type) {
case FtpFileSystemObjectType.File:
DeleteFile(item.FullName);
break;
case FtpFileSystemObjectType.Directory:
DeleteDirInternal(item.FullName, recurse, options);
break;
default:
throw new FtpException("Don't know how to delete object type: " + item.Type);
}
}
}
// SKIP DELETING ROOT DIRS
// can't delete the working directory and
// can't delete the server root.
if (ftppath == "." || ftppath == "./" || ftppath == "/") {
return;
}
// DELETE ACTUAL DIRECTORY
if (!(reply = Execute("RMD " + ftppath)).Success) {
throw new FtpCommandException(reply);
}
#if !CORE14
}
#endif
}
///
/// Checks whether will be called recursively or not.
///
///
///
private bool WasGetListingRecursive(FtpListOption options) {
// if recursive listings not supported by the server then obviously NO
if (!RecursiveList) {
return false;
}
// if machine listings and not force list then NO
if (HasFeature(FtpCapability.MLSD) && (options & FtpListOption.ForceList) != FtpListOption.ForceList) {
return false;
}
// if name listings then NO
if ((options & FtpListOption.UseLS) == FtpListOption.UseLS || (options & FtpListOption.NameList) == FtpListOption.NameList) {
return false;
}
// lastly if recursive is enabled then YES
if ((options & FtpListOption.Recursive) == FtpListOption.Recursive) {
return true;
}
// in all other cases NO
return false;
}
#if !CORE
delegate void AsyncDeleteDirectory(string path, FtpListOption options);
///
/// Begins an asynchronous operation to delete the specified directory and all its contents.
///
/// The full or relative path of the directory to delete
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginDeleteDirectory(string path, AsyncCallback callback, object state) {
return BeginDeleteDirectory(path, FtpListOption.ForceList | FtpListOption.Recursive, callback, state);
}
///
/// Begins an asynchronous operation to delete the specified directory and all its contents.
///
/// The full or relative path of the directory to delete
/// Useful to delete hidden files or dot-files.
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginDeleteDirectory(string path, FtpListOption options, AsyncCallback callback, object state) {
AsyncDeleteDirectory func;
IAsyncResult ar;
ar = (func = new AsyncDeleteDirectory(DeleteDirectory)).BeginInvoke(path, options, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from BeginDeleteDirectory
///
public void EndDeleteDirectory(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Asynchronously removes a directory and all its contents.
///
/// The full or relative path of the directory to delete
public Task DeleteDirectoryAsync(string path) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(DeleteDirectoryAsync), new object[] { path });
return DeleteDirInternalAsync(path, true, FtpListOption.ForceList | FtpListOption.Recursive);
}
///
/// Asynchronously removes a directory and all its contents.
///
/// The full or relative path of the directory to delete
/// Useful to delete hidden files or dot-files.
public Task DeleteDirectoryAsync(string path, FtpListOption options) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(DeleteDirectoryAsync), new object[] { path, options });
return DeleteDirInternalAsync(path, true, options);
}
///
/// Asynchronously removes a directory. Used by and
/// .
///
/// The full or relative path of the directory to delete
/// Delete the contents before deleting the folder
/// Useful to delete hidden files or dot-files.
///
private async Task DeleteDirInternalAsync(string path, bool deleteContents, FtpListOption options)
{
FtpReply reply;
string ftppath = path.GetFtpPath();
// DELETE CONTENTS OF THE DIRECTORY
if (deleteContents)
{
// when GetListing is called with recursive option, then it does not
// make any sense to call another DeleteDirectory with force flag set.
// however this requires always delete files first.
bool recurse = !WasGetListingRecursive(options);
// items that are deeper in directory tree are listed first,
// then files will be listed before directories. This matters
// only if GetListing was called with recursive option.
FtpListItem[] itemList;
if (recurse)
{
itemList = await GetListingAsync(path, options);
}
else
{
itemList = (await GetListingAsync(path, options)).OrderByDescending(x => x.FullName.Count(c => c.Equals('/'))).ThenBy(x => x.Type).ToArray();
}
// delete the item based on the type
foreach (FtpListItem item in itemList)
{
switch (item.Type)
{
case FtpFileSystemObjectType.File:
await DeleteFileAsync(item.FullName);
break;
case FtpFileSystemObjectType.Directory:
await DeleteDirInternalAsync(item.FullName, recurse, options);
break;
default:
throw new FtpException("Don't know how to delete object type: " + item.Type);
}
}
}
// SKIP DELETING ROOT DIRS
// can't delete the working directory and
// can't delete the server root.
if (ftppath == "." || ftppath == "./" || ftppath == "/")
{
return;
}
// DELETE ACTUAL DIRECTORY
if (!(reply = await ExecuteAsync("RMD " + ftppath)).Success)
{
throw new FtpCommandException(reply);
}
}
#endif
#endregion
#region Directory Exists
///
/// Tests if the specified directory exists on the server. This
/// method works by trying to change the working directory to
/// the path specified. If it succeeds, the directory is changed
/// back to the old working directory and true is returned. False
/// is returned otherwise and since the CWD failed it is assumed
/// the working directory is still the same.
///
/// The path of the directory
/// True if it exists, false otherwise.
///
public bool DirectoryExists(string path) {
string pwd;
// dont verify args as blank/null path is OK
//if (path.IsBlank())
// throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("DirectoryExists", new object[] { path });
// quickly check if root path, then it always exists!
string ftppath = path.GetFtpPath();
if (ftppath == "." || ftppath == "./" || ftppath == "/") {
return true;
}
// check if a folder exists by changing the working dir to it
#if !CORE14
lock (m_lock) {
#endif
pwd = GetWorkingDirectory();
if (Execute("CWD " + ftppath).Success) {
FtpReply reply = Execute("CWD " + pwd.GetFtpPath());
if (!reply.Success)
throw new FtpException("DirectoryExists(): Failed to restore the working directory.");
return true;
}
#if !CORE14
}
#endif
return false;
}
#if !CORE
delegate bool AsyncDirectoryExists(string path);
///
/// Begins an asynchronous operation to test if the specified directory exists on the server.
/// This method works by trying to change the working directory to
/// the path specified. If it succeeds, the directory is changed
/// back to the old working directory and true is returned. False
/// is returned otherwise and since the CWD failed it is assumed
/// the working directory is still the same.
///
/// IAsyncResult
/// The full or relative path of the directory to check for
/// Async callback
/// State object
///
public IAsyncResult BeginDirectoryExists(string path, AsyncCallback callback, object state) {
AsyncDirectoryExists func;
IAsyncResult ar;
ar = (func = new AsyncDirectoryExists(DirectoryExists)).BeginInvoke(path, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from BeginDirectoryExists
/// True if the directory exists. False otherwise.
///
public bool EndDirectoryExists(IAsyncResult ar) {
return GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Tests if the specified directory exists on the server asynchronously. This
/// method works by trying to change the working directory to
/// the path specified. If it succeeds, the directory is changed
/// back to the old working directory and true is returned. False
/// is returned otherwise and since the CWD failed it is assumed
/// the working directory is still the same.
///
/// The full or relative path of the directory to check for
/// True if the directory exists. False otherwise.
public async Task DirectoryExistsAsync(string path) {
// TODO: Add cancellation support
string pwd;
// dont verify args as blank/null path is OK
//if (path.IsBlank())
// throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(DirectoryExistsAsync), new object[] { path });
// quickly check if root path, then it always exists!
string ftppath = path.GetFtpPath();
if (ftppath == "." || ftppath == "./" || ftppath == "/")
{
return true;
}
// check if a folder exists by changing the working dir to it
pwd = await GetWorkingDirectoryAsync();
if ((await ExecuteAsync("CWD " + ftppath)).Success)
{
FtpReply reply = await ExecuteAsync("CWD " + pwd.GetFtpPath());
if (!reply.Success)
throw new FtpException("DirectoryExists(): Failed to restore the working directory.");
return true;
}
return false;
}
#endif
#endregion
#region File Exists
///
/// Checks if a file exists on the server.
///
/// The full or relative path to the file
/// True if the file exists
///
public bool FileExists(string path) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
#if !CORE14
lock (m_lock) {
#endif
FtpTrace.WriteFunc("FileExists", new object[] { path });
// calc the absolute filepath
path = GetAbsolutePath(path.GetFtpPath());
// since FTP does not include a specific command to check if a file exists
// here we check if file exists by attempting to get its filesize (SIZE)
if (HasFeature(FtpCapability.SIZE)) {
FtpReply reply = Execute("SIZE " + path);
char ch = reply.Code[0];
if (ch == '2') {
return true;
}
if (ch == '5' && IsKnownError(reply.Message, fileNotFoundStrings)) {
return false;
}
}
// check if file exists by attempting to get its date modified (MDTM)
if (HasFeature(FtpCapability.MDTM)) {
FtpReply reply = Execute("MDTM " + path);
char ch = reply.Code[0];
if (ch == '2') {
return true;
}
if (ch == '5' && IsKnownError(reply.Message, fileNotFoundStrings)) {
return false;
}
}
// check if file exists by getting a name listing (NLST)
string[] fileList = GetNameListing(path.GetFtpDirectoryName());
string pathName = path.GetFtpFileName();
if (fileList.Contains(pathName)) {
return true;
}
// check if file exists by attempting to download it (RETR)
/*try {
Stream stream = OpenRead(path);
stream.Close();
return true;
} catch (FtpException ex) {
}*/
return false;
#if !CORE14
}
#endif
}
#if !CORE
delegate bool AsyncFileExists(string path);
///
/// Begins an asynchronous operation to check if a file exists on the
/// server by taking a file listing of the parent directory in the path
/// and comparing the results the path supplied.
///
/// The full or relative path to the file
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginFileExists(string path, AsyncCallback callback, object state) {
AsyncFileExists func;
IAsyncResult ar = (func = new AsyncFileExists(FileExists)).BeginInvoke(path, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
/// True if the file exists, false otherwise
///
public bool EndFileExists(IAsyncResult ar) {
return GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Checks if a file exists on the server asynchronously.
///
/// The full or relative path to the file
/// True if the file exists, false otherwise
public async Task FileExistsAsync(string path)
{
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(FileExistsAsync), new object[] { path });
// calc the absolute filepath
path = await GetAbsolutePathAsync(path.GetFtpPath());
// since FTP does not include a specific command to check if a file exists
// here we check if file exists by attempting to get its filesize (SIZE)
if (HasFeature(FtpCapability.SIZE))
{
FtpReply reply = await ExecuteAsync("SIZE " + path);
char ch = reply.Code[0];
if (ch == '2')
{
return true;
}
if (ch == '5' && IsKnownError(reply.Message, fileNotFoundStrings))
{
return false;
}
}
// check if file exists by attempting to get its date modified (MDTM)
if (HasFeature(FtpCapability.MDTM))
{
FtpReply reply = await ExecuteAsync("MDTM " + path);
char ch = reply.Code[0];
if (ch == '2')
{
return true;
}
if (ch == '5' && IsKnownError(reply.Message, fileNotFoundStrings))
{
return false;
}
}
// check if file exists by getting a name listing (NLST)
string[] fileList = await GetNameListingAsync(path.GetFtpDirectoryName());
string pathName = path.GetFtpFileName();
if (fileList.Contains(pathName))
{
return true;
}
// check if file exists by attempting to download it (RETR)
/*try {
Stream stream = OpenRead(path);
stream.Close();
return true;
} catch (FtpException ex) {
}*/
return false;
}
#endif
#endregion
#region Create Directory
///
/// Creates a directory on the server. If the preceding
/// directories do not exist, then they are created.
///
/// The full or relative path to the new remote directory
///
public void CreateDirectory(string path) {
CreateDirectory(path, true);
}
///
/// Creates a directory on the server
///
/// The full or relative path to the new remote directory
/// Try to force all non-existent pieces of the path to be created
///
public void CreateDirectory(string path, bool force) {
// dont verify args as blank/null path is OK
//if (path.IsBlank())
// throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("CreateDirectory", new object[] { path, force });
FtpReply reply;
string ftppath = path.GetFtpPath();
if (ftppath == "." || ftppath == "./" || ftppath == "/")
return;
#if !CORE14
lock (m_lock) {
#endif
path = path.GetFtpPath().TrimEnd('/');
if (force && !DirectoryExists(path.GetFtpDirectoryName())) {
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Create non-existent parent directory: " + path.GetFtpDirectoryName());
CreateDirectory(path.GetFtpDirectoryName(), true);
} else if (DirectoryExists(path))
return;
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "CreateDirectory " + ftppath);
if (!(reply = Execute("MKD " + ftppath)).Success)
throw new FtpCommandException(reply);
#if !CORE14
}
#endif
}
#if !CORE
delegate void AsyncCreateDirectory(string path, bool force);
///
/// Begins an asynchronous operation to create a remote directory. If the preceding
/// directories do not exist, then they are created.
///
/// The full or relative path to the new remote directory
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginCreateDirectory(string path, AsyncCallback callback, object state) {
return BeginCreateDirectory(path, true, callback, state);
}
///
/// Begins an asynchronous operation to create a remote directory
///
/// The full or relative path to the new remote directory
/// Try to create the whole path if the preceding directories do not exist
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginCreateDirectory(string path, bool force, AsyncCallback callback, object state) {
AsyncCreateDirectory func;
IAsyncResult ar;
ar = (func = new AsyncCreateDirectory(CreateDirectory)).BeginInvoke(path, force, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
///
public void EndCreateDirectory(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Creates a remote directory asynchronously
///
/// The full or relative path to the new remote directory
/// Try to create the whole path if the preceding directories do not exist
public async Task CreateDirectoryAsync(string path, bool force)
{
// dont verify args as blank/null path is OK
//if (path.IsBlank())
// throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(CreateDirectoryAsync), new object[] { path, force });
FtpReply reply;
string ftppath = path.GetFtpPath();
if (ftppath == "." || ftppath == "./" || ftppath == "/")
return;
path = path.GetFtpPath().TrimEnd('/');
if (force && !await DirectoryExistsAsync(path.GetFtpDirectoryName()))
{
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Create non-existent parent directory: " + path.GetFtpDirectoryName());
await CreateDirectoryAsync(path.GetFtpDirectoryName(), true);
}
else if (await DirectoryExistsAsync(path))
return;
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "CreateDirectory " + ftppath);
if (!(reply = await ExecuteAsync("MKD " + ftppath)).Success)
throw new FtpCommandException(reply);
}
///
/// Creates a remote directory asynchronously. If the preceding
/// directories do not exist, then they are created.
///
/// The full or relative path to the new remote directory
public Task CreateDirectoryAsync(string path) {
return CreateDirectoryAsync(path, true);
}
#endif
#endregion
#region Rename File/Directory
///
/// Renames an object on the remote file system.
/// Low level method that should NOT be used in most cases. Prefer MoveFile() and MoveDirectory().
/// Throws exceptions if the file does not exist, or if the destination file already exists.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
///
public void Rename(string path, string dest) {
FtpReply reply;
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (dest.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "dest");
#if !CORE14
lock (m_lock) {
#endif
FtpTrace.WriteFunc("Rename", new object[] { path, dest });
// calc the absolute filepaths
path = GetAbsolutePath(path.GetFtpPath());
dest = GetAbsolutePath(dest.GetFtpPath());
if (!(reply = Execute("RNFR " + path)).Success)
throw new FtpCommandException(reply);
if (!(reply = Execute("RNTO " + dest)).Success)
throw new FtpCommandException(reply);
#if !CORE14
}
#endif
}
#if !CORE
delegate void AsyncRename(string path, string dest);
///
/// Begins an asynchronous operation to rename an object on the remote file system.
/// Low level method that should NOT be used in most cases. Prefer MoveFile() and MoveDirectory().
/// Throws exceptions if the file does not exist, or if the destination file already exists.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginRename(string path, string dest, AsyncCallback callback, object state) {
AsyncRename func;
IAsyncResult ar;
ar = (func = new AsyncRename(Rename)).BeginInvoke(path, dest, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
///
public void EndRename(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Renames an object on the remote file system asynchronously.
/// Low level method that should NOT be used in most cases. Prefer MoveFile() and MoveDirectory().
/// Throws exceptions if the file does not exist, or if the destination file already exists.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
public async Task RenameAsync(string path, string dest)
{
FtpReply reply;
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (dest.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "dest");
FtpTrace.WriteFunc(nameof(RenameAsync), new object[] { path, dest });
// calc the absolute filepaths
path = await GetAbsolutePathAsync(path.GetFtpPath());
dest = await GetAbsolutePathAsync(dest.GetFtpPath());
if (!(reply = await ExecuteAsync("RNFR " + path)).Success)
throw new FtpCommandException(reply);
if (!(reply = await ExecuteAsync("RNTO " + dest)).Success)
throw new FtpCommandException(reply);
}
#endif
#endregion
#region Move File
///
/// Moves a file on the remote file system from one directory to another.
/// Always checks if the source file exists. Checks if the dest file exists based on the `existsMode` parameter.
/// Only throws exceptions for critical errors.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
/// Should we check if the dest file exists? And if it does should we overwrite/skip the operation?
/// Whether the file was moved
public bool MoveFile(string path, string dest, FtpExists existsMode = FtpExists.Overwrite) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (dest.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "dest");
FtpTrace.WriteFunc("MoveFile", new object[] { path, dest, existsMode });
if (FileExists(path)) {
// check if dest file exists and act accordingly
if (existsMode != FtpExists.NoCheck) {
bool destExists = FileExists(dest);
if (destExists) {
switch (existsMode) {
case FtpExists.Overwrite:
DeleteFile(dest);
break;
case FtpExists.Skip:
return false;
}
}
}
// move the file
Rename(path, dest);
return true;
}
return false;
}
#if !CORE
delegate bool AsyncMoveFile(string path, string dest, FtpExists existsMode);
///
/// Begins an asynchronous operation to move a file on the remote file system, from one directory to another.
/// Always checks if the source file exists. Checks if the dest file exists based on the `existsMode` parameter.
/// Only throws exceptions for critical errors.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
/// Should we check if the dest file exists? And if it does should we overwrite/skip the operation?
/// Async callback
/// State object
/// IAsyncResult
public IAsyncResult BeginMoveFile(string path, string dest, FtpExists existsMode, AsyncCallback callback, object state) {
AsyncMoveFile func;
IAsyncResult ar;
ar = (func = new AsyncMoveFile(MoveFile)).BeginInvoke(path, dest, existsMode, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
public void EndMoveFile(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Moves a file asynchronously on the remote file system from one directory to another.
/// Always checks if the source file exists. Checks if the dest file exists based on the `existsMode` parameter.
/// Only throws exceptions for critical errors.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
/// Should we check if the dest file exists? And if it does should we overwrite/skip the operation?
/// Whether the file was moved
public async Task MoveFileAsync(string path, string dest, FtpExists existsMode = FtpExists.Overwrite) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (dest.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "dest");
FtpTrace.WriteFunc(nameof(MoveFileAsync), new object[] { path, dest, existsMode });
if (await FileExistsAsync(path))
{
// check if dest file exists and act accordingly
if (existsMode != FtpExists.NoCheck)
{
bool destExists = await FileExistsAsync(dest);
if (destExists)
{
switch (existsMode)
{
case FtpExists.Overwrite:
await DeleteFileAsync(dest);
break;
case FtpExists.Skip:
return false;
}
}
}
// move the file
await RenameAsync(path, dest);
return true;
}
return false;
}
#endif
#endregion
#region Move Directory
///
/// Moves a directory on the remote file system from one directory to another.
/// Always checks if the source directory exists. Checks if the dest directory exists based on the `existsMode` parameter.
/// Only throws exceptions for critical errors.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
/// Should we check if the dest directory exists? And if it does should we overwrite/skip the operation?
/// Whether the directory was moved
public bool MoveDirectory(string path, string dest, FtpExists existsMode = FtpExists.Overwrite) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (dest.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "dest");
FtpTrace.WriteFunc("MoveDirectory", new object[] { path, dest, existsMode });
if (DirectoryExists(path)) {
// check if dest directory exists and act accordingly
if (existsMode != FtpExists.NoCheck) {
bool destExists = DirectoryExists(dest);
if (destExists) {
switch (existsMode) {
case FtpExists.Overwrite:
DeleteDirectory(dest);
break;
case FtpExists.Skip:
return false;
}
}
}
// move the directory
Rename(path, dest);
return true;
}
return false;
}
#if !CORE
delegate bool AsyncMoveDirectory(string path, string dest, FtpExists existsMode);
///
/// Begins an asynchronous operation to move a directory on the remote file system, from one directory to another.
/// Always checks if the source directory exists. Checks if the dest directory exists based on the `existsMode` parameter.
/// Only throws exceptions for critical errors.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
/// Should we check if the dest directory exists? And if it does should we overwrite/skip the operation?
/// Async callback
/// State object
/// IAsyncResult
public IAsyncResult BeginMoveDirectory(string path, string dest, FtpExists existsMode, AsyncCallback callback, object state) {
AsyncMoveDirectory func;
IAsyncResult ar;
ar = (func = new AsyncMoveDirectory(MoveDirectory)).BeginInvoke(path, dest, existsMode, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
public void EndMoveDirectory(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Moves a directory asynchronously on the remote file system from one directory to another.
/// Always checks if the source directory exists. Checks if the dest directory exists based on the `existsMode` parameter.
/// Only throws exceptions for critical errors.
///
/// The full or relative path to the object
/// The new full or relative path including the new name of the object
/// Should we check if the dest directory exists? And if it does should we overwrite/skip the operation?
/// Whether the directory was moved
public async Task MoveDirectoryAsync(string path, string dest, FtpExists existsMode = FtpExists.Overwrite) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (dest.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "dest");
FtpTrace.WriteFunc(nameof(MoveDirectoryAsync), new object[] { path, dest, existsMode });
if (await DirectoryExistsAsync(path))
{
// check if dest directory exists and act accordingly
if (existsMode != FtpExists.NoCheck)
{
bool destExists = await DirectoryExistsAsync(dest);
if (destExists)
{
switch (existsMode)
{
case FtpExists.Overwrite:
await DeleteDirectoryAsync(dest);
break;
case FtpExists.Skip:
return false;
}
}
}
// move the directory
await RenameAsync(path, dest);
return true;
}
return false;
}
#endif
#endregion
#region File Permissions / Chmod
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The permissions in CHMOD format
public void SetFilePermissions(string path, int permissions) {
FtpReply reply;
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
#if !CORE14
lock (m_lock) {
#endif
FtpTrace.WriteFunc("SetFilePermissions", new object[] { path, permissions });
if (!(reply = Execute("SITE CHMOD " + permissions.ToString() + " " + path.GetFtpPath())).Success)
throw new FtpCommandException(reply);
#if !CORE14
}
#endif
}
#if ASYNC
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The permissions in CHMOD format
public async Task SetFilePermissionsAsync(string path, int permissions)
{
FtpReply reply;
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(SetFilePermissionsAsync), new object[] { path, permissions });
if (!(reply = await ExecuteAsync("SITE CHMOD " + permissions.ToString() + " " + path.GetFtpPath())).Success)
throw new FtpCommandException(reply);
}
#endif
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The permissions in CHMOD format
public void Chmod(string path, int permissions) {
SetFilePermissions(path, permissions);
}
#if ASYNC
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The permissions in CHMOD format
public Task ChmodAsync(string path, int permissions)
{
return SetFilePermissionsAsync(path, permissions);
}
#endif
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The owner permissions
/// The group permissions
/// The other permissions
public void SetFilePermissions(string path, FtpPermission owner, FtpPermission group, FtpPermission other) {
SetFilePermissions(path, CalcChmod(owner, group, other));
}
#if ASYNC
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The owner permissions
/// The group permissions
/// The other permissions
public Task SetFilePermissionsAsync(string path, FtpPermission owner, FtpPermission group, FtpPermission other)
{
return SetFilePermissionsAsync(path, CalcChmod(owner, group, other));
}
#endif
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The owner permissions
/// The group permissions
/// The other permissions
public void Chmod(string path, FtpPermission owner, FtpPermission group, FtpPermission other) {
SetFilePermissions(path, owner, group, other);
}
#if ASYNC
///
/// Modify the permissions of the given file/folder.
/// Only works on *NIX systems, and not on Windows/IIS servers.
/// Only works if the FTP server supports the SITE CHMOD command
/// (requires the CHMOD extension to be installed and enabled).
/// Throws FtpCommandException if there is an issue.
///
/// The full or relative path to the item
/// The owner permissions
/// The group permissions
/// The other permissions
public Task ChmodAsync(string path, FtpPermission owner, FtpPermission group, FtpPermission other)
{
return SetFilePermissionsAsync(path, owner, group, other);
}
#endif
///
/// Retrieve the permissions of the given file/folder as an FtpListItem object with all "Permission" properties set.
/// Throws FtpCommandException if there is an issue.
/// Returns null if the server did not specify a permission value.
/// Use `GetChmod` if you required the integer value instead.
///
/// The full or relative path to the item
public FtpListItem GetFilePermissions(string path) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("GetFilePermissions", new object[] { path });
string fullPath = path.GetFtpPath();
foreach (FtpListItem i in GetListing(path)) {
if (i.FullName == fullPath) {
return i;
}
}
return null;
}
#if ASYNC
///
/// Retrieve the permissions of the given file/folder as an FtpListItem object with all "Permission" properties set.
/// Throws FtpCommandException if there is an issue.
/// Returns null if the server did not specify a permission value.
/// Use `GetChmod` if you required the integer value instead.
///
/// The full or relative path to the item
public async Task GetFilePermissionsAsync(string path)
{
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(GetFilePermissionsAsync), new object[] { path });
string fullPath = path.GetFtpPath();
foreach (FtpListItem i in await GetListingAsync(path))
{
if (i.FullName == fullPath)
{
return i;
}
}
return null;
}
#endif
///
/// Retrieve the permissions of the given file/folder as an integer in the CHMOD format.
/// Throws FtpCommandException if there is an issue.
/// Returns 0 if the server did not specify a permission value.
/// Use `GetFilePermissions` if you required the permissions in the FtpPermission format.
///
/// The full or relative path to the item
public int GetChmod(string path) {
FtpListItem item = GetFilePermissions(path);
return item != null ? item.Chmod : 0;
}
#if ASYNC
///
/// Retrieve the permissions of the given file/folder as an integer in the CHMOD format.
/// Throws FtpCommandException if there is an issue.
/// Returns 0 if the server did not specify a permission value.
/// Use `GetFilePermissions` if you required the permissions in the FtpPermission format.
///
/// The full or relative path to the item
public async Task GetChmodAsync(string path)
{
FtpListItem item = await GetFilePermissionsAsync(path);
return item != null ? item.Chmod : 0;
}
#endif
#endregion
#region Dereference Link
///
/// Recursively dereferences a symbolic link. See the
/// MaximumDereferenceCount property for controlling
/// how deep this method will recurse before giving up.
///
/// The symbolic link
/// FtpListItem, null if the link can't be dereferenced
///
public FtpListItem DereferenceLink(FtpListItem item) {
return DereferenceLink(item, MaximumDereferenceCount);
}
///
/// Recursively dereferences a symbolic link
///
/// The symbolic link
/// The maximum depth of recursion that can be performed before giving up.
/// FtpListItem, null if the link can't be dereferenced
///
public FtpListItem DereferenceLink(FtpListItem item, int recMax) {
FtpTrace.WriteFunc("DereferenceLink", new object[] { item.FullName, recMax });
int count = 0;
return DereferenceLink(item, recMax, ref count);
}
///
/// Derefence a FtpListItem object
///
/// The item to derefence
/// Maximum recursive calls
/// Counter
/// FtpListItem, null if the link can't be dereferenced
///
FtpListItem DereferenceLink(FtpListItem item, int recMax, ref int count) {
if (item.Type != FtpFileSystemObjectType.Link)
throw new FtpException("You can only derefernce a symbolic link. Please verify the item type is Link.");
if (item.LinkTarget == null)
throw new FtpException("The link target was null. Please check this before trying to dereference the link.");
foreach (FtpListItem obj in GetListing(item.LinkTarget.GetFtpDirectoryName(), FtpListOption.ForceList)) {
if (item.LinkTarget == obj.FullName) {
if (obj.Type == FtpFileSystemObjectType.Link) {
if (++count == recMax)
return null;
return DereferenceLink(obj, recMax, ref count);
}
if (HasFeature(FtpCapability.MDTM)) {
DateTime modify = GetModifiedTime(obj.FullName);
if (modify != DateTime.MinValue)
obj.Modified = modify;
}
if (obj.Type == FtpFileSystemObjectType.File && obj.Size < 0 && HasFeature(FtpCapability.SIZE))
obj.Size = GetFileSize(obj.FullName);
return obj;
}
}
return null;
}
#if !CORE
delegate FtpListItem AsyncDereferenceLink(FtpListItem item, int recMax);
///
/// Begins an asynchronous operation to dereference a object
///
/// The item to dereference
/// Maximum recursive calls
/// AsyncCallback
/// State Object
/// IAsyncResult
///
public IAsyncResult BeginDereferenceLink(FtpListItem item, int recMax, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncDereferenceLink func;
ar = (func = new AsyncDereferenceLink(DereferenceLink)).BeginInvoke(item, recMax, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Begins an asynchronous operation to dereference a object. See the
/// property for controlling
/// how deep this method will recurse before giving up.
///
/// The item to dereference
/// AsyncCallback
/// State Object
/// IAsyncResult
///
public IAsyncResult BeginDereferenceLink(FtpListItem item, AsyncCallback callback, object state) {
return BeginDereferenceLink(item, MaximumDereferenceCount, callback, state);
}
///
/// Ends a call to
///
/// IAsyncResult
/// A , or null if the link can't be dereferenced
///
public FtpListItem EndDereferenceLink(IAsyncResult ar) {
return GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Derefence a FtpListItem object
///
/// The item to derefence
/// Maximum recursive calls
/// Counter
/// FtpListItem, null if the link can't be dereferenced
async Task DereferenceLinkAsync(FtpListItem item, int recMax, IntRef count)
{
if (item.Type != FtpFileSystemObjectType.Link)
throw new FtpException("You can only derefernce a symbolic link. Please verify the item type is Link.");
if (item.LinkTarget == null)
throw new FtpException("The link target was null. Please check this before trying to dereference the link.");
foreach (FtpListItem obj in GetListing(item.LinkTarget.GetFtpDirectoryName(), FtpListOption.ForceList))
{
if (item.LinkTarget == obj.FullName)
{
if (obj.Type == FtpFileSystemObjectType.Link)
{
if (++count.Value == recMax)
return null;
return await DereferenceLinkAsync(obj, recMax, count);
}
if (HasFeature(FtpCapability.MDTM))
{
DateTime modify = GetModifiedTime(obj.FullName);
if (modify != DateTime.MinValue)
obj.Modified = modify;
}
if (obj.Type == FtpFileSystemObjectType.File && obj.Size < 0 && HasFeature(FtpCapability.SIZE))
obj.Size = GetFileSize(obj.FullName);
return obj;
}
}
return null;
}
///
/// Dereference a object asynchronously
///
/// The item to dereference
/// Maximum recursive calls
/// FtpListItem, null if the link can't be dereferenced
public Task DereferenceLinkAsync(FtpListItem item, int recMax) {
//TODO: Add cancellation support
FtpTrace.WriteFunc(nameof(DereferenceLinkAsync), new object[] { item.FullName, recMax });
IntRef count = new IntRef { Value = 0 };
return DereferenceLinkAsync(item, recMax, count);
}
///
/// Dereference a object asynchronously
///
/// The item to dereference
/// FtpListItem, null if the link can't be dereferenced
public Task DereferenceLinkAsync(FtpListItem item) {
//TODO: Add cancellation support
return DereferenceLinkAsync(item, MaximumDereferenceCount);
}
#endif
#endregion
#region Set Working Dir
///
/// Sets the work directory on the server
///
/// The path of the directory to change to
///
public void SetWorkingDirectory(string path) {
FtpTrace.WriteFunc("SetWorkingDirectory", new object[] { path });
FtpReply reply;
string ftppath = path.GetFtpPath();
if (ftppath == "." || ftppath == "./")
return;
#if !CORE14
lock (m_lock) {
#endif
if (!(reply = Execute("CWD " + ftppath)).Success)
throw new FtpCommandException(reply);
#if !CORE14
}
#endif
}
#if !CORE
delegate void AsyncSetWorkingDirectory(string path);
///
/// Begins an asynchronous operation to set the working directory on the server
///
/// The directory to change to
/// Async Callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginSetWorkingDirectory(string path, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncSetWorkingDirectory func;
ar = (func = new AsyncSetWorkingDirectory(SetWorkingDirectory)).BeginInvoke(path, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
///
public void EndSetWorkingDirectory(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Sets the working directory on the server asynchronously
///
/// The directory to change to
public async Task SetWorkingDirectoryAsync(string path)
{
//TODO: Add cancellation support
FtpTrace.WriteFunc(nameof(SetWorkingDirectoryAsync), new object[] { path });
FtpReply reply;
string ftppath = path.GetFtpPath();
if (ftppath == "." || ftppath == "./")
return;
if (!(reply = await ExecuteAsync("CWD " + ftppath)).Success)
throw new FtpCommandException(reply);
}
#endif
#endregion
#region Get Working Dir
///
/// Gets the current working directory
///
/// The current working directory, ./ if the response couldn't be parsed.
///
public string GetWorkingDirectory() {
FtpTrace.WriteFunc("GetWorkingDirectory");
FtpReply reply;
Match m;
#if !CORE14
lock (m_lock) {
#endif
if (!(reply = Execute("PWD")).Success)
throw new FtpCommandException(reply);
#if !CORE14
}
#endif
if ((m = Regex.Match(reply.Message, "\"(?.*)\"")).Success) {
return m.Groups["pwd"].Value;
}
// check for MODCOMP ftp path mentioned in forums: https://netftp.codeplex.com/discussions/444461
if ((m = Regex.Match(reply.Message, "PWD = (?.*)")).Success) {
return m.Groups["pwd"].Value;
}
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse working directory from: " + reply.Message);
return "./";
}
#if !CORE
delegate string AsyncGetWorkingDirectory();
///
/// Begins an asynchronous operation to get the working directory
///
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginGetWorkingDirectory(AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncGetWorkingDirectory func;
ar = (func = new AsyncGetWorkingDirectory(GetWorkingDirectory)).BeginInvoke(callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
/// The current working directory
///
public string EndGetWorkingDirectory(IAsyncResult ar) {
return GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Gets the current working directory asynchronously
///
/// The current working directory, ./ if the response couldn't be parsed.
public async Task GetWorkingDirectoryAsync() {
//TODO: Add cancellation support
FtpTrace.WriteFunc(nameof(GetWorkingDirectoryAsync));
FtpReply reply;
Match m;
if (!(reply = await ExecuteAsync("PWD")).Success)
throw new FtpCommandException(reply);
if ((m = Regex.Match(reply.Message, "\"(?.*)\"")).Success)
{
return m.Groups["pwd"].Value;
}
// check for MODCOMP ftp path mentioned in forums: https://netftp.codeplex.com/discussions/444461
if ((m = Regex.Match(reply.Message, "PWD = (?.*)")).Success)
{
return m.Groups["pwd"].Value;
}
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse working directory from: " + reply.Message);
return "./";
}
#endif
#endregion
#region Get File Size
///
/// Gets the size of a remote file
///
/// The full or relative path of the file
/// -1 if the command fails, otherwise the file size
///
public virtual long GetFileSize(string path) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("GetFileSize", new object[] { path });
FtpReply reply;
long length = 0;
#if !CORE14
lock (m_lock) {
#endif
// Switch to binary mode since some servers don't support SIZE command for ASCII files.
//
// NOTE: We do this inside the lock so we're guaranteed to switch it back to the original
// type in a thread-safe manner
var savedDataType = CurrentDataType;
if (savedDataType != FtpDataType.Binary) {
this.SetDataTypeInternal(FtpDataType.Binary);
}
if (!(reply = Execute("SIZE " + path.GetFtpPath())).Success)
length = -1;
else if (!long.TryParse(reply.Message, out length))
length = -1;
if (savedDataType != FtpDataType.Binary)
this.SetDataTypeInternal(savedDataType);
#if !CORE14
}
#endif
return length;
}
#if !CORE
delegate long AsyncGetFileSize(string path);
///
/// Begins an asynchronous operation to retrieve the size of a remote file
///
/// The full or relative path of the file
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginGetFileSize(string path, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncGetFileSize func;
ar = (func = new AsyncGetFileSize(GetFileSize)).BeginInvoke(path, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
/// The size of the file, -1 if there was a problem.
///
public long EndGetFileSize(IAsyncResult ar) {
return GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Retrieve the size of a remote file asynchronously
///
/// The full or relative path of the file
/// The size of the file, -1 if there was a problem.
public async Task GetFileSizeAsync(string path) {
//TODO: Add cancellation support
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(GetFileSizeAsync), new object[] { path });
FtpReply reply;
long length = 0;
// Switch to binary mode since some servers don't support SIZE command for ASCII files.
//
var savedDataType = CurrentDataType;
if (savedDataType != FtpDataType.Binary)
{
await this.SetDataTypeAsync(FtpDataType.Binary);
}
if (!(reply = await ExecuteAsync("SIZE " + path.GetFtpPath())).Success)
length = -1;
else if (!long.TryParse(reply.Message, out length))
length = -1;
if (savedDataType != FtpDataType.Binary)
await this.SetDataTypeAsync(savedDataType);
return length;
}
#endif
#endregion
#region Get Modified Time
///
/// Gets the modified time of a remote file
///
/// The full path to the file
/// Return the date in local timezone or UTC? Use FtpDate.Original to disable timezone conversion.
/// The modified time, or if there was a problem
///
public virtual DateTime GetModifiedTime(string path, FtpDate type = FtpDate.Original) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc("GetModifiedTime", new object[] { path, type });
DateTime date = DateTime.MinValue;
FtpReply reply;
#if !CORE14
lock (m_lock) {
#endif
// get modified date of a file
if ((reply = Execute("MDTM " + path.GetFtpPath())).Success) {
date = reply.Message.GetFtpDate(DateTimeStyles.AssumeUniversal);
// convert server timezone to UTC, based on the TimeOffset property
if (type != FtpDate.Original && m_listParser.hasTimeOffset) {
date = (date - m_listParser.timeOffset);
}
// convert to local time if wanted
#if !CORE
if (type == FtpDate.Local) {
date = TimeZone.CurrentTimeZone.ToLocalTime(date);
}
#endif
}
#if !CORE14
}
#endif
return date;
}
#if !CORE
delegate DateTime AsyncGetModifiedTime(string path, FtpDate type);
///
/// Begins an asynchronous operation to get the modified time of a remote file
///
/// The full path to the file
/// Return the date in local timezone or UTC? Use FtpDate.Original to disable timezone conversion.
/// Async callback
/// State object
/// IAsyncResult
///
public IAsyncResult BeginGetModifiedTime(string path, FtpDate type, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncGetModifiedTime func;
ar = (func = new AsyncGetModifiedTime(GetModifiedTime)).BeginInvoke(path, type, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
/// The modified time, or if there was a problem
///
public DateTime EndGetModifiedTime(IAsyncResult ar) {
return GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Gets the modified time of a remote file asynchronously
///
/// The full path to the file
/// Return the date in local timezone or UTC? Use FtpDate.Original to disable timezone conversion.
/// The modified time, or if there was a problem
public async Task GetModifiedTimeAsync(string path, FtpDate type = FtpDate.Original) {
//TODO: Add cancellation support
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
FtpTrace.WriteFunc(nameof(GetModifiedTimeAsync), new object[] { path, type });
DateTime date = DateTime.MinValue;
FtpReply reply;
// get modified date of a file
if ((reply = await ExecuteAsync("MDTM " + path.GetFtpPath())).Success)
{
date = reply.Message.GetFtpDate(DateTimeStyles.AssumeUniversal);
// convert server timezone to UTC, based on the TimeOffset property
if (type != FtpDate.Original && m_listParser.hasTimeOffset)
{
date = (date - m_listParser.timeOffset);
}
// convert to local time if wanted
#if !CORE
if (type == FtpDate.Local) {
date = TimeZone.CurrentTimeZone.ToLocalTime(date);
}
#endif
}
return date;
}
#endif
#endregion
#region Set Modified Time
///
/// Changes the modified time of a remote file
///
/// The full path to the file
/// The new modified date/time value
/// Is the date provided in local timezone or UTC? Use FtpDate.Original to disable timezone conversion.
public virtual void SetModifiedTime(string path, DateTime date, FtpDate type = FtpDate.Original) {
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (date == null)
throw new ArgumentException("Required parameter is null or blank.", "date");
FtpTrace.WriteFunc("SetModifiedTime", new object[] { path, date, type });
FtpReply reply;
#if !CORE14
lock (m_lock) {
#endif
// convert local to UTC if wanted
#if !CORE
if (type == FtpDate.Local) {
date = TimeZone.CurrentTimeZone.ToUniversalTime(date);
}
#endif
// convert UTC to server timezone, based on the TimeOffset property
if (type != FtpDate.Original && m_listParser.hasTimeOffset) {
date = (date + m_listParser.timeOffset);
}
// set modified date of a file
string timeStr = date.ToString("yyyyMMddHHmmss");
if ((reply = Execute("MFMT " + timeStr + " " + path.GetFtpPath())).Success) {
}
#if !CORE14
}
#endif
}
#if !CORE
delegate void AsyncSetModifiedTime(string path, DateTime date, FtpDate type);
///
/// Begins an asynchronous operation to get the modified time of a remote file
///
/// The full path to the file
/// The new modified date/time value
/// Is the date provided in local timezone or UTC? Use FtpDate.Original to disable timezone conversion.
/// Async callback
/// State object
/// IAsyncResult
public IAsyncResult BeginSetModifiedTime(string path, DateTime date, FtpDate type, AsyncCallback callback, object state) {
IAsyncResult ar;
AsyncSetModifiedTime func;
ar = (func = new AsyncSetModifiedTime(SetModifiedTime)).BeginInvoke(path, date, type, callback, state);
lock (m_asyncmethods) {
m_asyncmethods.Add(ar, func);
}
return ar;
}
///
/// Ends a call to
///
/// IAsyncResult returned from
/// The modified time, or if there was a problem
public void EndSetModifiedTime(IAsyncResult ar) {
GetAsyncDelegate(ar).EndInvoke(ar);
}
#endif
#if ASYNC
///
/// Gets the modified time of a remote file asynchronously
///
/// The full path to the file
/// The new modified date/time value
/// Is the date provided in local timezone or UTC? Use FtpDate.Original to disable timezone conversion.
public async Task SetModifiedTimeAsync(string path, DateTime date, FtpDate type = FtpDate.Original)
{
//TODO: Add cancellation support
// verify args
if (path.IsBlank())
throw new ArgumentException("Required parameter is null or blank.", "path");
if (date == null)
throw new ArgumentException("Required parameter is null or blank.", "date");
FtpTrace.WriteFunc(nameof(SetModifiedTimeAsync), new object[] { path, date, type });
FtpReply reply;
// convert local to UTC if wanted
#if !CORE
if (type == FtpDate.Local) {
date = TimeZone.CurrentTimeZone.ToUniversalTime(date);
}
#endif
// convert UTC to server timezone, based on the TimeOffset property
if (type != FtpDate.Original && m_listParser.hasTimeOffset)
{
date = (date + m_listParser.timeOffset);
}
// set modified date of a file
string timeStr = date.ToString("yyyyMMddHHmmss");
if ((reply = await ExecuteAsync("MFMT " + timeStr + " " + path.GetFtpPath())).Success)
{
}
}
#endif
#endregion
}
}