/******************************************************************************* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"). You may not use * this file except in compliance with the License. A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * ***************************************************************************** * __ _ _ ___ * ( )( \/\/ )/ __) * /__\ \ / \__ \ * (_)(_) \/\/ (___/ * * AWS SDK for .NET * API Version: 2006-03-01 * */ using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using Amazon.Runtime; using Amazon.Runtime.Internal.Util; using Amazon.Util; using Amazon.S3.Model; using Amazon.S3.Util; namespace Amazon.S3.Transfer.Internal { /// <summary> /// The command to manage an upload using the S3 multipart API. /// </summary> internal partial class MultipartUploadCommand : BaseCommand { IAmazonS3 _s3Client; long _partSize; int _totalNumberOfParts; TransferUtilityConfig _config; TransferUtilityUploadRequest _fileTransporterRequest; List<UploadPartResponse> _uploadResponses = new List<UploadPartResponse>(); long _totalTransferredBytes; Queue<UploadPartRequest> _partsToUpload = new Queue<UploadPartRequest>(); long _contentLength; private static Logger Logger { get { return Logger.GetLogger(typeof(TransferUtility)); } } /// <summary> /// Initializes a new instance of the <see cref="MultipartUploadCommand"/> class. /// </summary> /// <param name="s3Client">The s3 client.</param> /// <param name="config">The config object that has the number of threads to use.</param> /// <param name="fileTransporterRequest">The file transporter request.</param> internal MultipartUploadCommand(IAmazonS3 s3Client, TransferUtilityConfig config, TransferUtilityUploadRequest fileTransporterRequest) { this._config = config; if (fileTransporterRequest.IsSetFilePath()) { Logger.DebugFormat("Beginning upload of file {0}.", fileTransporterRequest.FilePath); } else { Logger.DebugFormat("Beginning upload of stream."); } this._s3Client = s3Client; this._fileTransporterRequest = fileTransporterRequest; this._contentLength = this._fileTransporterRequest.ContentLength; if (fileTransporterRequest.IsSetPartSize()) this._partSize = fileTransporterRequest.PartSize; else this._partSize = calculatePartSize(this._contentLength); if (fileTransporterRequest.InputStream != null) { if (fileTransporterRequest.AutoResetStreamPosition && fileTransporterRequest.InputStream.CanSeek) { fileTransporterRequest.InputStream.Seek(0, SeekOrigin.Begin); } } Logger.DebugFormat("Upload part size {0}.", this._partSize); } private static long calculatePartSize(long fileSize) { double partSize = Math.Ceiling((double)fileSize / S3Constants.MaxNumberOfParts); if (partSize < S3Constants.MinPartSize) { partSize = S3Constants.MinPartSize; } return (long)partSize; } private string determineContentType() { if (this._fileTransporterRequest.IsSetContentType()) return this._fileTransporterRequest.ContentType; if (this._fileTransporterRequest.IsSetFilePath() || this._fileTransporterRequest.IsSetKey()) { // Get the extension of the file from the path. // Try the key as well. string ext = AWSSDKUtils.GetExtension(this._fileTransporterRequest.FilePath); if (String.IsNullOrEmpty(ext) && this._fileTransporterRequest.IsSetKey()) { ext = AWSSDKUtils.GetExtension(this._fileTransporterRequest.Key); } string type = AmazonS3Util.MimeTypeFromExtension(ext); return type; } return null; } private int CalculateConcurrentServiceRequests() { int threadCount; if (this._fileTransporterRequest.IsSetFilePath() && !(_s3Client is Amazon.S3.Internal.IAmazonS3Encryption)) { threadCount = this._config.ConcurrentServiceRequests; } else { threadCount = 1; // When using streams or encryption, multiple threads can not be used to read from the same stream. } if (this._totalNumberOfParts < threadCount) { threadCount = this._totalNumberOfParts; } return threadCount; } private CompleteMultipartUploadRequest ConstructCompleteMultipartUploadRequest(InitiateMultipartUploadResponse initResponse) { if(this._uploadResponses.Count != this._totalNumberOfParts) { throw new InvalidOperationException($"Cannot complete multipart upload request. The total number of completed parts ({this._uploadResponses.Count}) " + $"does not equal the total number of parts created ({this._totalNumberOfParts})."); } var compRequest = new CompleteMultipartUploadRequest() { BucketName = this._fileTransporterRequest.BucketName, Key = this._fileTransporterRequest.Key, UploadId = initResponse.UploadId }; compRequest.AddPartETags(this._uploadResponses); ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)compRequest).AddBeforeRequestHandler(this.RequestEventHandler); return compRequest; } private UploadPartRequest ConstructUploadPartRequest(int partNumber, long filePosition, InitiateMultipartUploadResponse initResponse) { var uploadRequest = new UploadPartRequest() { BucketName = this._fileTransporterRequest.BucketName, Key = this._fileTransporterRequest.Key, UploadId = initResponse.UploadId, PartNumber = partNumber, PartSize = this._partSize, ServerSideEncryptionCustomerMethod = this._fileTransporterRequest.ServerSideEncryptionCustomerMethod, ServerSideEncryptionCustomerProvidedKey = this._fileTransporterRequest.ServerSideEncryptionCustomerProvidedKey, ServerSideEncryptionCustomerProvidedKeyMD5 = this._fileTransporterRequest.ServerSideEncryptionCustomerProvidedKeyMD5, #if (BCL && !BCL45) Timeout = ClientConfig.GetTimeoutValue(this._config.DefaultTimeout, this._fileTransporterRequest.Timeout), #endif DisableMD5Stream = this._fileTransporterRequest.DisableMD5Stream, DisablePayloadSigning = this._fileTransporterRequest.DisablePayloadSigning, ChecksumAlgorithm = this._fileTransporterRequest.ChecksumAlgorithm }; if ((filePosition + this._partSize >= this._contentLength) && _s3Client is Amazon.S3.Internal.IAmazonS3Encryption) { uploadRequest.IsLastPart = true; uploadRequest.PartSize = 0; } var progressHandler = new ProgressHandler(this.UploadPartProgressEventCallback); ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)uploadRequest).StreamUploadProgressCallback += progressHandler.OnTransferProgress; ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)uploadRequest).AddBeforeRequestHandler(this.RequestEventHandler); if (this._fileTransporterRequest.IsSetFilePath()) { uploadRequest.FilePosition = filePosition; uploadRequest.FilePath = this._fileTransporterRequest.FilePath; } else { uploadRequest.InputStream = this._fileTransporterRequest.InputStream; } // If the InitiateMultipartUploadResponse indicates that this upload is // using KMS, force SigV4 for each UploadPart request bool useSigV4 = initResponse.ServerSideEncryptionMethod == ServerSideEncryptionMethod.AWSKMS || initResponse.ServerSideEncryptionMethod == ServerSideEncryptionMethod.AWSKMSDSSE; if (useSigV4) ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)uploadRequest).SignatureVersion = SignatureVersion.SigV4; uploadRequest.CalculateContentMD5Header = this._fileTransporterRequest.CalculateContentMD5Header; return uploadRequest; } private InitiateMultipartUploadRequest ConstructInitiateMultipartUploadRequest() { var initRequest = new InitiateMultipartUploadRequest() { BucketName = this._fileTransporterRequest.BucketName, Key = this._fileTransporterRequest.Key, CannedACL = this._fileTransporterRequest.CannedACL, ContentType = determineContentType(), StorageClass = this._fileTransporterRequest.StorageClass, ServerSideEncryptionMethod = this._fileTransporterRequest.ServerSideEncryptionMethod, ServerSideEncryptionKeyManagementServiceKeyId = this._fileTransporterRequest.ServerSideEncryptionKeyManagementServiceKeyId, ServerSideEncryptionCustomerMethod = this._fileTransporterRequest.ServerSideEncryptionCustomerMethod, ServerSideEncryptionCustomerProvidedKey = this._fileTransporterRequest.ServerSideEncryptionCustomerProvidedKey, ServerSideEncryptionCustomerProvidedKeyMD5 = this._fileTransporterRequest.ServerSideEncryptionCustomerProvidedKeyMD5, TagSet = this._fileTransporterRequest.TagSet, ChecksumAlgorithm = this._fileTransporterRequest.ChecksumAlgorithm, ObjectLockLegalHoldStatus = this._fileTransporterRequest.ObjectLockLegalHoldStatus, ObjectLockMode = this._fileTransporterRequest.ObjectLockMode }; if (this._fileTransporterRequest.IsSetObjectLockRetainUntilDate()) initRequest.ObjectLockRetainUntilDate = this._fileTransporterRequest.ObjectLockRetainUntilDate; ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)initRequest).AddBeforeRequestHandler(this.RequestEventHandler); if (this._fileTransporterRequest.Metadata != null && this._fileTransporterRequest.Metadata.Count > 0) initRequest.Metadata = this._fileTransporterRequest.Metadata; if (this._fileTransporterRequest.Headers != null && this._fileTransporterRequest.Headers.Count > 0) initRequest.Headers = this._fileTransporterRequest.Headers; return initRequest; } private void UploadPartProgressEventCallback(object sender, UploadProgressArgs e) { long transferredBytes = Interlocked.Add(ref _totalTransferredBytes, e.IncrementTransferred - e.CompensationForRetry); var progressArgs = new UploadProgressArgs(e.IncrementTransferred, transferredBytes, this._contentLength, e.CompensationForRetry, this._fileTransporterRequest.FilePath); this._fileTransporterRequest.OnRaiseProgressEvent(progressArgs); } } internal class ProgressHandler { private StreamTransferProgressArgs _lastProgressArgs; private EventHandler<UploadProgressArgs> _callback; public ProgressHandler(EventHandler<UploadProgressArgs> callback) { if (callback == null) throw new ArgumentNullException("callback"); _callback = callback; } public void OnTransferProgress(object sender, StreamTransferProgressArgs e) { var compensationForRetry = 0L; if (_lastProgressArgs != null) { if (_lastProgressArgs.TransferredBytes >= e.TransferredBytes) { // The request was retried compensationForRetry = _lastProgressArgs.TransferredBytes; } } var progressArgs = new UploadProgressArgs(e.IncrementTransferred, e.TransferredBytes, e.TotalBytes, compensationForRetry, null); _callback(this, progressArgs); _lastProgressArgs = e; } } }