Ejemplo n.º 1
0
        /// <summary>
        /// Performs a transfer of the specified file to the target providers.
        /// </summary>
        /// <param name="File"></param>
        /// <param name="CancelToken"></param>
        public async Task TransferAsync(BackupFile File, SourceLocation Source, CancellationToken CancelToken)
        {
            if (File == null)
            {
                throw new ArgumentNullException(nameof(File));
            }
            if (Source == null)
            {
                throw new ArgumentNullException(nameof(Source));
            }
            if (CancelToken == null)
            {
                throw new ArgumentNullException(nameof(CancelToken));
            }

            Logger.WriteTraceMessage(string.Format("Starting transfer operation for file: {0}", File.ToString()), InstanceID);

            try
            {
                // step 1: safety checks.
                // bail out if the file is missing or contents are empty.

                var info = new FileInfo(File.FullSourcePath);
                if (!info.Exists)
                {
                    Logger.WriteTraceMessage(string.Format("Unable to backup file ({0}). It has been deleted or is no longer accessible since it was scanned.", File.FullSourcePath), InstanceID);
                    await Database.DeleteBackupFileAsync(File).ConfigureAwait(false);

                    await Database.RemoveFileFromBackupQueueAsync(File).ConfigureAwait(false);

                    return;
                }

                if (info.Length == 0)
                {
                    var message = string.Format("Unable to backup file ({0}). It is empty (has no contents).", File.FullSourcePath);
                    Logger.WriteTraceMessage(message, InstanceID);
                    await Database.SetBackupFileAsFailedAsync(File, message).ConfigureAwait(false);

                    await Database.RemoveFileFromBackupQueueAsync(File).ConfigureAwait(false);

                    return;
                }

                // cancel if the caller is shutting down.
                // add these in a few different places to ensure we aren't holding the threads open too long.
                CancelToken.ThrowIfCancellationRequested();

                // step 2: open up a filestream to the specified file.
                // use a read-only lock: this prevents the file from being modified while this lock is open.
                // but others can still open for read.

                using (FileStream fs = new FileStream(File.FullSourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    // step 3: calculate and save the hash.

                    CancelToken.ThrowIfCancellationRequested();
                    await UpdateFileHashInDatabaseAsync(File, fs).ConfigureAwait(false);

                    // step 4: see if this file is already on the destination target provider(s).
                    // this avoids resending the file if for some reason the client DB/states got wiped out.

                    CancelToken.ThrowIfCancellationRequested();
                    await UpdateFileCopyStateIfFileAlreadyExistsAtProvidersAsync(File, Source, fs).ConfigureAwait(false);

                    // step 5: while the file has data that needs to be transferred- transfer it.
                    // this includes transferring to each potential target that needs this same file block.

                    while (File.HasDataToTransfer())
                    {
                        // step 5A: generate the next transfer data block.
                        CancelToken.ThrowIfCancellationRequested();
                        var payload = File.GenerateNextTransferPayload(fs, Hasher);

                        // step 5B: send the transfer payload.
                        CancelToken.ThrowIfCancellationRequested();
                        await SendTransferPayloadToFileTargetsAsync(File, Source, payload, CancelToken).ConfigureAwait(false);
                    }

                    // no more data to transfer, remove the file from the backup queue.
                    await Database.RemoveFileFromBackupQueueAsync(File).ConfigureAwait(false);
                }
            }
            catch (OperationCanceledException)
            {
                // the caller has requested that we stop.
                Logger.WriteTraceWarning("An in-progress file transfer has been cancelled by request of the Backup Engine. It will be resumed the next time the engine starts up.", InstanceID);
            }
            catch (Exception ex)
            {
                var message = "An error occurred while preparing to transfer a file.";
                Logger.WriteTraceError(message, ex, Logger.GenerateFullContextStackTrace(), InstanceID);
                await Database.SetBackupFileAsFailedAsync(File, (message + ex.ToString())).ConfigureAwait(false);

                await Database.RemoveFileFromBackupQueueAsync(File).ConfigureAwait(false);
            }
        }