public static bool HasFlag(this FtpVerify flags, FtpVerify flag)
 {
     return((flags & flag) == flag);
 }
        private async Task <bool> DownloadFileToFileAsync(string localPath, string remotePath, FtpLocalExists existsMode = FtpLocalExists.Append, FtpVerify verifyOptions = FtpVerify.None, IProgress <FtpProgress> progress = null, CancellationToken token = default(CancellationToken))
        {
            // verify args
            if (localPath.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localPath");
            }

            if (remotePath.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "remotePath");
            }

            LogFunc("DownloadFileAsync", new object[] { localPath, remotePath, existsMode, verifyOptions });


            var outStreamFileMode = FileMode.Create;

            // skip downloading if the local file exists
#if CORE
            if (existsMode == FtpLocalExists.Append && await Task.Run(() => File.Exists(localPath), token))
            {
                if ((await GetFileSizeAsync(remotePath, token)).Equals((await Task.Run(() => new FileInfo(localPath), token)).Length))
                {
#else
            if (existsMode == FtpLocalExists.Append && File.Exists(localPath))
            {
                if ((await GetFileSizeAsync(remotePath)).Equals(new FileInfo(localPath).Length))
                {
#endif
                    LogStatus(FtpTraceLevel.Info, "Append is enabled => Local file size matches size on server => skipping");
                    return(false);
                }
                else
                {
                    outStreamFileMode = FileMode.Append;
                }
            }
#if CORE
            else if (existsMode == FtpLocalExists.Skip && await Task.Run(() => File.Exists(localPath), token))
            {
#else
            else if (existsMode == FtpLocalExists.Skip && File.Exists(localPath))
            {
#endif
                LogStatus(FtpTraceLevel.Info, "Skip is selected => Local file exists => skipping");
                return(false);
            }

            try {
                // create the folders
                var dirPath = Path.GetDirectoryName(localPath);
#if CORE
                if (!string.IsNullOrWhiteSpace(dirPath) && !await Task.Run(() => Directory.Exists(dirPath), token))
                {
#else
                if (!string.IsNullOrWhiteSpace(dirPath) && !Directory.Exists(dirPath))
                {
#endif
                    Directory.CreateDirectory(dirPath);
                }
            }
            catch (Exception ex1) {
                // catch errors creating directory
                throw new FtpException("Error while crated directories. See InnerException for more info.", ex1);
            }

            bool downloadSuccess;
            var verified     = true;
            var attemptsLeft = verifyOptions.HasFlag(FtpVerify.Retry) ? m_retryAttempts : 1;
            do
            {
                // download the file from server
                using (var outStream = new FileStream(localPath, outStreamFileMode, FileAccess.Write, FileShare.None, 4096, true)) {
                    // download the file straight to a file stream
                    downloadSuccess = await DownloadFileInternalAsync(remotePath, outStream, await Task.Run(() => File.Exists(localPath), token)?(await Task.Run(() => new FileInfo(localPath), token)).Length : 0, progress, token);

                    attemptsLeft--;
                }

                // if verification is needed
                if (downloadSuccess && verifyOptions != FtpVerify.None)
                {
                    verified = await VerifyTransferAsync(localPath, remotePath, token);

                    LogStatus(FtpTraceLevel.Info, "File Verification: " + (verified ? "PASS" : "FAIL"));
#if DEBUG
                    if (!verified && attemptsLeft > 0)
                    {
                        LogStatus(FtpTraceLevel.Verbose, "Retrying due to failed verification." + (existsMode == FtpLocalExists.Append ? "  Overwrite will occur." : "") + "  " + attemptsLeft + " attempts remaining");
                    }
#endif
                }
            } while (!verified && attemptsLeft > 0);

            if (downloadSuccess && !verified && verifyOptions.HasFlag(FtpVerify.Delete))
            {
                File.Delete(localPath);
            }

            if (downloadSuccess && !verified && verifyOptions.HasFlag(FtpVerify.Throw))
            {
                throw new FtpException("Downloaded file checksum value does not match remote file");
            }

            return(downloadSuccess && verified);
        }
        /// <summary>
        /// Downloads the specified files into a local single directory.
        /// High-level API that takes care of various edge cases internally.
        /// Supports very large files since it downloads data in chunks.
        /// Same speed as <see cref="o:DownloadFile"/>.
        /// </summary>
        /// <param name="localDir">The full or relative path to the directory that files will be downloaded into.</param>
        /// <param name="remotePaths">The full or relative paths to the files on the server</param>
        /// <param name="existsMode">If the file exists on disk, should we skip it, resume the download or restart the download?</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful download and what to do if it fails verification (See Remarks)</param>
        /// <param name="errorHandling">Used to determine how errors are handled</param>
        /// <returns>The count of how many files were downloaded successfully. When existing files are skipped, they are not counted.</returns>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically switch to true for subsequent attempts.
        /// If <see cref="FtpVerify.Throw"/> is set and <see cref="FtpError.Throw"/> is <i>not set</i>, then individual verification errors will not cause an exception
        /// to propagate from this method.
        /// </remarks>
        public int DownloadFiles(string localDir, IEnumerable <string> remotePaths, FtpLocalExists existsMode = FtpLocalExists.Overwrite, FtpVerify verifyOptions = FtpVerify.None,
                                 FtpError errorHandling = FtpError.None)
        {
            // verify args
            if (!errorHandling.IsValidCombination())
            {
                throw new ArgumentException("Invalid combination of FtpError flags.  Throw & Stop cannot be combined");
            }

            if (localDir.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localDir");
            }

            LogFunc("DownloadFiles", new object[] { localDir, remotePaths, existsMode, verifyOptions });

            var errorEncountered    = false;
            var successfulDownloads = new List <string>();

            // ensure ends with slash
            localDir = !localDir.EndsWith(Path.DirectorySeparatorChar.ToString()) ? localDir + Path.DirectorySeparatorChar.ToString() : localDir;

            foreach (var remotePath in remotePaths)
            {
                // calc local path
                var localPath = localDir + remotePath.GetFtpFileName();

                // try to download it
                try {
                    var ok = DownloadFileToFile(localPath, remotePath, existsMode, verifyOptions, null);
                    if (ok)
                    {
                        successfulDownloads.Add(localPath);
                    }
                    else if ((int)errorHandling > 1)
                    {
                        errorEncountered = true;
                        break;
                    }
                }
                catch (Exception ex) {
                    LogStatus(FtpTraceLevel.Error, "Failed to download " + remotePath + ". Error: " + ex);
                    if (errorHandling.HasFlag(FtpError.Stop))
                    {
                        errorEncountered = true;
                        break;
                    }

                    if (errorHandling.HasFlag(FtpError.Throw))
                    {
                        if (errorHandling.HasFlag(FtpError.DeleteProcessed))
                        {
                            PurgeSuccessfulDownloads(successfulDownloads);
                        }

                        throw new FtpException("An error occurred downloading file(s).  See inner exception for more info.", ex);
                    }
                }
            }

            if (errorEncountered)
            {
                //Delete any successful uploads if needed
                if (errorHandling.HasFlag(FtpError.DeleteProcessed))
                {
                    PurgeSuccessfulDownloads(successfulDownloads);
                    successfulDownloads.Clear();                     //forces return of 0
                }

                //Throw generic error because requested
                if (errorHandling.HasFlag(FtpError.Throw))
                {
                    throw new FtpException("An error occurred downloading one or more files.  Refer to trace output if available.");
                }
            }

            return(successfulDownloads.Count);
        }
        private bool DownloadFileToFile(string localPath, string remotePath, FtpLocalExists existsMode, FtpVerify verifyOptions, Action <FtpProgress> progress)
        {
            var outStreamFileMode = FileMode.Create;

            // skip downloading if local file size matches
            if (existsMode == FtpLocalExists.Append && File.Exists(localPath))
            {
                if (GetFileSize(remotePath).Equals(new FileInfo(localPath).Length))
                {
                    LogStatus(FtpTraceLevel.Info, "Append is selected => Local file size matches size on server => skipping");
                    return(false);
                }
                else
                {
                    outStreamFileMode = FileMode.Append;
                }
            }
            else if (existsMode == FtpLocalExists.Skip && File.Exists(localPath))
            {
                LogStatus(FtpTraceLevel.Info, "Skip is selected => Local file exists => skipping");
                return(false);
            }

            try {
                // create the folders
                var dirPath = Path.GetDirectoryName(localPath);
                if (!FtpExtensions.IsNullOrWhiteSpace(dirPath) && !Directory.Exists(dirPath))
                {
                    Directory.CreateDirectory(dirPath);
                }
            }
            catch (Exception ex1) {
                // catch errors creating directory
                throw new FtpException("Error while creating directories. See InnerException for more info.", ex1);
            }

            bool downloadSuccess;
            var  verified     = true;
            var  attemptsLeft = verifyOptions.HasFlag(FtpVerify.Retry) ? m_retryAttempts : 1;

            do
            {
                // download the file from server
                using (var outStream = new FileStream(localPath, outStreamFileMode, FileAccess.Write, FileShare.None)) {
                    // download the file straight to a file stream
                    downloadSuccess = DownloadFileInternal(remotePath, outStream, File.Exists(localPath) ? new FileInfo(localPath).Length : 0, progress);
                    attemptsLeft--;
                }

                // if verification is needed
                if (downloadSuccess && verifyOptions != FtpVerify.None)
                {
                    verified = VerifyTransfer(localPath, remotePath);
                    LogLine(FtpTraceLevel.Info, "File Verification: " + (verified ? "PASS" : "FAIL"));
#if DEBUG
                    if (!verified && attemptsLeft > 0)
                    {
                        LogStatus(FtpTraceLevel.Verbose, "Retrying due to failed verification." + (existsMode == FtpLocalExists.Overwrite ? "  Overwrite will occur." : "") + "  " + attemptsLeft + " attempts remaining");
                    }
#endif
                }
            } while (!verified && attemptsLeft > 0);

            if (downloadSuccess && !verified && verifyOptions.HasFlag(FtpVerify.Delete))
            {
                File.Delete(localPath);
            }

            if (downloadSuccess && !verified && verifyOptions.HasFlag(FtpVerify.Throw))
            {
                throw new FtpException("Downloaded file checksum value does not match remote file");
            }

            return(downloadSuccess && verified);
        }
        /// <summary>
        /// Downloads the specified file onto the local file system asynchronously.
        /// High-level API that takes care of various edge cases internally.
        /// Supports very large files since it downloads data in chunks.
        /// </summary>
        /// <param name="localPath">The full or relative path to the file on the local file system</param>
        /// <param name="remotePath">The full or relative path to the file on the server</param>
        /// <param name="existsMode">Overwrite if you want the local file to be overwritten if it already exists. Append will also create a new file if it dosen't exists</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful download and what to do if it fails verification (See Remarks)</param>
        /// <param name="progress">Provide an implementation of IProgress to track download progress. The value provided is in the range 0 to 100, indicating the percentage of the file transferred. If the progress is indeterminate, -1 is sent.</param>
        /// <param name="token">Cancellation Token</param>
        /// <returns>If true then the file was downloaded, false otherwise.</returns>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically be set to true for subsequent attempts.
        /// </remarks>
        public async Task <bool> DownloadFileAsync(string localPath, string remotePath, FtpLocalExists existsMode = FtpLocalExists.Append, FtpVerify verifyOptions = FtpVerify.None, IProgress <FtpProgress> progress = null, CancellationToken token = default(CancellationToken))
        {
            // verify args
            if (localPath.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localPath");
            }

            if (remotePath.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "remotePath");
            }

            LogFunc("DownloadFileAsync", new object[] { localPath, remotePath, existsMode, verifyOptions });

            return(await DownloadFileToFileAsync(localPath, remotePath, existsMode, verifyOptions, progress, token));
        }
        /// <summary>
        /// Downloads the specified files into a local single directory.
        /// High-level API that takes care of various edge cases internally.
        /// Supports very large files since it downloads data in chunks.
        /// Same speed as <see cref="o:DownloadFile"/>.
        /// </summary>
        /// <param name="localDir">The full or relative path to the directory that files will be downloaded.</param>
        /// <param name="remotePaths">The full or relative paths to the files on the server</param>
        /// <param name="existsMode">Overwrite if you want the local file to be overwritten if it already exists. Append will also create a new file if it dosen't exists</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful download and what to do if it fails verification (See Remarks)</param>
        /// <param name="errorHandling">Used to determine how errors are handled</param>
        /// <param name="token">The token to monitor for cancellation requests</param>
        /// <returns>The count of how many files were downloaded successfully. When existing files are skipped, they are not counted.</returns>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically be set to true for subsequent attempts.
        /// If <see cref="FtpVerify.Throw"/> is set and <see cref="FtpError.Throw"/> is <i>not set</i>, then individual verification errors will not cause an exception
        /// to propagate from this method.
        /// </remarks>
        public async Task <int> DownloadFilesAsync(string localDir, IEnumerable <string> remotePaths, FtpLocalExists existsMode = FtpLocalExists.Overwrite,
                                                   FtpVerify verifyOptions = FtpVerify.None, FtpError errorHandling = FtpError.None, CancellationToken token = default(CancellationToken))
        {
            // verify args
            if (!errorHandling.IsValidCombination())
            {
                throw new ArgumentException("Invalid combination of FtpError flags.  Throw & Stop cannot be combined");
            }

            if (localDir.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localDir");
            }

            LogFunc("DownloadFilesAsync", new object[] { localDir, remotePaths, existsMode, verifyOptions });

            //check if cancellation was requested and throw to set TaskStatus state to Canceled
            token.ThrowIfCancellationRequested();
            var errorEncountered    = false;
            var successfulDownloads = new List <string>();

            // ensure ends with slash
            localDir = !localDir.EndsWith(Path.DirectorySeparatorChar.ToString()) ? localDir + Path.DirectorySeparatorChar.ToString() : localDir;

            foreach (var remotePath in remotePaths)
            {
                //check if cancellation was requested and throw to set TaskStatus state to Canceled
                token.ThrowIfCancellationRequested();

                // calc local path
                var localPath = localDir + remotePath.GetFtpFileName();

                // try to download it
                try {
                    bool ok = await DownloadFileToFileAsync(localPath, remotePath, existsMode, verifyOptions, token : token);

                    if (ok)
                    {
                        successfulDownloads.Add(localPath);
                    }
                    else if ((int)errorHandling > 1)
                    {
                        errorEncountered = true;
                        break;
                    }
                }
                catch (Exception ex) {
                    if (ex is OperationCanceledException)
                    {
                        LogStatus(FtpTraceLevel.Info, "Download cancellation requested");

                        //DO NOT SUPPRESS CANCELLATION REQUESTS -- BUBBLE UP!
                        throw;
                    }

                    if (errorHandling.HasFlag(FtpError.Stop))
                    {
                        errorEncountered = true;
                        break;
                    }

                    if (errorHandling.HasFlag(FtpError.Throw))
                    {
                        if (errorHandling.HasFlag(FtpError.DeleteProcessed))
                        {
                            PurgeSuccessfulDownloads(successfulDownloads);
                        }

                        throw new FtpException("An error occurred downloading file(s).  See inner exception for more info.", ex);
                    }
                }
            }

            if (errorEncountered)
            {
                //Delete any successful uploads if needed
                if (errorHandling.HasFlag(FtpError.DeleteProcessed))
                {
                    PurgeSuccessfulDownloads(successfulDownloads);
                    successfulDownloads.Clear();                     //forces return of 0
                }

                //Throw generic error because requested
                if (errorHandling.HasFlag(FtpError.Throw))
                {
                    throw new FtpException("An error occurred downloading one or more files.  Refer to trace output if available.");
                }
            }

            return(successfulDownloads.Count);
        }
        /// <summary>
        /// Downloads the specified file onto the local file system.
        /// High-level API that takes care of various edge cases internally.
        /// Supports very large files since it downloads data in chunks.
        /// </summary>
        /// <param name="localPath">The full or relative path to the file on the local file system</param>
        /// <param name="remotePath">The full or relative path to the file on the server</param>
        /// <param name="existsMode">If the file exists on disk, should we skip it, resume the download or restart the download?</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful download and what to do if it fails verification (See Remarks)</param>
        /// <param name="progress">Provide an implementation of IProgress to track download progress. The value provided is in the range 0 to 100, indicating the percentage of the file transferred. If the progress is indeterminate, -1 is sent.</param>
        /// <returns>If true then the file was downloaded, false otherwise.</returns>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically be set to true for subsequent attempts.
        /// </remarks>
        public bool DownloadFile(string localPath, string remotePath, FtpLocalExists existsMode = FtpLocalExists.Overwrite, FtpVerify verifyOptions = FtpVerify.None, Action <FtpProgress> progress = null)
        {
            // verify args
            if (localPath.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localPath");
            }

            if (remotePath.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "remotePath");
            }

            LogFunc("DownloadFile", new object[] { localPath, remotePath, existsMode, verifyOptions });

            return(DownloadFileToFile(localPath, remotePath, existsMode, verifyOptions, progress));
        }
        /// <summary>
        /// Upload all the files within the main directory
        /// </summary>
        private async Task UploadDirectoryFilesAsync(List <FtpResult> filesToUpload, FtpRemoteExists existsMode, FtpVerify verifyOptions, IProgress <FtpProgress> progress, FtpListItem[] remoteListing, CancellationToken token)
        {
            LogFunc("UploadDirectoryFilesAsync", new object[] { filesToUpload.Count + " files" });

            var r = -1;

            foreach (var result in filesToUpload)
            {
                r++;

                // absorb errors
                try {
                    // check if the file already exists on the server
                    var existsModeToUse = existsMode;
                    var fileExists      = FtpExtensions.FileExistsInListing(remoteListing, result.RemotePath);

                    // if we want to skip uploaded files and the file already exists, mark its skipped
                    if (existsMode == FtpRemoteExists.Skip && fileExists)
                    {
                        LogStatus(FtpTraceLevel.Info, "Skipped file that already exists: " + result.LocalPath);

                        result.IsSuccess = true;
                        result.IsSkipped = true;
                        continue;
                    }

                    // in any mode if the file does not exist, mark that exists check is not required
                    if (!fileExists)
                    {
                        existsModeToUse = existsMode == FtpRemoteExists.Append ? FtpRemoteExists.AppendNoCheck : FtpRemoteExists.NoCheck;
                    }

                    // create meta progress to store the file progress
                    var metaProgress = new FtpProgress(filesToUpload.Count, r);

                    // upload the file
                    var transferred = await UploadFileFromFileAsync(result.LocalPath, result.RemotePath, false, existsModeToUse, false, false, verifyOptions, token, progress, metaProgress);

                    result.IsSuccess = true;
                    result.IsSkipped = !transferred;
                }
                catch (Exception ex) {
                    LogStatus(FtpTraceLevel.Warn, "File failed to upload: " + result.LocalPath);

                    // mark that the file failed to upload
                    result.IsFailed  = true;
                    result.Exception = ex;
                }
            }
        }
        /// <summary>
        /// Uploads the specified directory onto the server.
        /// In Mirror mode, we will upload missing files, and delete any extra files from the server that are not present on disk. This is very useful when publishing an exact copy of a local folder onto an FTP server.
        /// In Update mode, we will only upload missing files and preserve any extra files on the server. This is useful when you want to simply upload missing files to a server.
        /// Only uploads the files and folders matching all the rules provided, if any.
        /// All exceptions during uploading are caught, and the exception is stored in the related FtpResult object.
        /// </summary>
        /// <param name="localFolder">The full path of the local folder on disk that you want to upload. If it does not exist, an empty result list is returned.</param>
        /// <param name="remoteFolder">The full path of the remote FTP folder to upload into. It is created if it does not exist.</param>
        /// <param name="mode">Mirror or Update mode, as explained above</param>
        /// <param name="existsMode">If the file exists on disk, should we skip it, resume the upload or restart the upload?</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful upload and what to do if it fails verification (See Remarks)</param>
        /// <param name="rules">Only files and folders that pass all these rules are downloaded, and the files that don't pass are skipped. In the Mirror mode, the files that fail the rules are also deleted from the local folder.</param>
        /// <param name="progress">Provide a callback to track upload progress.</param>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically switch to true for subsequent attempts.
        /// If <see cref="FtpVerify.Throw"/> is set and <see cref="FtpError.Throw"/> is <i>not set</i>, then individual verification errors will not cause an exception
        /// to propagate from this method.
        /// </remarks>
        /// <returns>
        /// Returns a listing of all the remote files, indicating if they were downloaded, skipped or overwritten.
        /// Returns a blank list if nothing was transfered. Never returns null.
        /// </returns>
        public List <FtpResult> UploadDirectory(string localFolder, string remoteFolder, FtpFolderSyncMode mode = FtpFolderSyncMode.Update,
                                                FtpRemoteExists existsMode = FtpRemoteExists.Skip, FtpVerify verifyOptions = FtpVerify.None, List <FtpRule> rules = null, Action <FtpProgress> progress = null)
        {
            if (localFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localFolder");
            }

            if (remoteFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "remoteFolder");
            }

            LogFunc("UploadDirectory", new object[] { localFolder, remoteFolder, mode, existsMode, verifyOptions, (rules.IsBlank() ? null : rules.Count + " rules") });

            var results = new List <FtpResult>();

            // ensure the local path ends with slash
            localFolder = localFolder.EnsurePostfix(Path.DirectorySeparatorChar.ToString());

            // cleanup the remote path
            remoteFolder = remoteFolder.GetFtpPath().EnsurePostfix("/");

            // if the dir does not exist, fail fast
            if (!Directory.Exists(localFolder))
            {
                return(results);
            }

            // flag to determine if existence checks are required
            var checkFileExistence = true;

            // ensure the remote dir exists
            if (!DirectoryExists(remoteFolder))
            {
                CreateDirectory(remoteFolder);
                checkFileExistence = false;
            }

            // collect paths of the files that should exist (lowercase for CI checks)
            var shouldExist = new Dictionary <string, bool>();

            // get all the folders in the local directory
            var dirListing = Directory.GetDirectories(localFolder, "*.*", SearchOption.AllDirectories);

            // get all the already existing files
            var remoteListing = checkFileExistence ? GetListing(remoteFolder, FtpListOption.Recursive) : null;

            // loop thru each folder and ensure it exists
            var dirsToUpload = GetSubDirectoriesToUpload(localFolder, remoteFolder, rules, results, dirListing);

            UploadSubDirectories(dirsToUpload);

            // get all the files in the local directory
            var fileListing = Directory.GetFiles(localFolder, "*.*", SearchOption.AllDirectories);

            // loop thru each file and transfer it
            var filesToUpload = GetFilesToUpload(localFolder, remoteFolder, rules, results, shouldExist, fileListing);

            UploadDirectoryFiles(filesToUpload, existsMode, verifyOptions, progress, remoteListing);

            // delete the extra remote files if in mirror mode and the directory was pre-existing
            DeleteExtraServerFiles(mode, shouldExist, remoteListing);

            return(results);
        }
