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 } }