/// <summary> /// Transfer data from the source stream to the destination stream. This method also performs the necessary /// data transforms (throttling, hashing, encrypting, etc) as the data is streamed. This allows a the /// source stream to only be read a single time while performing multiple operations on the data. /// </summary> /// <param name="sourceStream">The stream that the data is read from</param> /// <param name="destinationStream">The stream that the data is written to</param> /// <returns>(async) The result of the transfer</returns> private async Task <TransferResult> TransferDataWithTransformsAsync( Stream sourceStream, Stream destinationStream) { SHA1Cng sha1 = null; MD5Cng md5 = null; try { // By default, we will compute the SHA1 and MD5 hashes of the file as it is streamed. sha1 = new SHA1Cng(); md5 = new MD5Cng(); // Allocate the buffer that will be used to transfer the data. The source stream will be read one // suffer-size as a time, then written to the destination. byte[] buffer = new byte[TransferBufferSize]; long readTotal = 0; long writtenTotal = 0; while (true) { // If we are using a throttling manager, get the necessary number of tokens to // transfer the data if (this.throttlingManager != null) { int tokens = 0; while (true) { // Get the number of tokens needed. We will require tokens equaling the number of // bytes to be transferred. This is a non-blocking calling and will return between // 0 and the number of requested tokens. tokens += this.throttlingManager.GetTokens(TransferBufferSize - tokens); if (tokens >= TransferBufferSize) { // We have enough tokens to transfer the buffer break; } // We don't (yet) have enough tokens, so wait for a short duration and try again await Task.Delay(10, this.cancellationToken).ConfigureAwait(false); } } // Read data from the source adapter int read = sourceStream.Read(buffer, 0, buffer.Length); CounterManager.LogSyncJobCounter( "SyncJob/BytesRead", read); // Increment the total number of bytes read from the source adapter readTotal += read; int bytesWritten; if (read < buffer.Length) { // Compute the last part of the SHA1 and MD5 hashes (this finished the algorithm's work). sha1.TransformFinalBlock(buffer, 0, read); md5.TransformFinalBlock(buffer, 0, read); if (this.encryptionManager != null) { bytesWritten = this.encryptionManager.TransformFinalBlock(buffer, 0, read); } else { destinationStream.Write(buffer, 0, read); destinationStream.Flush(); bytesWritten = buffer.Length; } CounterManager.LogSyncJobCounter( "SyncJob/BytesWritten", read); writtenTotal += bytesWritten; // Increment the total number of bytes written to the desination adapter this.bytesCompleted += read; this.progressChanged(new CopyProgressInfo(this.bytesCompleted, this.updateInfo)); // Read the end of the stream break; } // Pass the data through the required hashing algorithms. sha1.TransformBlock(buffer, 0, read, buffer, 0); md5.TransformBlock(buffer, 0, read, buffer, 0); // Write the data to the destination adapter if (this.encryptionManager != null) { bytesWritten = this.encryptionManager.TransformBlock(buffer, 0, read); } else { destinationStream.Write(buffer, 0, read); bytesWritten = buffer.Length; } CounterManager.LogSyncJobCounter( "SyncJob/BytesWritten", read); writtenTotal += bytesWritten; // Increment the total number of bytes written to the desination adapter this.bytesCompleted += read; if (this.syncProgressUpdateStopwatch.ElapsedMilliseconds > 100) { this.progressChanged(new CopyProgressInfo(this.bytesCompleted, this.updateInfo)); // After reporting the number of bytes copied for this file, set back to 0 so that we are only // reporting the number of bytes sync we last invoked the callback. this.bytesCompleted = 0; this.syncProgressUpdateStopwatch.Restart(); } } TransferResult result = new TransferResult { BytesRead = readTotal, BytesWritten = writtenTotal }; if (this.encryptionManager != null) { if (this.encryptionManager.Mode == EncryptionMode.Encrypt) { result.Sha1Hash = sha1.Hash; result.Md5Hash = md5.Hash; result.TransformedSha1Hash = this.encryptionManager.Sha1Hash; result.TransformedMd5Hash = this.encryptionManager.Md5Hash; } else { result.TransformedSha1Hash = sha1.Hash; result.TransformedMd5Hash = md5.Hash; result.Sha1Hash = this.encryptionManager.Sha1Hash; // The SHA1 hash of the data written by the encryption manager result.Md5Hash = this.encryptionManager.Md5Hash; } } else { result.Sha1Hash = sha1.Hash; result.Md5Hash = md5.Hash; } return(result); } finally { sha1?.Dispose(); md5?.Dispose(); } }