Exemple #10
0
        /// <summary>
        /// Download all the listed files and folders from the main directory
        /// </summary>
        private async Task DownloadServerFilesAsync(List <FtpResult> toDownload, FtpLocalExists existsMode, FtpVerify verifyOptions, IProgress <FtpProgress> progress, CancellationToken token)
        {
            LogFunc(nameof(DownloadServerFilesAsync), new object[] { toDownload.Count + " files" });

            // per object to download
            var r = -1;

            foreach (var result in toDownload)
            {
                r++;

                if (result.Type == FtpFileSystemObjectType.File)
                {
                    // absorb errors
                    try {
                        // create meta progress to store the file progress
                        var metaProgress = new FtpProgress(toDownload.Count, r);

                        // download the file
                        var transferred = await DownloadFileToFileAsync(result.LocalPath, result.RemotePath, existsMode, verifyOptions, progress, token, metaProgress);

                        result.IsSuccess = transferred.IsSuccess();
                        result.IsSkipped = transferred == FtpStatus.Skipped;
                    }
                    catch (Exception ex) {
                        LogStatus(FtpTraceLevel.Warn, "File failed to download: " + result.RemotePath);

                        // mark that the file failed to download
                        result.IsFailed  = true;
                        result.Exception = ex;
                    }
                }
                else if (result.Type == FtpFileSystemObjectType.Directory)
                {
                    // absorb errors
                    try {
                        // create directory on local filesystem
                        // to ensure we download the blank remote dirs as well
                        var created = result.LocalPath.EnsureDirectory();
                        result.IsSuccess = true;
                        result.IsSkipped = !created;
                    }
                    catch (Exception ex) {
                        // mark that the file failed to download
                        result.IsFailed  = true;
                        result.Exception = ex;
                    }
                }
            }
        }
