/*******************************************************************************
* 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 Tools for Windows (TM) PowerShell (TM)
*
*/
using System;
using System.Management.Automation;
using System.Linq;
using Amazon.PowerShell.Common;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using System.IO;
using Amazon.PowerShell.Utils;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Amazon.Runtime;
using AWSRegion = Amazon.PowerShell.Common.AWSRegion;
using System.Text;
using Amazon.Runtime.CredentialManagement;
using Amazon.Util;
namespace Amazon.PowerShell.Cmdlets.S3
{
///
///
/// Copies an S3 object to another object in S3, or downloads one or more objects from S3 to the local file system.
/// Note that you can also use the
/// Read-S3Object
/// cmdlet to download one or more objects to the local file system.
///
///
/// You can pipe an Amazon.S3.Model.S3Object instance to this cmdlet and its members will be used to
/// satisfy the BucketName, Key and optionally VersionId (if an S3ObjectVersion instance is supplied), parameters
/// when downloading or copying a single object.
///
///
[Cmdlet("Copy", "S3Object", DefaultParameterSetName = CopySingleObjectToLocalFile, SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low)]
[OutputType(new[] { typeof(S3Object), typeof(FileInfo), typeof(DirectoryInfo) })]
[AWSCmdlet("Calls the Amazon S3 CopyObject API operation to copy an existing S3 object to another S3 destination (bucket and/or object),"
+ " or download a single S3 object to a local file or folder or download object(s) matching a supplied"
+ " key prefix to a folder.",
Operation = new [] {"CopyObject"})]
[AWSCmdletOutput("Amazon.S3.Model.S3Object or System.IO.FileInfo or System.IO.DirectoryInfo",
"When copying an object to another object in S3 the cmdlet returns an Amazon.S3.Model.S3Object referencing the new object. "
+ " When copying a single object from S3 to the local file system the cmdlet returns a FileInfo instance representing the local file. "
+ " When copying multiple objects to a local folder the cmdlet returns a DirectoryInfo instance to the folder."
)]
public class CopyS3ObjectCmdlet : AmazonS3ClientCmdlet, IExecutor
{
const string CopySingleObjectToLocalFile = "CopySingleObjectToLocalFile";
const string CopySingleObjectToLocalFolder = "CopySingleObjectToLocalFolder";
const string CopyMultipleObjectsToLocalFolder = "CopyMultipleObjectsToLocalFolder";
const string CopyS3ObjectToS3Object = "CopyS3ObjectToS3Object";
private const long OneGigabyte = 1024 * 1024 * 1024;
private const long FiveGigabytes = 5 * OneGigabyte;
protected override bool IsSensitiveRequest { get; set; } = true;
protected override bool IsSensitiveResponse { get; set; } = true;
#region Parameter BucketName
///
///
/// The name of the bucket containing the source object.
///
///
///
/// When using this action with an access point, you must direct requests to the access
/// point hostname. The access point hostname takes the form AccessPointName-AccountId.s3-accesspoint.Region.amazonaws.com.
/// When using this action with an access point through the Amazon Web Services SDKs,
/// you provide the access point ARN in place of the bucket name. For more information
/// about access point ARNs, see Using
/// access points in the Amazon S3 User Guide.
///
///
///
/// When you use this action with Amazon S3 on Outposts, you must direct requests to the
/// S3 on Outposts hostname. The S3 on Outposts hostname takes the form AccessPointName-AccountId.outpostID.s3-outposts.Region.amazonaws.com
.
/// When you use this action with S3 on Outposts through the Amazon Web Services SDKs,
/// you provide the Outposts access point ARN in place of the bucket name. For more information
/// about S3 on Outposts ARNs, see What
/// is S3 on Outposts in the Amazon S3 User Guide.
///
///
[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true)]
[Alias("SourceBucket")]
[Amazon.PowerShell.Common.AWSRequiredParameter]
public System.String BucketName { get; set; }
#endregion
#region Parameter Key
///
/// The key of the single source object to copy.
///
[Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = CopySingleObjectToLocalFile)]
[Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = CopyS3ObjectToS3Object)]
[Parameter(Position = 1, ValueFromPipelineByPropertyName = true, ParameterSetName = CopySingleObjectToLocalFolder)]
[Alias("SourceKey")]
[Amazon.PowerShell.Common.AWSRequiredParameter(ParameterSets = new[] { CopySingleObjectToLocalFile, CopyS3ObjectToS3Object })]
public System.String Key { get; set; }
#endregion
#region Parameter VersionId
///
/// Specifies the version of the source object to copy.
///
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CopySingleObjectToLocalFile)]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CopySingleObjectToLocalFolder)]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CopyS3ObjectToS3Object)]
[Alias("SourceVersionId")]
public System.String VersionId { get; set; }
#endregion
#region Copy to S3 Target Parameters
#region Parameter DestinationKey
///
/// The key for the copy of the source S3 object.
///
[Parameter(Position = 2, ParameterSetName = CopyS3ObjectToS3Object, Mandatory = true, ValueFromPipelineByPropertyName = true)]
public System.String DestinationKey { get; set; }
#endregion
#region Parameter DestinationBucket
///
///
/// The name of the bucket that will contain the copied object.
/// If not specified, the copy is to another S3 object in the source bucket.
///
///
///
/// When using this action with an access point, you must direct requests to the access
/// point hostname. The access point hostname takes the form AccessPointName-AccountId.s3-accesspoint.Region.amazonaws.com.
/// When using this action with an access point through the Amazon Web Services SDKs,
/// you provide the access point ARN in place of the bucket name. For more information
/// about access point ARNs, see Using
/// access points in the Amazon S3 User Guide.
///
///
///
/// When you use this action with Amazon S3 on Outposts, you must direct requests to the
/// S3 on Outposts hostname. The S3 on Outposts hostname takes the form AccessPointName-AccountId.outpostID.s3-outposts.Region.amazonaws.com
.
/// When you use this action with S3 on Outposts through the Amazon Web Services SDKs,
/// you provide the Outposts access point ARN in place of the bucket name. For more information
/// about S3 on Outposts ARNs, see What
/// is S3 on Outposts in the Amazon S3 User Guide.
///
///
[Parameter(Position = 3, ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String DestinationBucket { get; set; }
#endregion
#region Parameter MetadataDirective
///
/// Specifies whether the metadata is copied from the source object or replaced with metadata provided in the request.
/// Valid values are COPY or REPLACE. COPY is the default if not specified.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public Amazon.S3.S3MetadataDirective MetadataDirective { get; set; }
#endregion
#region Parameter ContentType
///
/// Sets the content type of the target object; if not specified an attempt is made to infer it using the destination
/// or source object keys.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String ContentType { get; set; }
#endregion
///
/// Specifies the Region that the source bucket resides in; If not specified an attempt is made to infer it using the Region
/// set in your credential profile. The -Region parameter specifies the Destination Region.
///
#region Parameter SourceRegion
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public Object SourceRegion { get; set; }
#endregion
#region Parameter CannedACLName
///
/// Specifies the canned ACL (access control list) of permissions to be applied to the S3 bucket.
/// Please refer to Amazon.S3.Model.S3CannedACL for information on S3 Canned ACLs.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[AWSConstantClassSource("Amazon.S3.S3CannedACL")]
public Amazon.S3.S3CannedACL CannedACLName { get; set; }
#endregion
#region Parameter PublicReadOnly
///
/// If set, applies an ACL making the bucket public with read-only permissions
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public SwitchParameter PublicReadOnly { get; set; }
#endregion
#region Parameter PublicReadWrite
///
/// If set, applies an ACL making the bucket public with read-write permissions
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public SwitchParameter PublicReadWrite { get; set; }
#endregion
#region Parameter StorageClass
// NOTE: This parameter does not use the marker attribute for automated validate set
// updating because it would cause GLACIER to be added as a valid value. S3 does not
// support use of GLACIER as a storage class on PutObject, it must be handled as a
// lifecycle configuration
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
///
/// Specifies the storage class for the object.
/// Please refer to Storage Classes for information on S3 storage classes.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public Amazon.S3.S3StorageClass StorageClass { get; set; }
#endregion
#region Parameter StandardStorage
///
/// Specifies the STANDARD storage class, which is the default storage class for S3 objects.
/// Provides a 99.999999999% durability guarantee.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public SwitchParameter StandardStorage { get; set; }
#endregion
#region Parameter ReducedRedundancyStorage
///
/// Specifies S3 should use REDUCED_REDUNDANCY storage class for the object. This
/// provides a reduced (99.99%) durability guarantee at a lower
/// cost as compared to the STANDARD storage class. Use this
/// storage class for non-mission critical data or for data
/// that doesn’t require the higher level of durability that S3
/// provides with the STANDARD storage class.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public SwitchParameter ReducedRedundancyStorage { get; set; }
#endregion
#region Parameter ServerSideEncryption
///
///
/// The server-side encryption algorithm used when storing this object in Amazon S3 (for
/// example, AES256, aws:kms
).
///
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[AWSConstantClassSource("Amazon.S3.ServerSideEncryptionMethod")]
public Amazon.S3.ServerSideEncryptionMethod ServerSideEncryption { get; set; }
#endregion
#region Parameter ServerSideEncryptionKeyManagementServiceKeyId
///
/// Specifies the AWS KMS key for Amazon S3 to use to encrypt the object.
///
/// Specifies the Amazon Web Services KMS key ID to use for object encryption. All GET
/// and PUT requests for an object protected by Amazon Web Services KMS will fail if not
/// made via SSL or using SigV4. For information about configuring using any of the officially
/// supported Amazon Web Services SDKs and Amazon Web Services CLI, see Specifying
/// the Signature Version in Request Authentication in the Amazon S3 User Guide.
///
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String ServerSideEncryptionKeyManagementServiceKeyId { get; set; }
#endregion
#region Parameter WebsiteRedirectLocation
///
///
/// If the bucket is configured as a website, redirects requests for this object to another
/// object in the same bucket or to an external URL. Amazon S3 stores the value of this
/// header in the object metadata. This value is unique to each object and is not copied
/// when using the x-amz-metadata-directive
header. Instead, you may opt
/// to provide this header in combination with the directive.
///
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String WebsiteRedirectLocation { get; set; }
#endregion
#region Parameter Metadata
///
/// Metadata headers to set on the object.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.Collections.Hashtable Metadata { get; set; }
#endregion
#region Parameter HeaderCollection
///
/// Response headers to set on the object.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[Alias("Headers")]
public System.Collections.Hashtable HeaderCollection { get; set; }
#endregion
#region Parameter TagSet
///
/// One or more tags to apply to the object.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public Tag[] TagSet { get; set; }
#endregion
#endregion
#region Copy to Local File Parameters
#region Parameter LocalFile
///
/// The full path to the local file that will be created.
///
[Parameter(Position = 2, ParameterSetName = CopySingleObjectToLocalFile, Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Alias("File")]
[Amazon.PowerShell.Common.AWSRequiredParameter]
public System.String LocalFile { get; set; }
#endregion
#endregion
#region Copy to Local Folder Parameters
#region Parameter LocalFolder
///
///
/// The path to a local folder that will contain the downloaded object. If a relative path
/// is supplied, it will be resolved to a full path using the current session's location.
///
///
/// When copying to a local folder the object key is used as the filename. Note that object
/// keys that are not valid filenames for the host system could cause an exception to be thrown.
///
///
[Parameter(ParameterSetName = CopySingleObjectToLocalFolder, Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopyMultipleObjectsToLocalFolder, Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Alias("Folder")]
[Amazon.PowerShell.Common.AWSRequiredParameter]
public System.String LocalFolder { get; set; }
#endregion
#region Parameter KeyPrefix
///
/// Used to download multiple objects to the specified local folder. The supplied prefix
/// will be used to determine the set of objects to download that share the same key prefix.
/// You must specify either this parameter, or the -Key parameter, to determine what object(s) to
/// download.
///
[Parameter(ParameterSetName = CopyMultipleObjectsToLocalFolder, ValueFromPipelineByPropertyName = true)]
[Alias("SourcePrefix")]
public System.String KeyPrefix { get; set; }
#endregion
#endregion
#region Parameter ETagToMatch
///
/// Copies the object if its entity tag (ETag) matches the specified tag; otherwise return a PreconditionFailed.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String ETagToMatch { get; set; }
#endregion
#region Parameter ETagToNotMatch
///
/// Copies the object if its entity tag (ETag) is different than the specified Etag; otherwise returns an error.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String ETagToNotMatch { get; set; }
#endregion
#region Parameter ModifiedSinceDate
///
/// Copies the object if it has been modified since the specified time; otherwise returns an error.
/// This parameter is deprecated.
///
[Parameter(ValueFromPipelineByPropertyName = true)]
[System.ObsoleteAttribute("This property is deprecated and may result in the wrong timestamp being passed to" +
" the service, use UtcModifiedSinceDate instead.")]
public System.DateTime ModifiedSinceDate { get; set; }
#endregion
#region Parameter UnmodifiedSinceDate
///
/// Copies the object if it hasn't been modified since the specified time; otherwise returns a PreconditionFailed.
/// This parameter is deprecated.
///
[Parameter(ValueFromPipelineByPropertyName = true)]
[System.ObsoleteAttribute("This property is deprecated and may result in the wrong timestamp being passed to" +
" the service, use UtcUnmodifiedSinceDate instead.")]
public System.DateTime UnmodifiedSinceDate { get; set; }
#endregion
#region Parameter UtcModifiedSinceDate
///
/// Copies the object if it has been modified since the specified time; otherwise returns an error.
///
[Parameter(ValueFromPipelineByPropertyName = true)]
public System.DateTime UtcModifiedSinceDate { get; set; }
#endregion
#region Parameter UtcUnmodifiedSinceDate
///
/// Copies the object if it hasn't been modified since the specified time; otherwise returns a PreconditionFailed.
///
[Parameter(ValueFromPipelineByPropertyName = true)]
public System.DateTime UtcUnmodifiedSinceDate { get; set; }
#endregion
#region Parameter CopySourceServerSideEncryptionCustomerMethod
///
/// Specifies the server-side encryption algorithm used on the source object with the customer provided key.
/// Allowable values: None or AES256.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[AWSConstantClassSource("Amazon.S3.ServerSideEncryptionCustomerMethod")]
public Amazon.S3.ServerSideEncryptionCustomerMethod CopySourceServerSideEncryptionCustomerMethod { get; set; }
#endregion
#region Parameter CopySourceServerSideEncryptionCustomerProvidedKey
///
/// Specifies base64-encoded encryption key for Amazon S3 used on the source object.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String CopySourceServerSideEncryptionCustomerProvidedKey { get; set; }
#endregion
#region Parameter CopySourceServerSideEncryptionCustomerProvidedKeyMD5
///
/// Specifies base64-encoded MD5 of the encryption key for Amazon S3 used on the source object. This field is optional, the SDK will calculate the MD5 if this is not set.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
public System.String CopySourceServerSideEncryptionCustomerProvidedKeyMD5 { get; set; }
#endregion
#region Parameter ServerSideEncryptionCustomerMethod
///
/// Specifies the server-side encryption algorithm to be used with the customer provided key.
/// Allowable values: None or AES256.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFolder, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFile, ValueFromPipelineByPropertyName = true)]
[AWSConstantClassSource("Amazon.S3.ServerSideEncryptionCustomerMethod")]
public Amazon.S3.ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod { get; set; }
#endregion
#region Parameter ServerSideEncryptionCustomerProvidedKey
///
/// Specifies base64-encoded encryption key for Amazon S3 to use to decrypt the object.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFolder, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFile, ValueFromPipelineByPropertyName = true)]
public System.String ServerSideEncryptionCustomerProvidedKey { get; set; }
#endregion
#region Parameter ServerSideEncryptionCustomerProvidedKeyMD5
///
/// Specifies base64-encoded MD5 of the encryption key for Amazon S3 to use to decrypt the object. This field is optional,
/// the SDK will calculate the MD5 if this is not set.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFolder, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFile, ValueFromPipelineByPropertyName = true)]
public System.String ServerSideEncryptionCustomerProvidedKeyMD5 { get; set; }
#endregion
#region Parameter ChecksumAlgorithm
///
/// Indicates the algorithm you want Amazon S3 to use to create the checksum for the object.
/// For more information, see Checking
/// object integrity in the Amazon S3 User Guide.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[AWSConstantClassSource("Amazon.S3.ChecksumAlgorithm")]
public ChecksumAlgorithm ChecksumAlgorithm { get; set; }
#endregion
#region Parameter ChecksumMode
///
/// Specifies base64-encoded MD5 of the encryption key for Amazon S3 to use to decrypt the object. This field is optional,
/// the SDK will calculate the MD5 if this is not set.
///
[Parameter(ParameterSetName = CopyS3ObjectToS3Object, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFolder, ValueFromPipelineByPropertyName = true)]
[Parameter(ParameterSetName = CopySingleObjectToLocalFile, ValueFromPipelineByPropertyName = true)]
[AWSConstantClassSource("Amazon.S3.ChecksumMode")]
public ChecksumMode ChecksumMode { get; set; }
#endregion
#region Parameter Force
///
/// This parameter overrides confirmation prompts to force
/// the cmdlet to continue its operation. This parameter should always
/// be used with caution.
///
[Parameter(ValueFromPipelineByPropertyName = true)]
public SwitchParameter Force { get; set; }
#endregion
protected override void ProcessRecord()
{
base.ProcessRecord();
if (!ConfirmShouldProceed(this.Force.IsPresent, this.Key, "Copy-S3Object (CopyObject)"))
return;
var context = new CmdletContext
{
SourceBucket = this.BucketName,
SourceKey = this.Key,
SourceVersionId = this.VersionId,
DestinationBucket = this.DestinationBucket,
DestinationKey = this.DestinationKey,
SourceRegion = this.SourceRegion,
ContentType = this.ContentType,
ETagToMatch = this.ETagToMatch,
ETagToNotMatch = this.ETagToNotMatch,
WebsiteRedirectLocation = this.WebsiteRedirectLocation,
ServerSideEncryptionKeyManagementServiceKeyId = this.ServerSideEncryptionKeyManagementServiceKeyId,
ServerSideEncryptionCustomerMethod = this.ServerSideEncryptionCustomerMethod,
ServerSideEncryptionCustomerProvidedKey = this.ServerSideEncryptionCustomerProvidedKey,
ServerSideEncryptionCustomerProvidedKeyMD5 = this.ServerSideEncryptionCustomerProvidedKeyMD5,
CopySourceServerSideEncryptionCustomerMethod = this.CopySourceServerSideEncryptionCustomerMethod,
CopySourceServerSideEncryptionCustomerProvidedKey = this.CopySourceServerSideEncryptionCustomerProvidedKey,
CopySourceServerSideEncryptionCustomerProvidedKeyMD5 = this.CopySourceServerSideEncryptionCustomerProvidedKeyMD5,
ChecksumAlgorithm = this.ChecksumAlgorithm,
ChecksumMode = this.ChecksumMode,
Metadata = this.Metadata,
Headers = this.HeaderCollection
};
// this makes things simpler later
if (string.IsNullOrEmpty(context.DestinationBucket))
context.DestinationBucket = context.SourceBucket;
if (string.IsNullOrEmpty(context.DestinationKey))
context.DestinationKey = context.SourceKey;
switch (this.ParameterSetName)
{
case CopySingleObjectToLocalFile:
context.LocalFile = this.LocalFile;
break;
case CopySingleObjectToLocalFolder:
// transform to download to local file
var path = PSHelpers.PSPathToAbsolute(this.SessionState.Path, PSHelpers.PSPathToAbsolute(this.SessionState.Path, this.LocalFolder));
context.LocalFile = Path.Combine(path, this.Key);
break;
case CopyMultipleObjectsToLocalFolder:
context.OriginalKeyPrefix = this.KeyPrefix;
context.KeyPrefix = ReadS3ObjectCmdlet.rootIndicators.Contains(this.KeyPrefix, StringComparer.OrdinalIgnoreCase)
? "/" : AmazonS3Helper.CleanKey(this.KeyPrefix);
context.LocalFolder = PSHelpers.PSPathToAbsolute(this.SessionState.Path, this.LocalFolder);
break;
case CopyS3ObjectToS3Object:
context.TagSet = this.TagSet;
break;
}
if (ParameterWasBound("UtcModifiedSinceDate"))
context.UtcModifiedSinceDate = this.UtcModifiedSinceDate;
if (ParameterWasBound("UtcUnmodifiedSinceDate"))
context.UtcUnmodifiedSinceDate = this.UtcUnmodifiedSinceDate;
#pragma warning disable CS0618, CS0612 //A class member was marked with the Obsolete attribute
if (ParameterWasBound("ModifiedSinceDate"))
context.ModifiedSinceDate = this.ModifiedSinceDate;
if (ParameterWasBound("UnmodifiedSinceDate"))
context.UnmodifiedSinceDate = this.UnmodifiedSinceDate;
#pragma warning restore CS0618, CS0612 //A class member was marked with the Obsolete attribute
if (ParameterWasBound("MetadataDirective"))
context.MetadataDirective = this.MetadataDirective;
if (!string.IsNullOrEmpty(this.CannedACLName))
{
context.CannedACL = this.CannedACLName;
}
else if (this.PublicReadOnly.IsPresent)
{
context.CannedACL = S3CannedACL.PublicRead;
}
else if (this.PublicReadWrite.IsPresent)
{
context.CannedACL = S3CannedACL.PublicReadWrite;
}
if (ParameterWasBound("StorageClass"))
context.StorageClass = this.StorageClass;
else
{
if (this.StandardStorage.IsPresent)
context.StorageClass = S3StorageClass.Standard;
else if (this.ReducedRedundancyStorage.IsPresent)
context.StorageClass = S3StorageClass.ReducedRedundancy;
}
if (!string.IsNullOrEmpty(this.ServerSideEncryption))
{
context.ServerSideEncryptionMethod = AmazonS3Helper.Convert(this.ServerSideEncryption);
}
var output = Execute(context) as CmdletOutput;
ProcessOutput(output);
}
#region IExecutor Members
public object Execute(ExecutorContext context)
{
var cmdletContext = context as CmdletContext;
if (!string.IsNullOrEmpty(cmdletContext.LocalFile))
return CopyS3ObjectToLocalFile(context);
if (!string.IsNullOrEmpty(cmdletContext.KeyPrefix))
return CopyS3ObjectsToLocalFolder(context);
// S3 requires multi-part operation for objects > 5GB
var objectSize = GetSourceObjectData(cmdletContext.SourceBucket, cmdletContext.SourceKey).Size;
return objectSize > FiveGigabytes ? MultipartCopyS3ObjectToS3(context, objectSize) : CopyS3ObjectToS3(context);
}
public ExecutorContext CreateContext()
{
return new CmdletContext();
}
#endregion
private S3Object GetSourceObjectData(string bucketName, string objectKey)
{
var copySourceRegionArgs = new StandaloneRegionArguments
{
Region = this.SourceRegion,
ProfileLocation = this.ProfileLocation
};
copySourceRegionArgs.TryGetRegion(true, out var region, out var regionSource, SessionState);
var sourceRegionClient = CreateClient(_CurrentCredentials, region);
var request = new ListObjectsRequest
{
BucketName = bucketName,
Prefix = objectKey.TrimStart('/')
};
var response = CallAWSServiceOperation(sourceRegionClient, request);
return response.S3Objects[0];
}
private S3Object GetObjectData(IAmazonS3 s3Client, string bucketName, string objectKey)
{
// The underlying S3 api does not like listing with prefixes starting with / so strip
// (eg Copy-S3Object -BucketName test-bucket -Key /data/sample.txt -DestinationKey /data/sample-copy.txt)
var request = new ListObjectsRequest
{
BucketName = bucketName,
Prefix = objectKey.TrimStart('/')
};
var response = CallAWSServiceOperation(s3Client, request);
return response.S3Objects[0];
}
#region S3 to S3 Copy
private object CopyS3ObjectToS3(ExecutorContext context)
{
var cmdletContext = context as CmdletContext;
var request = new CopyObjectRequest();
request.SourceBucket = cmdletContext.SourceBucket;
request.SourceKey = cmdletContext.SourceKey;
if (cmdletContext.SourceVersionId != null)
request.SourceVersionId = cmdletContext.SourceVersionId;
request.DestinationBucket = cmdletContext.DestinationBucket;
request.DestinationKey = cmdletContext.DestinationKey;
if (cmdletContext.ContentType != null)
request.ContentType = cmdletContext.ContentType;
if (cmdletContext.ETagToMatch != null)
request.ETagToMatch = cmdletContext.ETagToMatch;
if (cmdletContext.ETagToNotMatch != null)
request.ETagToNotMatch = cmdletContext.ETagToNotMatch;
if (cmdletContext.MetadataDirective != null)
request.MetadataDirective = cmdletContext.MetadataDirective.Value;
if (cmdletContext.CannedACL != null)
request.CannedACL = cmdletContext.CannedACL.Value;
if (cmdletContext.StorageClass != null)
request.StorageClass = cmdletContext.StorageClass.Value;
if (cmdletContext.ServerSideEncryptionMethod != null)
request.ServerSideEncryptionMethod = cmdletContext.ServerSideEncryptionMethod.Value;
if (cmdletContext.WebsiteRedirectLocation != null)
request.WebsiteRedirectLocation = cmdletContext.WebsiteRedirectLocation;
if (cmdletContext.ServerSideEncryptionKeyManagementServiceKeyId != null)
request.ServerSideEncryptionKeyManagementServiceKeyId = cmdletContext.ServerSideEncryptionKeyManagementServiceKeyId;
if (cmdletContext.UtcModifiedSinceDate.HasValue)
request.ModifiedSinceDateUtc = cmdletContext.UtcModifiedSinceDate.Value;
if (cmdletContext.UtcUnmodifiedSinceDate.HasValue)
request.UnmodifiedSinceDateUtc = cmdletContext.UtcUnmodifiedSinceDate.Value;
#pragma warning disable CS0618, CS0612 //A class member was marked with the Obsolete attribute
if (cmdletContext.ModifiedSinceDate.HasValue)
{
if (cmdletContext.UtcModifiedSinceDate != null)
{
throw new ArgumentException("Parameters ModifiedSinceDate and UtcModifiedSinceDate are mutually exclusive.");
}
request.ModifiedSinceDate = cmdletContext.ModifiedSinceDate.Value;
}
if (cmdletContext.UnmodifiedSinceDate.HasValue)
{
if (cmdletContext.UtcUnmodifiedSinceDate != null)
{
throw new ArgumentException("Parameters UnmodifiedSinceDate and UtcUnmodifiedSinceDate are mutually exclusive.");
}
request.UnmodifiedSinceDate = cmdletContext.UnmodifiedSinceDate.Value;
}
#pragma warning restore CS0618, CS0612 //A class member was marked with the Obsolete attribute
request.CopySourceServerSideEncryptionCustomerMethod = cmdletContext.CopySourceServerSideEncryptionCustomerMethod;
request.CopySourceServerSideEncryptionCustomerProvidedKey = cmdletContext.CopySourceServerSideEncryptionCustomerProvidedKey;
request.CopySourceServerSideEncryptionCustomerProvidedKeyMD5 = cmdletContext.CopySourceServerSideEncryptionCustomerProvidedKeyMD5;
request.ServerSideEncryptionCustomerMethod = cmdletContext.ServerSideEncryptionCustomerMethod;
request.ServerSideEncryptionCustomerProvidedKey = cmdletContext.ServerSideEncryptionCustomerProvidedKey;
request.ServerSideEncryptionCustomerProvidedKeyMD5 = cmdletContext.ServerSideEncryptionCustomerProvidedKeyMD5;
request.ChecksumAlgorithm = cmdletContext.ChecksumAlgorithm;
if (cmdletContext.TagSet != null)
request.TagSet.AddRange(cmdletContext.TagSet);
AmazonS3Helper.SetMetadataAndHeaders(request, cmdletContext.Metadata, cmdletContext.Headers);
// issue call
using (var client = Client ?? CreateClient(_CurrentCredentials, _RegionEndpoint))
{
Utils.Common.WriteVerboseEndpointMessage(this, client.Config, "Amazon S3", "CopyObject");
CmdletOutput output;
try
{
var response = CallAWSServiceOperation(client, request);
var objectData = GetObjectData(Client,
request.DestinationBucket,
string.IsNullOrEmpty(cmdletContext.DestinationKey)
? cmdletContext.SourceKey : cmdletContext.DestinationKey);
output = new CmdletOutput
{
PipelineOutput = objectData,
ServiceResponse = response
};
}
catch (Exception e)
{
output = new CmdletOutput { ErrorResponse = e };
}
return output;
}
}
#endregion
#region S3 to S3 MultiPart Copy
private object MultipartCopyS3ObjectToS3(ExecutorContext context, long objectSize)
{
var cmdletContext = context as CmdletContext;
const string activityFormat = "Copying {0}:{1} to {2}:{3}";
const string msgFormat = "Copied {0:N0} of {1:N0} bytes";
CmdletOutput output = null;
var activity = string.Format(activityFormat,
cmdletContext.SourceBucket,
cmdletContext.SourceKey,
cmdletContext.DestinationBucket,
cmdletContext.DestinationKey);
Utils.Common.WriteVerboseEndpointMessage(this, Client.Config, "Amazon S3", "CopyObject");
WriteProgressRecord(activity, string.Format(msgFormat, 0, objectSize), 0);
var initiateRequest = new InitiateMultipartUploadRequest();
PopulateRequest(cmdletContext, initiateRequest);
var initiateResponse = CallAWSServiceOperation(Client, initiateRequest);
var uploadId = initiateResponse.UploadId;
var copyController = new MultiPartObjectCopyController(Client, uploadId, objectSize, cmdletContext);
try
{
copyController.Run();
while (true)
{
Thread.Sleep(1000);
var percentDone = (int)((double)copyController.BytesUploaded / objectSize * 100);
var progressMsg = string.Format(msgFormat, copyController.BytesUploaded, objectSize);
WriteProgressRecord(activity, progressMsg, percentDone);
if (copyController.ShouldExit())
break;
}
if (copyController.Error != null)
{
// leaving uploadId set will cause us to abort the upload so the
// user is not charged for incomplete uploads
throw new Exception("Error during upload copy of part", copyController.Error);
}
var completeRequest = new CompleteMultipartUploadRequest
{
UploadId = uploadId,
BucketName = cmdletContext.DestinationBucket,
Key = cmdletContext.DestinationKey,
PartETags = copyController.ETags
};
CallAWSServiceOperation(Client, completeRequest);
uploadId = null;
}
finally
{
if (string.IsNullOrEmpty(uploadId))
{
WriteProgressCompleteRecord(activity, "Copy object completed");
var objectData = GetObjectData(Client, cmdletContext.DestinationBucket, cmdletContext.DestinationKey);
output = new CmdletOutput
{
PipelineOutput = objectData
};
}
else
{
var abortRequest = new AbortMultipartUploadRequest
{
UploadId = uploadId,
BucketName = cmdletContext.DestinationBucket,
Key = cmdletContext.DestinationKey
};
CallAWSServiceOperation(Client, abortRequest);
WriteProgressCompleteRecord(activity, "Operation failed");
}
}
return output;
}
private void PopulateRequest(CmdletContext cmdletContext, InitiateMultipartUploadRequest request)
{
request.BucketName = cmdletContext.DestinationBucket;
request.Key = cmdletContext.DestinationKey;
if (cmdletContext.ContentType != null)
request.ContentType = cmdletContext.ContentType;
if (cmdletContext.CannedACL != null)
request.CannedACL = cmdletContext.CannedACL.Value;
if (cmdletContext.StorageClass != null)
request.StorageClass = cmdletContext.StorageClass.Value;
if (cmdletContext.ServerSideEncryptionMethod != null)
request.ServerSideEncryptionMethod = cmdletContext.ServerSideEncryptionMethod.Value;
if (cmdletContext.ServerSideEncryptionKeyManagementServiceKeyId != null)
request.ServerSideEncryptionKeyManagementServiceKeyId = cmdletContext.ServerSideEncryptionKeyManagementServiceKeyId;
request.ServerSideEncryptionCustomerMethod = cmdletContext.ServerSideEncryptionCustomerMethod;
request.ServerSideEncryptionCustomerProvidedKey = cmdletContext.ServerSideEncryptionCustomerProvidedKey;
request.ServerSideEncryptionCustomerProvidedKeyMD5 = cmdletContext.ServerSideEncryptionCustomerProvidedKeyMD5;
request.ChecksumAlgorithm = cmdletContext.ChecksumAlgorithm;
if (cmdletContext.WebsiteRedirectLocation != null)
request.WebsiteRedirectLocation = cmdletContext.WebsiteRedirectLocation;
if (cmdletContext.TagSet != null)
request.TagSet.AddRange(cmdletContext.TagSet);
AmazonS3Helper.SetMetadataAndHeaders(request, cmdletContext.Metadata, cmdletContext.Headers);
}
#endregion
#region S3 to Local File or Folder Copy
private object CopyS3ObjectToLocalFile(ExecutorContext context)
{
// Adapted from Read-S3Object's single file mode
var cmdletContext = context as CmdletContext;
var request = new TransferUtilityDownloadRequest
{
BucketName = cmdletContext.SourceBucket,
FilePath = cmdletContext.LocalFile,
Key = cmdletContext.SourceKey
};
if (!string.IsNullOrEmpty(cmdletContext.SourceVersionId))
request.VersionId = cmdletContext.SourceVersionId;
if (cmdletContext.UtcModifiedSinceDate.HasValue)
request.ModifiedSinceDateUtc = cmdletContext.UtcModifiedSinceDate.Value;
if (cmdletContext.UtcUnmodifiedSinceDate.HasValue)
request.UnmodifiedSinceDateUtc = cmdletContext.UtcUnmodifiedSinceDate.Value;
#pragma warning disable CS0618, CS0612 //A class member was marked with the Obsolete attribute
if (cmdletContext.ModifiedSinceDate.HasValue)
{
if (cmdletContext.UtcModifiedSinceDate != null)
{
throw new ArgumentException("Parameters ModifiedSinceDate and UtcModifiedSinceDate are mutually exclusive.");
}
request.ModifiedSinceDate = cmdletContext.ModifiedSinceDate.Value;
}
if (cmdletContext.UnmodifiedSinceDate.HasValue)
{
if (cmdletContext.UtcUnmodifiedSinceDate != null)
{
throw new ArgumentException("Parameters UnmodifiedSinceDate and UtcUnmodifiedSinceDate are mutually exclusive.");
}
request.UnmodifiedSinceDate = cmdletContext.UnmodifiedSinceDate.Value;
}
#pragma warning restore CS0618, CS0612 //A class member was marked with the Obsolete attribute
request.ServerSideEncryptionCustomerMethod = cmdletContext.ServerSideEncryptionCustomerMethod;
request.ServerSideEncryptionCustomerProvidedKey = cmdletContext.ServerSideEncryptionCustomerProvidedKey;
request.ServerSideEncryptionCustomerProvidedKeyMD5 = cmdletContext.ServerSideEncryptionCustomerProvidedKeyMD5;
request.ChecksumMode = cmdletContext.ChecksumMode;
CmdletOutput output;
using (var tu = new TransferUtility(Client ?? CreateClient(_CurrentCredentials, _RegionEndpoint)))
{
Utils.Common.WriteVerboseEndpointMessage(this, Client.Config, "Amazon S3", "GetObject");
var runner = new ProgressRunner(this);
var tracker = new ReadS3ObjectCmdlet.DownloadFileProgressTracker(runner, handler => request.WriteObjectProgressEvent += handler, cmdletContext.SourceKey);
output = runner.SafeRun(() => tu.Download(request), tracker);
if (output.ErrorResponse == null)
output.PipelineOutput = new FileInfo(cmdletContext.LocalFile);
}
return output;
}
private object CopyS3ObjectsToLocalFolder(ExecutorContext context)
{
// Adapted from Read-S3Object's folder download
var cmdletContext = context as CmdletContext;
var request = new TransferUtilityDownloadDirectoryRequest
{
BucketName = cmdletContext.SourceBucket,
LocalDirectory = cmdletContext.LocalFolder,
S3Directory = cmdletContext.KeyPrefix
};
if (cmdletContext.UtcModifiedSinceDate.HasValue)
request.ModifiedSinceDateUtc = cmdletContext.UtcModifiedSinceDate.Value;
if (cmdletContext.UtcUnmodifiedSinceDate.HasValue)
request.UnmodifiedSinceDateUtc = cmdletContext.UtcUnmodifiedSinceDate.Value;
#pragma warning disable CS0618, CS0612 //A class member was marked with the Obsolete attribute
if (cmdletContext.ModifiedSinceDate.HasValue)
{
if (cmdletContext.UtcModifiedSinceDate != null)
{
throw new ArgumentException("Parameters ModifiedSinceDate and UtcModifiedSinceDate are mutually exclusive.");
}
request.ModifiedSinceDate = cmdletContext.ModifiedSinceDate.Value;
}
if (cmdletContext.UnmodifiedSinceDate.HasValue)
{
if (cmdletContext.UtcUnmodifiedSinceDate != null)
{
throw new ArgumentException("Parameters UnmodifiedSinceDate and UtcUnmodifiedSinceDate are mutually exclusive.");
}
request.UnmodifiedSinceDate = cmdletContext.UnmodifiedSinceDate.Value;
}
#pragma warning restore CS0618, CS0612 //A class member was marked with the Obsolete attribute
CmdletOutput output;
using (var tu = new TransferUtility(Client ?? CreateClient(_CurrentCredentials, _RegionEndpoint)))
{
Utils.Common.WriteVerboseEndpointMessage(this, Client.Config, "Amazon S3 object download APIs");
var runner = new ProgressRunner(this);
var tracker = new ReadS3ObjectCmdlet.DownloadFolderProgressTracker(runner, handler => request.DownloadedDirectoryProgressEvent += handler);
output = runner.SafeRun(() => tu.DownloadDirectory(request), tracker);
if (output.ErrorResponse == null)
output.PipelineOutput = new DirectoryInfo(cmdletContext.LocalFolder);
WriteVerbose(string.Format("Downloaded {0} object(s) from bucket '{1}' with keyprefix '{2}' to '{3}'",
tracker.DownloadedCount,
cmdletContext.SourceBucket,
cmdletContext.OriginalKeyPrefix,
cmdletContext.LocalFolder));
}
return output;
}
#endregion
#region AWS Service Operation Call
private Amazon.S3.Model.CopyObjectResponse CallAWSServiceOperation(IAmazonS3 client, Amazon.S3.Model.CopyObjectRequest request)
{
try
{
#if DESKTOP
return client.CopyObject(request);
#elif CORECLR
return client.CopyObjectAsync(request).GetAwaiter().GetResult();
#else
#error "Unknown build edition"
#endif
}
catch (AmazonServiceException exc)
{
TestForNameResolutionException(client, exc);
}
return null;
}
private Amazon.S3.Model.ListObjectsResponse CallAWSServiceOperation(IAmazonS3 client, Amazon.S3.Model.ListObjectsRequest request)
{
try
{
#if DESKTOP
return client.ListObjects(request);
#elif CORECLR
return client.ListObjectsAsync(request).GetAwaiter().GetResult();
#else
#error "Unknown build edition"
#endif
}
catch (AmazonServiceException exc)
{
TestForNameResolutionException(client, exc);
}
return null;
}
private Amazon.S3.Model.InitiateMultipartUploadResponse CallAWSServiceOperation(IAmazonS3 client, Amazon.S3.Model.InitiateMultipartUploadRequest request)
{
try
{
#if DESKTOP
return client.InitiateMultipartUpload(request);
#elif CORECLR
return client.InitiateMultipartUploadAsync(request).GetAwaiter().GetResult();
#else
#error "Unknown build edition"
#endif
}
catch (AmazonServiceException exc)
{
TestForNameResolutionException(client, exc);
}
return null;
}
private Amazon.S3.Model.CompleteMultipartUploadResponse CallAWSServiceOperation(IAmazonS3 client, Amazon.S3.Model.CompleteMultipartUploadRequest request)
{
// not testing for name resolution error here since it would have already
// failed during the init call
#if DESKTOP
return client.CompleteMultipartUpload(request);
#elif CORECLR
return client.CompleteMultipartUploadAsync(request).GetAwaiter().GetResult();
#else
#error "Unknown build edition"
#endif
}
private Amazon.S3.Model.AbortMultipartUploadResponse CallAWSServiceOperation(IAmazonS3 client, Amazon.S3.Model.AbortMultipartUploadRequest request)
{
// not testing for name resolution error here since it would have already
// failed during the init call
#if DESKTOP
return client.AbortMultipartUpload(request);
#elif CORECLR
return client.AbortMultipartUploadAsync(request).GetAwaiter().GetResult();
#else
#error "Unknown build edition"
#endif
}
private void TestForNameResolutionException(IAmazonS3 client, Exception exc)
{
var webException = exc.InnerException as System.Net.WebException;
if (webException != null)
{
throw new Exception(Utils.Common.FormatNameResolutionFailureMessage(client.Config, webException.Message), webException);
}
throw exc;
}
#endregion
internal class CmdletContext : ExecutorContext
{
public String SourceBucket { get; set; }
public String SourceKey { get; set; }
public String DestinationBucket { get; set; }
public String DestinationKey { get; set; }
public Object SourceRegion { get; set; }
public String LocalFile { get; set; }
public String LocalFolder { get; set; }
public String OriginalKeyPrefix { get; set; }
public String KeyPrefix { get; set; }
public String ContentType { get; set; }
public String ETagToMatch { get; set; }
public String ETagToNotMatch { get; set; }
[System.ObsoleteAttribute]
public DateTime? ModifiedSinceDate { get; set; }
[System.ObsoleteAttribute]
public DateTime? UnmodifiedSinceDate { get; set; }
public DateTime? UtcModifiedSinceDate { get; set; }
public DateTime? UtcUnmodifiedSinceDate { get; set; }
public S3MetadataDirective? MetadataDirective { get; set; }
public S3CannedACL CannedACL { get; set; }
public String SourceVersionId { get; set; }
public S3StorageClass StorageClass { get; set; }
public ServerSideEncryptionMethod ServerSideEncryptionMethod { get; set; }
public string ServerSideEncryptionKeyManagementServiceKeyId { get; set; }
public ServerSideEncryptionCustomerMethod CopySourceServerSideEncryptionCustomerMethod { get; set; }
public string CopySourceServerSideEncryptionCustomerProvidedKey { get; set; }
public string CopySourceServerSideEncryptionCustomerProvidedKeyMD5 { get; set; }
public ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod { get; set; }
public string ServerSideEncryptionCustomerProvidedKey { get; set; }
public string ServerSideEncryptionCustomerProvidedKeyMD5 { get; set; }
public Hashtable Metadata { get; set; }
public Hashtable Headers { get; set; }
public String WebsiteRedirectLocation { get; set; }
public Tag[] TagSet { get; set; }
public ChecksumAlgorithm ChecksumAlgorithm { get; set; }
public ChecksumMode ChecksumMode { get; set; }
}
internal class MultiPartObjectCopyController
{
private static readonly long MinPartSize = 5 * (long)Math.Pow(2, 20);
private const int MaxNumberOfParts = 10000;
private const int MaxWorkerThreads = 5;
private readonly object _lock = new object();
private readonly long _objectSize;
private long _bytesUploaded;
private readonly CmdletContext _context;
private readonly string _uploadId;
private readonly IAmazonS3 _s3Client;
private int _nextPartToUpload = -1;
private Exception _errorException;
///
/// Byte range for each part and the service's etag value
/// on completion of the part upload
///
public class PartDetail
{
public int PartNumber { get; set; }
public long StartByte { get; set; }
public long Size { get; set; }
public string ETag { get; set; }
public string ChecksumSHA1 { get; set; }
public string ChecksumSHA256 { get; set; }
public string ChecksumCRC32 { get; set; }
public string ChecksumCRC32C { get; set; }
public PartETag ToPartETag()
{
var partETag = new PartETag(PartNumber, ETag);
partETag.ChecksumSHA1 = ChecksumSHA1;
partETag.ChecksumSHA256 = ChecksumSHA256;
partETag.ChecksumCRC32 = ChecksumCRC32;
partETag.ChecksumCRC32C = ChecksumCRC32C;
return partETag;
}
}
///
/// Contains the collection of parts we need to process
///
private readonly List _parts = new List();
///
/// The number of parts we've broken the upload into, based on
/// the max allowed by S3 in conjunction with the minimum part size
///
public int NumberOfParts { get { return _parts.Count; } }
public long BytesUploaded
{
get
{
long uploaded;
lock (_lock)
{
uploaded = _bytesUploaded;
}
return uploaded;
}
}
///
/// If set, an error was trapped by one of the upload threads
/// and the outer code should quit (ShouldExit will yield true)
/// and report this to the user. The exception is posted by the
/// first thread to encounter an error; errors on other threads
/// are dropped on the floor.
///
public Exception Error
{
get
{
lock (_lock)
{
return _errorException;
}
}
private set
{
lock (_lock)
{
if (_errorException == null)
{
_errorException = value;
}
}
}
}
public MultiPartObjectCopyController(IAmazonS3 s3Client, string uploadId, long objectSize, CmdletContext context)
{
_s3Client = s3Client;
_uploadId = uploadId;
_context = context;
_objectSize = objectSize;
PartitionIntoParts();
}
///
/// Indicates to the caller we're done either because we've encountered an error or
/// all parts have been processed
///
///
public bool ShouldExit()
{
lock (_lock)
{
if (Error != null)
return true;
if (_bytesUploaded == _objectSize)
return true;
}
return false;
}
///
/// Launches threads to process the part list and immediately
/// exits back to the caller.
///
public void Run()
{
var threadCount = _parts.Count > MaxWorkerThreads ? MaxWorkerThreads : 2;
for (var i = 0; i < threadCount; i++)
{
ThreadPool.QueueUserWorkItem(UploadPartCopyThreadWorker, this);
Thread.Sleep(100);
}
}
public List ETags
{
get
{
return _parts.Select(p => p.ToPartETag()).ToList();
}
}
private static void UploadPartCopyThreadWorker(object state)
{
var _this = state as MultiPartObjectCopyController;
Debug.Assert(_this != null, "_this != null");
#if DEBUG
Thread.CurrentThread.Name = string.Format("UploadPartCopyThreadWorker {0}:{1}",
_this._context.DestinationBucket,
_this._context.DestinationKey);
#endif
var part = _this.NextPartToUpload;
while (part != null)
{
var copyPartRequest = new CopyPartRequest
{
UploadId = _this._uploadId,
PartNumber = part.PartNumber,
FirstByte = part.StartByte,
LastByte = part.StartByte + part.Size - 1
};
_this.PopulateRequest(copyPartRequest);
try
{
var response = _this.CallAWSServiceOperation(_this._s3Client, copyPartRequest);
part.ETag = response.ETag;
part.ChecksumSHA1 = response.ChecksumSHA1;
part.ChecksumSHA256 = response.ChecksumSHA256;
part.ChecksumCRC32 = response.ChecksumCRC32;
part.ChecksumCRC32C = response.ChecksumCRC32C;
lock (_this._lock)
{
_this._bytesUploaded += part.Size;
}
}
catch (Exception e)
{
_this.Error = e;
}
part = _this.NextPartToUpload;
}
}
private PartDetail NextPartToUpload
{
get
{
if (Error == null)
{
var partIndex = Interlocked.Increment(ref _nextPartToUpload);
if (partIndex < _parts.Count)
return _parts[partIndex];
}
return null;
}
}
private void PopulateRequest(CopyPartRequest request)
{
request.SourceBucket = _context.SourceBucket;
request.SourceKey = _context.SourceKey;
request.DestinationBucket = _context.DestinationBucket;
request.DestinationKey = _context.DestinationKey;
if (string.IsNullOrEmpty(_context.SourceVersionId))
request.SourceVersionId = _context.SourceVersionId;
if (_context.ETagToMatch != null)
request.ETagToMatch = new List { _context.ETagToMatch };
if (_context.ETagToNotMatch != null)
request.ETagsToNotMatch = new List { _context.ETagToNotMatch };
if (_context.UtcModifiedSinceDate.HasValue)
request.ModifiedSinceDate = _context.UtcModifiedSinceDate.Value;
if (_context.UtcUnmodifiedSinceDate.HasValue)
request.UnmodifiedSinceDate = _context.UtcUnmodifiedSinceDate.Value;
#pragma warning disable CS0618, CS0612 //A class member was marked with the Obsolete attribute
if (_context.ModifiedSinceDate.HasValue)
{
if (_context.UtcModifiedSinceDate != null)
{
throw new ArgumentException("Parameters ModifiedSinceDate and UtcModifiedSinceDate are mutually exclusive.");
}
request.ModifiedSinceDate = _context.ModifiedSinceDate.Value;
}
if (_context.UnmodifiedSinceDate.HasValue)
{
if (_context.UtcUnmodifiedSinceDate != null)
{
throw new ArgumentException("Parameters UnmodifiedSinceDate and UtcUnmodifiedSinceDate are mutually exclusive.");
}
request.UnmodifiedSinceDate = _context.UnmodifiedSinceDate.Value;
}
#pragma warning restore CS0618, CS0612 //A class member was marked with the Obsolete attribute
request.CopySourceServerSideEncryptionCustomerMethod = _context.CopySourceServerSideEncryptionCustomerMethod;
request.CopySourceServerSideEncryptionCustomerProvidedKey = _context.CopySourceServerSideEncryptionCustomerProvidedKey;
request.CopySourceServerSideEncryptionCustomerProvidedKeyMD5 = _context.CopySourceServerSideEncryptionCustomerProvidedKeyMD5;
request.ServerSideEncryptionCustomerMethod = _context.ServerSideEncryptionCustomerMethod;
request.ServerSideEncryptionCustomerProvidedKey = _context.ServerSideEncryptionCustomerProvidedKey;
request.ServerSideEncryptionCustomerProvidedKeyMD5 = _context.ServerSideEncryptionCustomerProvidedKeyMD5;
}
private void PartitionIntoParts()
{
var partSize = (long)Math.Ceiling((double)_objectSize / MaxNumberOfParts);
if (partSize < MinPartSize)
{
partSize = MinPartSize;
}
long startByte = 0;
var remaining = _objectSize;
var partNumber = 1;
while (remaining > 0)
{
var part = new PartDetail
{
PartNumber = partNumber++,
StartByte = startByte,
Size = (remaining > partSize) ? partSize : remaining
};
_parts.Add(part);
remaining -= part.Size;
startByte = part.StartByte + part.Size;
}
}
private Amazon.S3.Model.CopyPartResponse CallAWSServiceOperation(IAmazonS3 client, Amazon.S3.Model.CopyPartRequest request)
{
// not testing for name resolution error here, since it would have already failed
// in the init call
#if DESKTOP
return client.CopyPart(request);
#elif CORECLR
return client.CopyPartAsync(request).GetAwaiter().GetResult();
#else
#error "Unknown build edition"
#endif
}
}
}
}