/// <summary> /// Sends progress to the user, either a value between 0-100 indicating percentage complete, or -1 for indeterminate. /// </summary> private void ReportProgress(IProgress <FtpProgress> progress, long fileSize, long position, long bytesProcessed, TimeSpan elapsedtime, string localPath, string remotePath, FtpProgress metaProgress) { // calculate % done, transfer speed and time remaining FtpProgress status = FtpProgress.Generate(fileSize, position, bytesProcessed, elapsedtime, localPath, remotePath, metaProgress); // send progress to parent progress.Report(status); }
/// <summary> /// Create a new FtpProgress object for individual file transfer progress. /// </summary> public FtpProgress(double progress, long bytesTransferred, double transferspeed, TimeSpan remainingtime, string localPath, string remotePath, FtpProgress metaProgress) { // progress of individual file transfer Progress = progress; TransferSpeed = transferspeed; ETA = remainingtime; LocalPath = localPath; RemotePath = remotePath; TransferredBytes = bytesTransferred; // progress of the entire task if (metaProgress != null) { FileCount = metaProgress.FileCount; FileIndex = metaProgress.FileIndex; } }
/// <summary> /// Create a new FtpProgress object for a file transfer and calculate the ETA, Percentage and Transfer Speed. /// </summary> public static FtpProgress Generate(long fileSize, long position, long bytesProcessed, TimeSpan elapsedtime, string localPath, string remotePath, FtpProgress metaProgress) { // default values to send double progressValue = -1; double transferSpeed = 0; var estimatedRemaingTime = TimeSpan.Zero; // catch any divide-by-zero errors try { // calculate raw transferSpeed (bytes per second) transferSpeed = bytesProcessed / elapsedtime.TotalSeconds; // If fileSize < 0 the below computations make no sense if (fileSize > 0) { // calculate % based on file length vs file offset // send a value between 0-100 indicating percentage complete progressValue = (double)position / (double)fileSize * 100; //calculate remaining time estimatedRemaingTime = TimeSpan.FromSeconds((fileSize - position) / transferSpeed); } } catch (Exception) { } // suppress invalid values and send -1 instead if (double.IsNaN(progressValue) && double.IsInfinity(progressValue)) { progressValue = -1; } if (double.IsNaN(transferSpeed) && double.IsInfinity(transferSpeed)) { transferSpeed = 0; } var p = new FtpProgress(progressValue, position, transferSpeed, estimatedRemaingTime, localPath, remotePath, metaProgress); return(p); }
/// <summary> /// Transfers a file from the source FTP Server to the destination FTP Server via the FXP protocol asynchronously. /// </summary> private async Task <bool> TransferFileFXPInternalAsync(string sourcePath, FtpClient remoteClient, string remotePath, bool createRemoteDir, FtpRemoteExists existsMode, IProgress <FtpProgress> progress, CancellationToken token, FtpProgress metaProgress) { FtpReply reply; long offset = 0; bool fileExists = false; long fileSize = 0; var ftpFxpSession = await OpenPassiveFXPConnectionAsync(remoteClient, progress != null, token); if (ftpFxpSession != null) { try { ftpFxpSession.SourceServer.ReadTimeout = (int)TimeSpan.FromMinutes(30.0).TotalMilliseconds; ftpFxpSession.TargetServer.ReadTimeout = (int)TimeSpan.FromMinutes(30.0).TotalMilliseconds; // check if the file exists, and skip, overwrite or append if (existsMode == FtpRemoteExists.AppendNoCheck) { offset = await remoteClient.GetFileSizeAsync(remotePath, token); if (offset == -1) { offset = 0; // start from the beginning } } else { fileExists = await remoteClient.FileExistsAsync(remotePath, token); switch (existsMode) { case FtpRemoteExists.Skip: if (fileExists) { LogStatus(FtpTraceLevel.Info, "Skip is selected => Destination file exists => skipping"); //Fix #413 - progress callback isn't called if the file has already been uploaded to the server //send progress reports if (progress != null) { progress.Report(new FtpProgress(100.0, 0, 0, TimeSpan.FromSeconds(0), sourcePath, remotePath, metaProgress)); } return(true); } break; case FtpRemoteExists.Overwrite: if (fileExists) { await remoteClient.DeleteFileAsync(remotePath, token); } break; case FtpRemoteExists.Append: if (fileExists) { offset = await remoteClient.GetFileSizeAsync(remotePath, token); if (offset == -1) { offset = 0; // start from the beginning } } break; } } fileSize = await GetFileSizeAsync(sourcePath, token); // ensure the remote dir exists .. only if the file does not already exist! if (createRemoteDir && !fileExists) { var dirname = remotePath.GetFtpDirectoryName(); if (!await remoteClient.DirectoryExistsAsync(dirname, token)) { await remoteClient.CreateDirectoryAsync(dirname, token); } } if (offset == 0 && existsMode != FtpRemoteExists.AppendNoCheck) { // send command to tell the source server to 'send' the file to the destination server if (!(reply = await ftpFxpSession.SourceServer.ExecuteAsync($"RETR {sourcePath}", token)).Success) { throw new FtpCommandException(reply); } //Instruct destination server to store the file if (!(reply = await ftpFxpSession.TargetServer.ExecuteAsync($"STOR {remotePath}", token)).Success) { throw new FtpCommandException(reply); } } else { //tell source server to restart / resume if (!(reply = await ftpFxpSession.SourceServer.ExecuteAsync($"REST {offset}", token)).Success) { throw new FtpCommandException(reply); } // send command to tell the source server to 'send' the file to the destination server if (!(reply = await ftpFxpSession.SourceServer.ExecuteAsync($"RETR {sourcePath}", token)).Success) { throw new FtpCommandException(reply); } //Instruct destination server to append the file if (!(reply = await ftpFxpSession.TargetServer.ExecuteAsync($"APPE {remotePath}", token)).Success) { throw new FtpCommandException(reply); } } var transferStarted = DateTime.Now; long lastSize = 0; var sourceFXPTransferReply = ftpFxpSession.SourceServer.GetReplyAsync(token); var destinationFXPTransferReply = ftpFxpSession.TargetServer.GetReplyAsync(token); // while the transfer is not complete while (!sourceFXPTransferReply.IsCompleted || !destinationFXPTransferReply.IsCompleted) { // send progress reports every 1 second if (ftpFxpSession.ProgressServer != null) { // send progress reports if (progress != null && fileSize != -1) { offset = await ftpFxpSession.ProgressServer.GetFileSizeAsync(remotePath, token); if (offset != -1 && lastSize <= offset) { long bytesProcessed = offset - lastSize; lastSize = offset; ReportProgress(progress, fileSize, offset, bytesProcessed, DateTime.Now - transferStarted, sourcePath, remotePath, metaProgress); } } } await Task.Delay(FXPProgressInterval); } FtpTrace.WriteLine(FtpTraceLevel.Info, $"FXP transfer of file {sourcePath} has completed"); await NoopAsync(token); await remoteClient.NoopAsync(token); ftpFxpSession.Dispose(); return(true); } // Fix: catch all exceptions and dispose off the FTP clients if one occurs catch (Exception ex) { ftpFxpSession.Dispose(); throw ex; } } else { FtpTrace.WriteLine(FtpTraceLevel.Error, "Failed to open FXP passive Connection"); return(false); } }
/// <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 & 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.Append, FtpVerify verifyOptions = FtpVerify.None, Action <FtpProgress> progress = null, FtpProgress metaProgress = null) { LogFunc(nameof(TransferFile), new object[] { sourcePath, remoteClient, remotePath, FXPDataType, createRemoteDir, existsMode, verifyOptions }); // verify input params VerifyTransferFileParams(sourcePath, remoteClient, remotePath); // 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.Append ? " 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> /// Transfer the specified file from the source FTP Server to the destination FTP Server asynchronously 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> /// <param name="token">The token that can be used to cancel the entire process</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 & 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 <FtpStatus> TransferFileAsync(string sourcePath, FtpClient remoteClient, string remotePath, bool createRemoteDir = false, FtpRemoteExists existsMode = FtpRemoteExists.Append, FtpVerify verifyOptions = FtpVerify.None, IProgress <FtpProgress> progress = null, FtpProgress metaProgress = null, CancellationToken token = default(CancellationToken)) { LogFunc(nameof(TransferFileAsync), new object[] { sourcePath, remoteClient, remotePath, FXPDataType, createRemoteDir, existsMode, verifyOptions }); // verify input params VerifyTransferFileParams(sourcePath, remoteClient, remotePath); // ensure source file exists if (!await FileExistsAsync(sourcePath, token)) { throw new FtpException(string.Format("Source File {0} cannot be found or does not exists!", sourcePath)); } bool fxpSuccess; var verified = true; var attemptsLeft = verifyOptions.HasFlag(FtpVerify.Retry) ? m_retryAttempts : 1; do { fxpSuccess = await TransferFileFXPInternalAsync(sourcePath, remoteClient, remotePath, createRemoteDir, existsMode, progress, token, metaProgress is null?new FtpProgress(1, 0) : metaProgress); attemptsLeft--; // if verification is needed if (fxpSuccess && verifyOptions != FtpVerify.None) { verified = await VerifyFXPTransferAsync(sourcePath, remoteClient, 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 == FtpRemoteExists.Append ? " Overwrite will occur." : "") + " " + attemptsLeft + " attempts remaining"); } #endif } } while (!verified && attemptsLeft > 0); if (fxpSuccess && !verified && verifyOptions.HasFlag(FtpVerify.Delete)) { await remoteClient.DeleteFileAsync(remotePath, token); } 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); }