Exemple #11
0
        /// <summary>
        /// Downloads the specified directory onto the local file system.
        /// In Mirror mode, we will download missing files, and delete any extra files from disk that are not present on the server. This is very useful when creating an exact local backup of an FTP directory.
        /// In Update mode, we will only download missing files and preserve any extra files on disk. This is useful when you want to simply download missing files from an FTP directory.
        /// Only downloads the files and folders matching all the rules provided, if any.
        /// All exceptions during downloading are caught, and the exception is stored in the related FtpResult object.
        /// </summary>
        /// <param name="localFolder">The full path of the local folder on disk to download into. It is created if it does not exist.</param>
        /// <param name="remoteFolder">The full path of the remote FTP folder that you want to download. If it does not exist, an empty result list is returned.</param>
        /// <param name="mode">Mirror or Update mode, as explained above</param>
        /// <param name="existsMode">If the file exists on disk, should we skip it, resume the download or restart the download?</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful download and what to do if it fails verification (See Remarks)</param>
        /// <param name="rules">Only files and folders that pass all these rules are downloaded, and the files that don't pass are skipped. In the Mirror mode, the files that fail the rules are also deleted from the local folder.</param>
        /// <param name="progress">Provide a callback to track download progress.</param>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically switch to true for subsequent attempts.
        /// If <see cref="FtpVerify.Throw"/> is set and <see cref="FtpError.Throw"/> is <i>not set</i>, then individual verification errors will not cause an exception
        /// to propagate from this method.
        /// </remarks>
        /// <returns>
        /// Returns a listing of all the remote files, indicating if they were downloaded, skipped or overwritten.
        /// Returns a blank list if nothing was transfered. Never returns null.
        /// </returns>
        public List <FtpResult> DownloadDirectory(string localFolder, string remoteFolder, FtpFolderSyncMode mode = FtpFolderSyncMode.Update,
                                                  FtpLocalExists existsMode = FtpLocalExists.Skip, FtpVerify verifyOptions = FtpVerify.None, List <FtpRule> rules = null, Action <FtpProgress> progress = null)
        {
            if (localFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localFolder");
            }

            if (remoteFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "remoteFolder");
            }

            LogFunc(nameof(DownloadDirectory), new object[] { localFolder, remoteFolder, mode, existsMode, verifyOptions, (rules.IsBlank() ? null : rules.Count + " rules") });

            var results = new List <FtpResult>();

            // ensure the local path ends with slash
            localFolder = localFolder.EnsurePostfix(Path.DirectorySeparatorChar.ToString());

            // cleanup the remote path
            remoteFolder = remoteFolder.GetFtpPath().EnsurePostfix("/");

            // if the dir does not exist, fail fast
            if (!DirectoryExists(remoteFolder))
            {
                return(results);
            }

            // ensure the local dir exists
            localFolder.EnsureDirectory();

            // get all the files in the remote directory
            var listing = GetListing(remoteFolder, FtpListOption.Recursive | FtpListOption.Size);

            // collect paths of the files that should exist (lowercase for CI checks)
            var shouldExist = new Dictionary <string, bool>();

            // loop thru each file and transfer it
            var toDownload = GetFilesToDownload(localFolder, remoteFolder, rules, results, listing, shouldExist);

            DownloadServerFiles(toDownload, existsMode, verifyOptions, progress);

            // delete the extra local files if in mirror mode
            DeleteExtraLocalFiles(localFolder, mode, shouldExist);

            return(results);
        }
