From 97324efc537b944b26afff0373611ed067640e78 Mon Sep 17 00:00:00 2001
From: P R <25353498+BigUstad@users.noreply.github.com>
Date: Mon, 1 Feb 2021 22:03:00 -0800
Subject: [PATCH] Add builder pattern support for CopyObject API. (#485)
---
Docs/API.md | 20 +-
Minio.Examples/Cases/CopyObject.cs | 26 +-
Minio.Examples/Cases/CopyObjectMetadata.cs | 18 +-
Minio.Examples/Cases/CopyObjectReplaceTags.cs | 57 +++
Minio.Functional.Tests/FunctionalTest.cs | 142 +++++-
Minio/ApiEndpoints/IObjectOperations.cs | 9 +
Minio/ApiEndpoints/ObjectOperations.cs | 265 ++++++++----
Minio/DataModel/BucketArgs.cs | 4 +-
Minio/DataModel/ObjectOperationsArgs.cs | 404 ++++++++++++++++++
Minio/DataModel/ObjectOperationsResponse.cs | 38 ++
Minio/DataModel/ObjectWriteArgs.cs | 61 +++
Minio/DataModel/Tagging.cs | 16 +
12 files changed, 929 insertions(+), 131 deletions(-)
create mode 100644 Minio.Examples/Cases/CopyObjectReplaceTags.cs
create mode 100644 Minio/DataModel/ObjectWriteArgs.cs
diff --git a/Docs/API.md b/Docs/API.md
index 56b8b7808..ff0c44fa5 100644
--- a/Docs/API.md
+++ b/Docs/API.md
@@ -2075,9 +2075,9 @@ catch(MinioException e)
```
-### CopyObjectAsync(string bucketName, string objectName, string destBucketName, string destObjectName = null, CopyConditions copyConditions = null, Dictionary metadata = null, ServerSideEncryption sseSrc = null, ServerSideEncryption sseDest = null)
+### CopyObjectAsync(CopyObjectArgs args)
-*`Task CopyObjectAsync(string bucketName, string objectName, string destBucketName, string destObjectName = null, CopyConditions copyConditions = null, Dictionary metadata = null, ServerSideEncryption sseSrc = null, ServerSideEncryption sseDest = null, CancellationToken cancellationToken = default(CancellationToken))`*
+*`Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))`*
Copies content from objectName to destObjectName.
@@ -2087,21 +2087,19 @@ __Parameters__
|Param | Type | Description |
|:--- |:--- |:--- |
-| ``bucketName`` | _string_ | Name of the source bucket |
-| ``objectName`` | _string_ | Object name in the source bucket to be copied |
-| ``destBucketName`` | _string_ | Destination bucket name |
-| ``destObjectName`` | _string_ | Destination object name to be created, if not provided defaults to source object name|
-| ``copyConditions`` | _CopyConditions_ | Map of conditions useful for applying restrictions on copy operation|
-| ``metadata`` | _Dictionary_ | Dictionary of meta data headers and their values on the destination side.Defaults to null.|
-| ``sseSrc`` | _ServerSideEncryption_ | Server-side encryption option for source object | Optional parameter. Defaults to null |
-| ``sseDest`` | _ServerSideEncryption_ | Server-side encryption option for destination object| Optional parameter. Defaults to null |
+| ``args`` | _CopyObjectArgs_ | Arguments object - bucket name, object name, destination bucket name, destination object name, copy conditions, metadata, Source SSE, Destination SSE. etc. |
| ``cancellationToken``| _System.Threading.CancellationToken_ | Optional parameter. Defaults to default(CancellationToken) |
| Return Type | Exceptions |
|:--- |:--- |
| ``Task`` | Listed Exceptions: |
-| | ``InvalidBucketNameException`` : upon invalid bucket name |
+| | ``AuthorizationException`` : upon access or secret key wrong or not found |
+| | ``InvalidBucketNameException`` : upon invalid bucket name |
+| | ``InvalidObjectNameException`` : upon invalid object name |
+| | ``BucketNotFoundException`` : upon bucket with name not found |
+| | ``ObjectNotFoundException`` : upon object with name not found |
+| | ``MalFormedXMLException`` : upon configuration XML in http request validation failure |
| | ``ConnectionException`` : upon connection error |
| | ``InternalClientException`` : upon internal library error |
| | ``ArgumentException`` : upon missing bucket/object names |
diff --git a/Minio.Examples/Cases/CopyObject.cs b/Minio.Examples/Cases/CopyObject.cs
index 3dc5c02aa..e65e12e57 100644
--- a/Minio.Examples/Cases/CopyObject.cs
+++ b/Minio.Examples/Cases/CopyObject.cs
@@ -1,5 +1,5 @@
/*
- * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
+ * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
using Minio.DataModel;
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
namespace Minio.Examples.Cases
@@ -34,14 +35,21 @@ public async static Task Run(MinioClient minio,
try
{
Console.WriteLine("Running example for API: CopyObjectAsync");
+ var metaData = new Dictionary
+ {
+ { "Test-Metadata", "Test Test" }
+ };
// Optionally pass copy conditions
- await minio.CopyObjectAsync(fromBucketName,
- fromObjectName,
- destBucketName,
- destObjectName,
- copyConditions: null,
- sseSrc: sseSrc,
- sseDest: sseDest);
+ CopySourceObjectArgs cpSrcArgs = new CopySourceObjectArgs()
+ .WithBucket(fromBucketName)
+ .WithObject(fromObjectName)
+ .WithServerSideEncryption(sseSrc);
+ CopyObjectArgs args = new CopyObjectArgs()
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName)
+ .WithCopyObjectSource(cpSrcArgs)
+ .WithServerSideEncryption(sseDest);
+ await minio.CopyObjectAsync(args);
Console.WriteLine("Copied object {0} from bucket {1} to bucket {2}", fromObjectName, fromBucketName, destBucketName);
Console.WriteLine();
}
@@ -51,4 +59,4 @@ await minio.CopyObjectAsync(fromBucketName,
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Minio.Examples/Cases/CopyObjectMetadata.cs b/Minio.Examples/Cases/CopyObjectMetadata.cs
index 646dc0e74..aa3e33df4 100644
--- a/Minio.Examples/Cases/CopyObjectMetadata.cs
+++ b/Minio.Examples/Cases/CopyObjectMetadata.cs
@@ -1,5 +1,5 @@
/*
- * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2018 MinIO, Inc.
+ * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2018-2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,12 +45,16 @@ public async static Task Run(MinioClient minio,
{ "Mynewkey", "my-new-value" }
};
- await minio.CopyObjectAsync(fromBucketName,
- fromObjectName,
- destBucketName,
- destObjectName,
- copyConditions:copyCond,
- metadata: metadata);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(fromBucketName)
+ .WithObject(fromObjectName)
+ .WithCopyConditions(copyCond);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName)
+ .WithHeaders(metadata)
+ .WithCopyObjectSource(copySourceObjectArgs);
+ await minio.CopyObjectAsync(copyObjectArgs);
Console.WriteLine($"Copied object {fromObjectName} from bucket {fromBucketName} to bucket {destBucketName}");
Console.WriteLine();
diff --git a/Minio.Examples/Cases/CopyObjectReplaceTags.cs b/Minio.Examples/Cases/CopyObjectReplaceTags.cs
new file mode 100644
index 000000000..875126fcc
--- /dev/null
+++ b/Minio.Examples/Cases/CopyObjectReplaceTags.cs
@@ -0,0 +1,57 @@
+/*
+ * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-2021 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading.Tasks;
+
+namespace Minio.Examples.Cases
+{
+ class CopyObjectReplaceTags
+ {
+ // Copy object from one bucket to another, replace tags in the copied object
+ public async static Task Run(MinioClient minio,
+ string fromBucketName = "from-bucket-name",
+ string fromObjectName = "from-object-name",
+ string destBucketName = "dest-bucket",
+ string destObjectName =" to-object-name")
+ {
+ try
+ {
+ Console.WriteLine("Running example for API: CopyObjectAsync with Tags");
+ var tags = new Dictionary
+ {
+ { "Test-TagKey", "Test-TagValue" },
+ };
+ CopySourceObjectArgs cpSrcArgs = new CopySourceObjectArgs()
+ .WithBucket(fromBucketName)
+ .WithObject(fromObjectName);
+ CopyObjectArgs args = new CopyObjectArgs()
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName)
+ .WithTagKeyValuePairs(tags)
+ .WithReplaceTagsDirective(true)
+ .WithCopyObjectSource(cpSrcArgs);
+ await minio.CopyObjectAsync(args).ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("[Bucket] Exception: {0}", e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Minio.Functional.Tests/FunctionalTest.cs b/Minio.Functional.Tests/FunctionalTest.cs
index b9c6abd62..a470787c5 100644
--- a/Minio.Functional.Tests/FunctionalTest.cs
+++ b/Minio.Functional.Tests/FunctionalTest.cs
@@ -57,8 +57,8 @@ public class FunctionalTest
private const string putObjectSignature1 = "Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType, Dictionary metaData=null, CancellationToken cancellationToken = default(CancellationToken))";
private const string putObjectSignature2 = "Task PutObjectAsync(string bucketName, string objectName, string filePath, string contentType=null, Dictionary metaData=null, CancellationToken cancellationToken = default(CancellationToken))";
private const string listenBucketNotificationsSignature = "IObservable ListenBucketNotificationsAsync(ListenBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))";
+ private const string copyObjectSignature = "Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))";
private const string statObjectSignature = "Task StatObjectAsync(StatObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))";
- private const string copyObjectSignature = "Task CopyObjectAsync(string bucketName, string objectName, string destBucketName, string destObjectName = null, CopyConditions copyConditions = null, CancellationToken cancellationToken = default(CancellationToken))";
private const string removeObjectSignature1 = "Task RemoveObjectAsync(RemoveObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))";
private const string removeObjectSignature2 = "Task> RemoveObjectsAsync(RemoveObjectsArgs, CancellationToken cancellationToken = default(CancellationToken))";
private const string removeIncompleteUploadSignature = "Task RemoveIncompleteUploadAsync(RemoveIncompleteUploadArgs args, CancellationToken cancellationToken = default(CancellationToken))";
@@ -1141,7 +1141,15 @@ await minio.PutObjectAsync(bucketName,
filestream, filestream.Length, null);
}
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName);
+
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(destBucketName)
@@ -1213,12 +1221,22 @@ await minio.PutObjectAsync(bucketName,
conditions.SetMatchETag("TestETag");
try
{
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, conditions);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithCopyConditions(conditions);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName);
+
+ await minio.CopyObjectAsync(copyObjectArgs);
}
catch (MinioException ex)
{
- Assert.AreEqual(ex.Message, "MinIO API responded with message=At least one of the pre-conditions you specified did not hold");
+ Console.WriteLine(ex.Message);
+ StringAssert.Equals(ex.Message, "MinIO API responded with message=At least one of the pre-conditions you specified did not hold");
}
RemoveObjectArgs rmArgs = new RemoveObjectArgs()
@@ -1278,7 +1296,16 @@ await minio.PutObjectAsync(bucketName,
CopyConditions conditions = new CopyConditions();
conditions.SetMatchETag(stats.ETag);
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, conditions);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithCopyConditions(conditions);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName);
+
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(destBucketName)
.WithObject(destObjectName)
@@ -1354,7 +1381,14 @@ await minio.PutObjectAsync(bucketName,
CopyConditions conditions = new CopyConditions();
conditions.SetMatchETag("TestETag");
// omit dest bucket name.
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName);
+
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(bucketName)
@@ -1430,8 +1464,15 @@ await minio.PutObjectAsync(bucketName,
conditions.SetByteRange(1024, 6291455);
// omit dest object name.
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, copyConditions: conditions);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithCopyConditions(conditions);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName);
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(bucketName)
.WithObject(objectName)
@@ -1520,15 +1561,24 @@ await minio.PutObjectAsync(bucketName,
CopyConditions conditions = new CopyConditions();
conditions.SetModified(new DateTime(2017, 8, 18));
// Should copy object since modification date header < object modification date.
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, conditions);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithCopyConditions(conditions);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName);
+
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(destBucketName)
.WithObject(destObjectName)
.WithFile(outFileName);
await minio.GetObjectAsync(getObjectArgs);
statObjectArgs = new StatObjectArgs()
- .WithBucket(destBucketName)
- .WithObject(destObjectName);
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName);
ObjectStat dstats = await minio.StatObjectAsync(statObjectArgs);
Assert.IsNotNull(dstats);
StringAssert.Equals(dstats.ObjectName, destObjectName);
@@ -1605,8 +1655,15 @@ await minio.PutObjectAsync(bucketName,
// Should not copy object since modification date header > object modification date.
try
{
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, conditions);
-
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithCopyConditions(conditions);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName);
+ await minio.CopyObjectAsync(copyObjectArgs);
}
catch (Exception ex)
{
@@ -1688,13 +1745,26 @@ await minio.PutObjectAsync(bucketName,
{ "Content-Type", "application/css" },
{ "Mynewkey", "test test" }
};
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, copyConditions:copyCond, metadata: metadata);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithCopyConditions(copyCond);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithHeaders(metadata)
+ .WithObject(destObjectName);
+
+ await minio.CopyObjectAsync(copyObjectArgs);
statObjectArgs = new StatObjectArgs()
.WithBucket(destBucketName)
.WithObject(destObjectName);
ObjectStat dstats = await minio.StatObjectAsync(statObjectArgs);
+ Assert.IsTrue(dstats.MetaData["Content-Type"] != null);
Assert.IsTrue(dstats.MetaData["Mynewkey"] != null);
+ StringAssert.Equals(dstats.MetaData["Content-Type"], "application/css");
+ StringAssert.Equals(dstats.MetaData["Mynewkey"], "test test");
RemoveObjectArgs rmArgs = new RemoveObjectArgs()
.WithBucket(bucketName)
.WithObject(objectName);
@@ -1767,8 +1837,16 @@ await minio.PutObjectAsync(bucketName,
filestream, filestream.Length, null, sse:ssec);
}
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, sseSrc:sseCpy, sseDest:ssecDst);
-
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithServerSideEncryption(sseCpy);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName)
+ .WithServerSideEncryption(ssecDst);
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(destBucketName)
.WithObject(destObjectName)
@@ -1841,7 +1919,16 @@ await minio.PutObjectAsync(bucketName,
filestream, filestream.Length, null, sse:ssec);
}
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, sseSrc:sseCpy, sseDest:null);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithServerSideEncryption(sseCpy);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName)
+ .WithServerSideEncryption(null);
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(destBucketName)
@@ -1916,8 +2003,16 @@ await minio.PutObjectAsync(bucketName,
filestream, filestream.Length, null, sse:ssec);
}
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, sseSrc:sseCpy, sseDest:sses3);
-
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithServerSideEncryption(sseCpy);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName)
+ .WithServerSideEncryption(sses3);
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(destBucketName)
.WithObject(destObjectName)
@@ -1987,7 +2082,16 @@ await minio.PutObjectAsync(bucketName,
filestream, filestream.Length, null, sse:sses3);
}
- await minio.CopyObjectAsync(bucketName, objectName, destBucketName, destObjectName, sseSrc:null, sseDest:sses3);
+ CopySourceObjectArgs copySourceObjectArgs = new CopySourceObjectArgs()
+ .WithBucket(bucketName)
+ .WithObject(objectName)
+ .WithServerSideEncryption(null);
+ CopyObjectArgs copyObjectArgs = new CopyObjectArgs()
+ .WithCopyObjectSource(copySourceObjectArgs)
+ .WithBucket(destBucketName)
+ .WithObject(destObjectName)
+ .WithServerSideEncryption(sses3);
+ await minio.CopyObjectAsync(copyObjectArgs);
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(destBucketName)
diff --git a/Minio/ApiEndpoints/IObjectOperations.cs b/Minio/ApiEndpoints/IObjectOperations.cs
index 0bd109640..9338e2d89 100644
--- a/Minio/ApiEndpoints/IObjectOperations.cs
+++ b/Minio/ApiEndpoints/IObjectOperations.cs
@@ -108,6 +108,14 @@ public interface IObjectOperations
/// Observable that returns delete error while deleting objects if any
Task> RemoveObjectsAsync(RemoveObjectsArgs args, CancellationToken cancellationToken = default(CancellationToken));
+ ///
+ /// Copy a source object into a new destination object.
+ ///
+ /// CopyObjectArgs Arguments Object which encapsulates bucket name, object name, destination bucket, destination object names, Copy conditions object, metadata, SSE source, destination objects
+ /// Optional cancellation token to cancel the operation
+ ///
+ Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default(CancellationToken));
+
///
/// Get an object. The object will be streamed to the callback given by the user.
///
@@ -215,6 +223,7 @@ public interface IObjectOperations
/// Optional Server-side encryption option for destination. Defaults to null.
/// Optional cancellation token to cancel the operation
///
+ [Obsolete("Use CopyObjectAsync method with CopyObjectArgs object. Refer CopyObject example code.")]
Task CopyObjectAsync(string bucketName, string objectName, string destBucketName, string destObjectName = null, CopyConditions copyConditions = null, Dictionary metadata = null, ServerSideEncryption sseSrc = null, ServerSideEncryption sseDest = null, CancellationToken cancellationToken = default(CancellationToken));
///
diff --git a/Minio/ApiEndpoints/ObjectOperations.cs b/Minio/ApiEndpoints/ObjectOperations.cs
index e46fcc72c..91caac544 100644
--- a/Minio/ApiEndpoints/ObjectOperations.cs
+++ b/Minio/ApiEndpoints/ObjectOperations.cs
@@ -21,6 +21,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -480,6 +481,173 @@ public async Task PresignedPutObjectAsync(PresignedPutObjectArgs args)
}
+ ///
+ /// Copy a source object into a new destination object.
+ ///
+ /// CopyObjectArgs Arguments Object which encapsulates bucket name, object name, destination bucket, destination object names, Copy conditions object, metadata, SSE source, destination objects
+ /// Optional cancellation token to cancel the operation
+ ///
+ public async Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ StatObjectArgs statArgs = new StatObjectArgs()
+ .WithBucket(args.CopySourceObject.BucketName)
+ .WithObject(args.CopySourceObject.ObjectName)
+ .WithServerSideEncryption(args.CopySourceObject.SSE);
+ ObjectStat stat = await this.StatObjectAsync(statArgs, cancellationToken: cancellationToken).ConfigureAwait(false);
+ args.WithCopyObjectSourceStats(stat);
+ args.Validate();
+ bool copyReplaceMeta = (args.CopySourceObject.CopyOperationConditions != null )?args.CopySourceObject.CopyOperationConditions.HasReplaceMetadataDirective() : false;
+ if (string.IsNullOrEmpty(args.ObjectName))
+ {
+ args.ObjectName = args.CopySourceObject.ObjectName;
+ }
+ if (args.CopySourceObject.SSE != null && args.CopySourceObject.SSE is SSECopy sSECopy )
+ {
+ args.SSE = sSECopy.CloneToSSEC();
+ }
+ if (!copyReplaceMeta)
+ {
+ args.HeaderMap = args.CopySourceObject.HeaderMap;
+ }
+ else
+ {
+ args.CopySourceObject.HeaderMap = null;
+ }
+ Dictionary meta = new Dictionary();
+ if (args.HeaderMap != null)
+ {
+ foreach (var item in args.HeaderMap)
+ {
+ var key = item.Key;
+ if (!OperationsUtil.IsSupportedHeader(item.Key) && !item.Key.StartsWith("x-amz-meta", StringComparison.OrdinalIgnoreCase))
+ {
+ key = "x-amz-meta-" + key.ToLowerInvariant();
+ }
+ meta[key] = item.Value;
+ }
+ }
+ args.HeaderMap = args.HeaderMap.Concat(meta).GroupBy(item => item.Key).ToDictionary(item => item.Key, item => item.First().Value);
+ long srcByteRangeSize = (args.CopySourceObject.CopyOperationConditions != null)? args.CopySourceObject.CopyOperationConditions.GetByteRange():0L;
+ long copySize = (srcByteRangeSize == 0) ? args.CopySourceObjectInfo.Size : srcByteRangeSize;
+ if ((srcByteRangeSize > args.CopySourceObjectInfo.Size) || ((srcByteRangeSize > 0) && (args.CopySourceObject.CopyOperationConditions.byteRangeEnd >= args.CopySourceObjectInfo.Size)))
+ {
+ throw new ArgumentException("Specified byte range (" + args.CopySourceObject.CopyOperationConditions.byteRangeStart.ToString() + "-" + args.CopySourceObject.CopyOperationConditions.byteRangeEnd.ToString() + ") does not fit within source object (size=" + args.CopySourceObjectInfo.Size.ToString() + ")");
+ }
+
+ if ((copySize > Constants.MaxSingleCopyObjectSize) || (srcByteRangeSize > 0 && (srcByteRangeSize != args.CopySourceObjectInfo.Size)))
+ {
+ MultipartCopyUploadArgs multiArgs = new MultipartCopyUploadArgs(args)
+ .WithCopySize(copySize);
+ await MultipartCopyUploadAsync(multiArgs, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ CopyObjectRequestArgs cpReqArgs = new CopyObjectRequestArgs(args)
+ .WithCopyOperationObjectType(typeof(CopyObjectResult));
+ if (cpReqArgs.CopySourceObject.SSE != null && cpReqArgs.CopySourceObject.SSE is SSECopy)
+ {
+ cpReqArgs.CopySourceObject.SSE.Marshal(args.HeaderMap);
+ }
+ if (cpReqArgs.SSE != null)
+ {
+ cpReqArgs.SSE.Marshal(args.HeaderMap);
+ }
+ await this.CopyObjectRequestAsync(cpReqArgs, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+
+ ///
+ /// Make a multi part copy upload for objects larger than 5GB or if CopyCondition specifies a byte range.
+ ///
+ /// MultipartCopyUploadArgs Arguments object encapsulating destination and source bucket, object names, copy conditions, size, metadata, SSE
+ /// Optional cancellation token to cancel the operation
+ private async Task MultipartCopyUploadAsync(MultipartCopyUploadArgs args, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ dynamic multiPartInfo = utils.CalculateMultiPartSize(args.CopySize);
+ double partSize = multiPartInfo.partSize;
+ double partCount = multiPartInfo.partCount;
+ double lastPartSize = multiPartInfo.lastPartSize;
+ Part[] totalParts = new Part[(int)partCount];
+
+ args.SSEHeaders = args.SSEHeaders ?? new Dictionary();
+
+ NewMultipartUploadArgs nmuArgs = new NewMultipartUploadArgs(args);
+ // No need to resume upload since this is a Server-side copy. Just initiate a new upload.
+ string uploadId = await this.NewMultipartUploadAsync(nmuArgs, cancellationToken).ConfigureAwait(false);
+ double expectedReadSize = partSize;
+ int partNumber;
+ for (partNumber = 1; partNumber <= partCount; partNumber++)
+ {
+ CopyConditions partCondition = args.CopySourceObject.CopyOperationConditions.Clone();
+ partCondition.byteRangeStart = (long)partSize * (partNumber - 1) + partCondition.byteRangeStart;
+ if (partNumber < partCount)
+ {
+ partCondition.byteRangeEnd = partCondition.byteRangeStart + (long)partSize - 1;
+ }
+ else
+ {
+ partCondition.byteRangeEnd = partCondition.byteRangeStart + (long)lastPartSize - 1;
+ }
+
+ var queryMap = new Dictionary();
+ if (!string.IsNullOrEmpty(uploadId) && partNumber > 0)
+ {
+ queryMap.Add("uploadId",uploadId);
+ queryMap.Add("partNumber",partNumber.ToString());
+ }
+
+ args.HeaderMap = args.HeaderMap ?? new Dictionary();
+ args.HeaderMap["x-amz-copy-source-range"] = "bytes=" + partCondition.byteRangeStart.ToString() + "-" + partCondition.byteRangeEnd.ToString();
+
+ if (args.CopySourceObject.SSE != null && args.CopySourceObject.SSE is SSECopy)
+ {
+ args.CopySourceObject.SSE.Marshal(args.SSEHeaders);
+ }
+ if (args.SSE != null)
+ {
+ args.SSE.Marshal(args.SSEHeaders);
+ }
+ CopyObjectRequestArgs cpPartArgs = new CopyObjectRequestArgs(args)
+ .WithCopyOperationObjectType(typeof(CopyPartResult));
+ CopyPartResult cpPartResult = (CopyPartResult)await this.CopyObjectRequestAsync(cpPartArgs, cancellationToken).ConfigureAwait(false);
+
+ totalParts[partNumber - 1] = new Part { PartNumber = partNumber, ETag = cpPartResult.ETag, Size = (long)expectedReadSize };
+ }
+
+ }
+
+
+ ///
+ /// Start a new multi-part upload request
+ ///
+ /// NewMultipartUploadArgs arguments object encapsulating bucket name, object name, Headers, SSE Headers
+ /// Optional cancellation token to cancel the operation
+ ///
+ private async Task NewMultipartUploadAsync(NewMultipartUploadArgs args, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ args.Validate();
+ RestRequest request = await this.CreateRequest(args).ConfigureAwait(false);
+ IRestResponse response = await this.ExecuteAsync(this.NoErrorHandlers, request, cancellationToken);
+ NewMultipartUploadResponse uploadResponse = new NewMultipartUploadResponse(response.StatusCode, response.Content);
+ return uploadResponse.UploadId;
+ }
+
+ ///
+ /// Create the copy request, execute it and return the copy result.
+ ///
+ /// CopyObjectRequestArgs Arguments Object encapsulating
+ /// Optional cancellation token to cancel the operation
+ private async Task