Exemple #12
0
        /// <summary>
        /// Downloads the specified directory onto the local file system.
        /// In Mirror mode, we will download missing files, and delete any extra files from disk that are not present on the server. This is very useful when creating an exact local backup of an FTP directory.
        /// In Update mode, we will only download missing files and preserve any extra files on disk. This is useful when you want to simply download missing files from an FTP directory.
        /// Only downloads the files and folders matching all the rules provided, if any.
        /// All exceptions during downloading are caught, and the exception is stored in the related FtpResult object.
        /// </summary>
        /// <param name="localFolder">The full path of the local folder on disk to download into. It is created if it does not exist.</param>
        /// <param name="remoteFolder">The full path of the remote FTP folder that you want to download. If it does not exist, an empty result list is returned.</param>
        /// <param name="mode">Mirror or Update mode, as explained above</param>
        /// <param name="existsMode">If the file exists on disk, should we skip it, resume the download or restart the download?</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful download and what to do if it fails verification (See Remarks)</param>
        /// <param name="rules">Only files and folders that pass all these rules are downloaded, and the files that don't pass are skipped. In the Mirror mode, the files that fail the rules are also deleted from the local folder.</param>
        /// <param name="progress">Provide an implementation of IProgress to track upload progress.</param>
        /// <param name="token">The token that can be used to cancel the entire process</param>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically switch to true for subsequent attempts.
        /// If <see cref="FtpVerify.Throw"/> is set and <see cref="FtpError.Throw"/> is <i>not set</i>, then individual verification errors will not cause an exception
        /// to propagate from this method.
        /// </remarks>
        /// <returns>
        /// Returns a listing of all the remote files, indicating if they were downloaded, skipped or overwritten.
        /// Returns a blank list if nothing was transfered. Never returns null.
        /// </returns>
        public async Task <List <FtpResult> > DownloadDirectoryAsync(string localFolder, string remoteFolder, FtpFolderSyncMode mode = FtpFolderSyncMode.Update,
                                                                     FtpLocalExists existsMode = FtpLocalExists.Skip, FtpVerify verifyOptions = FtpVerify.None, List <FtpRule> rules = null, IProgress <FtpProgress> progress = null, CancellationToken token = default(CancellationToken))
        {
            if (localFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localFolder");
            }

            if (remoteFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "remoteFolder");
            }

            LogFunc(nameof(DownloadDirectoryAsync), new object[] { localFolder, remoteFolder, mode, existsMode, verifyOptions, (rules.IsBlank() ? null : rules.Count + " rules") });

            var results = new List <FtpResult>();

            // ensure the local path ends with slash
            localFolder = localFolder.EnsurePostfix(Path.DirectorySeparatorChar.ToString());

            // cleanup the remote path
            remoteFolder = remoteFolder.GetFtpPath().EnsurePostfix("/");

            // if the dir does not exist, fail fast
            if (!await DirectoryExistsAsync(remoteFolder, token))
            {
                return(results);
            }

            // ensure the local dir exists
            localFolder.EnsureDirectory();

            // get all the files in the remote directory
            var listing = await GetListingAsync(remoteFolder, FtpListOption.Recursive | FtpListOption.Size, token);

            // break if task is cancelled
            token.ThrowIfCancellationRequested();

            // collect paths of the files that should exist (lowercase for CI checks)
            var shouldExist = new Dictionary <string, bool>();

            // loop thru each file and transfer it #1
            var toDownload = GetFilesToDownload(localFolder, remoteFolder, rules, results, listing, shouldExist);

            // break if task is cancelled
            token.ThrowIfCancellationRequested();

            /*-------------------------------------------------------------------------------------/
            *   Cancelling after this point would leave the FTP server in an inconsistant state   *
            *-------------------------------------------------------------------------------------*/

            // loop thru each file and transfer it #2
            await DownloadServerFilesAsync(toDownload, existsMode, verifyOptions, progress, token);

            // delete the extra local files if in mirror mode
            DeleteExtraLocalFiles(localFolder, mode, shouldExist);

            return(results);
        }
Exemple #13
0
        /// <summary>
        /// Download all the listed files and folders from the main directory
        /// </summary>
        private void DownloadServerFiles(List <FtpResult> toDownload, FtpLocalExists existsMode, FtpVerify verifyOptions, Action <FtpProgress> progress)
        {
            LogFunc("DownloadServerFiles", new object[] { toDownload.Count + " files" });

            foreach (var result in toDownload)
            {
                if (result.Type == FtpFileSystemObjectType.File)
                {
                    // absorb errors
                    try {
                        // download the file
                        var transferred = this.DownloadFile(result.LocalPath, result.RemotePath, existsMode, verifyOptions, progress);
                        result.IsSuccess = true;
                        result.IsSkipped = !transferred;
                    }
                    catch (Exception ex) {
                        LogStatus(FtpTraceLevel.Warn, "File failed to download: " + result.RemotePath);

                        // mark that the file failed to download
                        result.IsFailed  = true;
                        result.Exception = ex;
                    }
                }
                else if (result.Type == FtpFileSystemObjectType.Directory)
                {
                    // absorb errors
                    try {
                        // create directory on local filesystem
                        // to ensure we download the blank remote dirs as well
                        var created = result.LocalPath.EnsureDirectory();
                        result.IsSuccess = true;
                        result.IsSkipped = !created;
                    }
                    catch (Exception ex) {
                        // mark that the file failed to download
                        result.IsFailed  = true;
                        result.Exception = ex;
                    }
                }
            }
        }
Exemple #14
0
        /// <summary>
        /// Transfer the specified file from the source FTP Server to the destination FTP Server using the FXP protocol.
        /// High-level API that takes care of various edge cases internally.
        /// </summary>
        /// <param name="sourcePath">The full or relative path to the file on the source FTP Server</param>
        /// <param name="remoteClient">Valid FTP connection to the destination FTP Server</param>
        /// <param name="remotePath">The full or relative path to destination file on the remote FTP Server</param>
        /// <param name="createRemoteDir">Indicates if the folder should be created on the remote FTP Server</param>
        /// <param name="existsMode">If the file exists on disk, should we skip it, resume the download or restart the download?</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful download and what to do if it fails verification (See Remarks)</param>
        /// <param name="progress">Provide a callback to track download progress.</param>
        /// Returns a FtpStatus indicating if the file was transfered.
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically be set to true for subsequent attempts.
        /// </remarks>
        public FtpStatus TransferFile(string sourcePath, FtpClient remoteClient, string remotePath,
                                      bool createRemoteDir = false, FtpRemoteExists existsMode = FtpRemoteExists.Resume, FtpVerify verifyOptions = FtpVerify.None, Action <FtpProgress> progress = null, FtpProgress metaProgress = null)
        {
            sourcePath = sourcePath.GetFtpPath();
            remotePath = remotePath.GetFtpPath();

            LogFunc(nameof(TransferFile), new object[] { sourcePath, remoteClient, remotePath, FXPDataType, createRemoteDir, existsMode, verifyOptions });

            // verify input params
            VerifyTransferFileParams(sourcePath, remoteClient, remotePath, existsMode);

            // ensure source file exists
            if (!FileExists(sourcePath))
            {
                throw new FtpException("Source File " + sourcePath + " cannot be found or does not exists!");
            }

            bool fxpSuccess;
            var  verified     = true;
            var  attemptsLeft = verifyOptions.HasFlag(FtpVerify.Retry) ? m_retryAttempts : 1;

            do
            {
                fxpSuccess = TransferFileFXPInternal(sourcePath, remoteClient, remotePath, createRemoteDir, existsMode, progress, metaProgress is null ? new FtpProgress(1, 0) : metaProgress);
                attemptsLeft--;

                // if verification is needed
                if (fxpSuccess && verifyOptions != FtpVerify.None)
                {
                    verified = VerifyFXPTransfer(sourcePath, remoteClient, remotePath);
                    LogStatus(FtpTraceLevel.Info, "File Verification: " + (verified ? "PASS" : "FAIL"));
                    if (!verified && attemptsLeft > 0)
                    {
                        LogStatus(FtpTraceLevel.Verbose, "Retrying due to failed verification." + (existsMode == FtpRemoteExists.Resume ? "  Overwrite will occur." : "") + "  " + attemptsLeft + " attempts remaining");
                        // Force overwrite if a retry is required
                        existsMode = FtpRemoteExists.Overwrite;
                    }
                }
            } while (!verified && attemptsLeft > 0);

            if (fxpSuccess && !verified && verifyOptions.HasFlag(FtpVerify.Delete))
            {
                remoteClient.DeleteFile(remotePath);
            }

            if (fxpSuccess && !verified && verifyOptions.HasFlag(FtpVerify.Throw))
            {
                throw new FtpException("Destination file checksum value does not match source file");
            }

            return(fxpSuccess && verified ? FtpStatus.Success : FtpStatus.Failed);
        }
        /// <summary>
        /// Upload all the files within the main directory
        /// </summary>
        private async Task UploadDirectoryFilesAsync(List <FtpResult> filesToUpload, FtpRemoteExists existsMode, FtpVerify verifyOptions, IProgress <FtpProgress> progress, FtpListItem[] remoteListing, CancellationToken token)
        {
            LogFunc(nameof(UploadDirectoryFilesAsync), new object[] { filesToUpload.Count + " files" });

            var r = -1;

            foreach (var result in filesToUpload)
            {
                r++;

                // absorb errors
                try {
                    // skip uploading if the file already exists on the server
                    FtpRemoteExists existsModeToUse;
                    if (!CanUploadFile(result, remoteListing, existsMode, out existsModeToUse))
                    {
                        continue;
                    }

                    // create meta progress to store the file progress
                    var metaProgress = new FtpProgress(filesToUpload.Count, r);

                    // upload the file
                    var transferred = await UploadFileFromFileAsync(result.LocalPath, result.RemotePath, false, existsModeToUse, false, false, verifyOptions, token, progress, metaProgress);

                    result.IsSuccess = transferred.IsSuccess();
                    result.IsSkipped = transferred == FtpStatus.Skipped;
                }
                catch (Exception ex) {
                    LogStatus(FtpTraceLevel.Warn, "File failed to upload: " + result.LocalPath);

                    // mark that the file failed to upload
                    result.IsFailed  = true;
                    result.Exception = ex;
                }
            }
        }
        /// <summary>
        /// Uploads the specified directory onto the server.
        /// In Mirror mode, we will upload missing files, and delete any extra files from the server that are not present on disk. This is very useful when publishing an exact copy of a local folder onto an FTP server.
        /// In Update mode, we will only upload missing files and preserve any extra files on the server. This is useful when you want to simply upload missing files to a server.
        /// Only uploads the files and folders matching all the rules provided, if any.
        /// All exceptions during uploading are caught, and the exception is stored in the related FtpResult object.
        /// </summary>
        /// <param name="localFolder">The full path of the local folder on disk that you want to upload. If it does not exist, an empty result list is returned.</param>
        /// <param name="remoteFolder">The full path of the remote FTP folder to upload into. It is created if it does not exist.</param>
        /// <param name="mode">Mirror or Update mode, as explained above</param>
        /// <param name="existsMode">If the file exists on disk, should we skip it, resume the upload or restart the upload?</param>
        /// <param name="verifyOptions">Sets if checksum verification is required for a successful upload and what to do if it fails verification (See Remarks)</param>
        /// <param name="rules">Only files and folders that pass all these rules are downloaded, and the files that don't pass are skipped. In the Mirror mode, the files that fail the rules are also deleted from the local folder.</param>
        /// <param name="progress">Provide an implementation of IProgress to track upload progress.</param>
        /// <param name="token">The token that can be used to cancel the entire process</param>
        /// <remarks>
        /// If verification is enabled (All options other than <see cref="FtpVerify.None"/>) the hash will be checked against the server.  If the server does not support
        /// any hash algorithm, then verification is ignored.  If only <see cref="FtpVerify.OnlyChecksum"/> is set then the return of this method depends on both a successful
        /// upload &amp; verification.  Additionally, if any verify option is set and a retry is attempted then overwrite will automatically switch to true for subsequent attempts.
        /// If <see cref="FtpVerify.Throw"/> is set and <see cref="FtpError.Throw"/> is <i>not set</i>, then individual verification errors will not cause an exception
        /// to propagate from this method.
        /// </remarks>
        /// <returns>
        /// Returns a listing of all the remote files, indicating if they were downloaded, skipped or overwritten.
        /// Returns a blank list if nothing was transfered. Never returns null.
        /// </returns>
        public async Task <List <FtpResult> > UploadDirectoryAsync(string localFolder, string remoteFolder, FtpFolderSyncMode mode = FtpFolderSyncMode.Update,
                                                                   FtpRemoteExists existsMode = FtpRemoteExists.Skip, FtpVerify verifyOptions = FtpVerify.None, List <FtpRule> rules = null, IProgress <FtpProgress> progress = null, CancellationToken token = default(CancellationToken))
        {
            if (localFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "localFolder");
            }

            if (remoteFolder.IsBlank())
            {
                throw new ArgumentException("Required parameter is null or blank.", "remoteFolder");
            }

            LogFunc(nameof(UploadDirectoryAsync), new object[] { localFolder, remoteFolder, mode, existsMode, verifyOptions, (rules.IsBlank() ? null : rules.Count + " rules") });

            var results = new List <FtpResult>();

            // ensure the local path ends with slash
            localFolder = localFolder.EnsurePostfix(Path.DirectorySeparatorChar.ToString());

            // cleanup the remote path
            remoteFolder = remoteFolder.GetFtpPath().EnsurePostfix("/");

            // if the dir does not exist, fail fast
            if (!Directory.Exists(localFolder))
            {
                return(results);
            }

            // flag to determine if existence checks are required
            var checkFileExistence = true;

            // ensure the remote dir exists
            if (!await DirectoryExistsAsync(remoteFolder, token))
            {
                await CreateDirectoryAsync(remoteFolder, token);

                checkFileExistence = false;
            }

            // break if task is cancelled
            token.ThrowIfCancellationRequested();

            // collect paths of the files that should exist (lowercase for CI checks)
            var shouldExist = new Dictionary <string, bool>();

            // get all the folders in the local directory
            var dirListing = Directory.GetDirectories(localFolder, "*.*", SearchOption.AllDirectories);

            // break if task is cancelled
            token.ThrowIfCancellationRequested();

            // get all the already existing files
            var remoteListing = checkFileExistence ? GetListing(remoteFolder, FtpListOption.Recursive) : null;

            // break if task is cancelled
            token.ThrowIfCancellationRequested();

            // loop thru each folder and ensure it exists #1
            var dirsToUpload = GetSubDirectoriesToUpload(localFolder, remoteFolder, rules, results, dirListing);

            // break if task is cancelled
            token.ThrowIfCancellationRequested();

            /*-------------------------------------------------------------------------------------/
            *   Cancelling after this point would leave the FTP server in an inconsistant state   *
            *-------------------------------------------------------------------------------------*/

            // loop thru each folder and ensure it exists #2
            await CreateSubDirectoriesAsync(this, dirsToUpload, token);

            // get all the files in the local directory
            var fileListing = Directory.GetFiles(localFolder, "*.*", SearchOption.AllDirectories);

            // loop thru each file and transfer it
            var filesToUpload = GetFilesToUpload(localFolder, remoteFolder, rules, results, shouldExist, fileListing);

            await UploadDirectoryFilesAsync(filesToUpload, existsMode, verifyOptions, progress, remoteListing, token);

            // delete the extra remote files if in mirror mode and the directory was pre-existing
            await DeleteExtraServerFilesAsync(mode, remoteFolder, shouldExist, remoteListing, rules, token);

            return(results);
        }