Browse Source

Support metadata for PutObject and StatObject calls (#143)

pull/154/head
poornas 8 years ago
committed by Harshavardhana
parent
commit
81744a6415
  1. 8
      Docs/API.md
  2. 37
      Minio.Functional.Tests/FunctionalTest.cs
  3. 8
      Minio/ApiEndpoints/IObjectOperations.cs
  4. 92
      Minio/ApiEndpoints/ObjectOperations.cs
  5. 6
      Minio/DataModel/ObjectStat.cs
  6. 9
      Minio/MinioClient.cs

8
Docs/API.md

@ -765,7 +765,7 @@ catch (MinioException e)
<a name="putObject"></a>
### PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType)
` Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType, CancellationToken cancellationToken = default(CancellationToken))`
` Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType,Dictionary<string,string> metaData=null, CancellationToken cancellationToken = default(CancellationToken))`
Uploads contents from a stream to objectName.
@ -781,6 +781,8 @@ __Parameters__
| ``data`` | _Stream_ | Stream to upload |
| ``size`` | _long_ | size of stream |
| ``contentType`` | _string_ | Content type of the file. Defaults to "application/octet-stream" |
| ``metaData`` | _Dictionary<string,string>_ | Dictionary of metadata headers. Defaults to null. |
| ``cancellationToken``| _System.Threading.CancellationToken_ | Optional parameter. Defaults to default(CancellationToken) |
@ -822,7 +824,7 @@ catch(MinioException e)
<a name="putObject"></a>
### PutObjectAsync(string bucketName, string objectName, string filePath, string contentType=null)
` Task PutObjectAsync(string bucketName, string objectName, string filePath, string contentType=null, CancellationToken cancellationToken = default(CancellationToken))`
` Task PutObjectAsync(string bucketName, string objectName, string filePath, string contentType=null,Dictionary<string,string> metaData=null, CancellationToken cancellationToken = default(CancellationToken))`
Uploads contents from a file to objectName.
@ -837,6 +839,8 @@ __Parameters__
| ``objectName`` | _string_ | Object name in the bucket |
| ``fileName`` | _string_ | File to upload |
| ``contentType`` | _string_ | Content type of the file. Defaults to " |
| ``metadata`` | _Dictionary<string,string>_ | Dictionary of meta data headers and their values.Defaults to null.|
| ``cancellationToken``| _System.Threading.CancellationToken_ | Optional parameter. Defaults to default(CancellationToken) |

37
Minio.Functional.Tests/FunctionalTest.cs

@ -96,7 +96,6 @@ namespace Minio.Functional.Tests
// Set app Info
minioClient.SetAppInfo("app-name", "app-version");
// Set HTTP Tracing On
// minioClient.SetTraceOn();
@ -124,6 +123,7 @@ namespace Minio.Functional.Tests
PutObject_Test3(minioClient).Wait();
PutObject_Test4(minioClient).Wait();
PutObject_Test5(minioClient).Wait();
// Test StatObject function
StatObject_Test1(minioClient).Wait();
@ -364,8 +364,33 @@ namespace Minio.Functional.Tests
File.Delete(fileName);
Console.Out.WriteLine("Test4: PutobjectAsync with different content-type complete");
}
private async static Task PutObject_Tester(MinioClient minio, string bucketName, string objectName, string fileName = null, string contentType = "application/octet-stream", long size = 0)
private async static Task PutObject_Test5(MinioClient minio)
{
Console.Out.WriteLine("Test5: PutobjectAsync with custom metadata");
string bucketName = GetRandomName(15);
string objectName = GetRandomName(10);
string fileName = CreateFile(1 * MB);
string contentType = "custom/contenttype";
Dictionary<string, string> metaData = new Dictionary<string, string>(){
{ "x-amz-meta-customheader", "minio-dotnet"}
};
await Setup_Test(minio, bucketName);
ObjectStat statObject = await PutObject_Tester(minio, bucketName, objectName, fileName, contentType:contentType, metaData:metaData);
Assert.IsTrue(statObject != null);
Assert.IsTrue(statObject.metaData != null);
Dictionary<string, string> statMeta = new Dictionary<string, string>(statObject.metaData,StringComparer.OrdinalIgnoreCase);
Assert.IsTrue(statMeta.ContainsKey("x-amz-meta-customheader"));
Assert.IsTrue(statObject.metaData.ContainsKey("Content-Type") && statObject.metaData["Content-Type"].Equals("custom/contenttype"));
await TearDown(minio, bucketName);
File.Delete(fileName);
Console.Out.WriteLine("Test5: PutobjectAsync with different content-type complete");
}
private async static Task<ObjectStat> PutObject_Tester(MinioClient minio, string bucketName, string objectName, string fileName = null, string contentType = "application/octet-stream", long size = 0, Dictionary<string, string> metaData=null)
{
ObjectStat statObject = null;
try
{
byte[] bs = File.ReadAllBytes(fileName);
@ -388,8 +413,8 @@ namespace Minio.Functional.Tests
objectName,
filestream,
size,
contentType);
contentType,
metaData: metaData);
await minio.GetObjectAsync(bucketName, objectName,
(stream) =>
{
@ -402,7 +427,7 @@ namespace Minio.Functional.Tests
Assert.AreEqual(file_read_size, file_write_size);
File.Delete(tempFileName);
});
ObjectStat statObject = await minio.StatObjectAsync(bucketName, objectName);
statObject = await minio.StatObjectAsync(bucketName, objectName);
Assert.IsNotNull(statObject);
Assert.AreEqual(statObject.ObjectName, objectName);
Assert.AreEqual(statObject.Size, file_read_size);
@ -415,7 +440,7 @@ namespace Minio.Functional.Tests
Console.WriteLine("[Bucket] Exception: {0}", e);
Assert.Fail();
}
return statObject;
}
private async static Task StatObject_Test1(MinioClient minio)

8
Minio/ApiEndpoints/IObjectOperations.cs

@ -54,8 +54,8 @@ namespace Minio
/// <param name="size">Size of stream</param>
/// <param name="contentType">Content type of the new object, null defaults to "application/octet-stream"</param>
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType = null, CancellationToken cancellationToken = default(CancellationToken));
/// <param name="metaData">Optional Object metadata to be stored. Defaults to null.</param>
Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType = null, CancellationToken cancellationToken = default(CancellationToken), Dictionary<string, string> metadata = null);
/// <summary>
/// Removes an object with given name in specific bucket
@ -115,8 +115,8 @@ namespace Minio
/// <param name="fileName">Path of file to upload</param>
/// <param name="contentType">Content type of the new object, null defaults to "application/octet-stream"</param>
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
Task PutObjectAsync(string bucketName, string objectName, string filePath, string contentType = null, CancellationToken cancellationToken = default(CancellationToken));
/// <param name="metaData">Optional Object metadata to be stored. Defaults to null.</param>
Task PutObjectAsync(string bucketName, string objectName, string filePath, string contentType = null, CancellationToken cancellationToken = default(CancellationToken), Dictionary<string, string> metadata = null);
/// <summary>
/// Get an object. The object will be streamed to the callback given by the user.

92
Minio/ApiEndpoints/ObjectOperations.cs

@ -166,15 +166,15 @@ namespace Minio
/// <param name="fileName">Path of file to upload</param>
/// <param name="contentType">Content type of the new object, null defaults to "application/octet-stream"</param>
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
public async Task PutObjectAsync(string bucketName, string objectName, string fileName, string contentType = null, CancellationToken cancellationToken = default(CancellationToken))
/// <param name="metaData">Object metadata to be stored. Defaults to null.</param>
public async Task PutObjectAsync(string bucketName, string objectName, string fileName, string contentType = null, CancellationToken cancellationToken = default(CancellationToken), Dictionary<string, string> metaData = null)
{
utils.ValidateFile(fileName, contentType);
FileInfo fileInfo = new FileInfo(fileName);
long size = fileInfo.Length;
using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
await PutObjectAsync(bucketName, objectName, file, size, contentType, cancellationToken);
await PutObjectAsync(bucketName, objectName, file, size, contentType, cancellationToken, metaData);
}
}
@ -188,11 +188,27 @@ namespace Minio
/// <param name="contentType">Content type of the new object, null defaults to "application/octet-stream"</param>
/// <param name="data">Stream of bytes to send</param>
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
public async Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType = null, CancellationToken cancellationToken = default(CancellationToken))
/// <param name="metaData">Object metadata to be stored. Defaults to null.</param>
public async Task PutObjectAsync(string bucketName, string objectName, Stream data, long size, string contentType = null, CancellationToken cancellationToken = default(CancellationToken), Dictionary<string, string> metaData = null)
{
utils.validateBucketName(bucketName);
utils.validateObjectName(objectName);
if (metaData == null)
{
metaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
else
{
metaData = new Dictionary<string, string>(metaData, StringComparer.OrdinalIgnoreCase);
}
if (string.IsNullOrWhiteSpace(contentType))
{
contentType = "application/octet-stream";
}
if (! metaData.ContainsKey("Content-Type"))
{
metaData["Content-Type"] = contentType;
}
if (data == null)
{
throw new ArgumentNullException("Invalid input stream,cannot be null");
@ -206,7 +222,7 @@ namespace Minio
{
throw new UnexpectedShortReadException("Data read " + bytes.Length + " is shorter than the size " + size + " of input buffer.");
}
await this.PutObjectAsync(bucketName, objectName, null, 0, bytes, contentType, cancellationToken);
await this.PutObjectAsync(bucketName, objectName, null, 0, bytes, metaData, cancellationToken);
return;
}
// For all sizes greater than 5MiB do multipart.
@ -223,7 +239,7 @@ namespace Minio
if (uploadId == null)
{
uploadId = await this.NewMultipartUploadAsync(bucketName, objectName, contentType, cancellationToken);
uploadId = await this.NewMultipartUploadAsync(bucketName, objectName, metaData, cancellationToken);
}
else
{
@ -265,7 +281,7 @@ namespace Minio
if (!skipUpload)
{
string etag = await this.PutObjectAsync(bucketName, objectName, uploadId, partNumber, dataToCopy, contentType, cancellationToken);
string etag = await this.PutObjectAsync(bucketName, objectName, uploadId, partNumber, dataToCopy, metaData, cancellationToken);
totalParts[partNumber - 1] = new Part() { PartNumber = partNumber, ETag = etag, size = (long)expectedReadSize };
}
@ -399,16 +415,12 @@ namespace Minio
/// <param name="contentType"></param>
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
/// <returns></returns>
private async Task<string> NewMultipartUploadAsync(string bucketName, string objectName, string contentType, CancellationToken cancellationToken)
private async Task<string> NewMultipartUploadAsync(string bucketName, string objectName, Dictionary<string,string> metaData, CancellationToken cancellationToken)
{
var resource = "?uploads";
if (string.IsNullOrWhiteSpace(contentType))
{
contentType = "application/octet-stream";
}
var request = await this.CreateRequest(Method.POST, bucketName, objectName: objectName,
contentType: contentType, resourcePath: resource);
headerMap: metaData, resourcePath: resource);
var response = await this.ExecuteTaskAsync(this.NoErrorHandlers, request, cancellationToken);
@ -431,21 +443,23 @@ namespace Minio
/// <param name="contentType"></param>
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
/// <returns></returns>
private async Task<string> PutObjectAsync(string bucketName, string objectName, string uploadId, int partNumber, byte[] data, string contentType, CancellationToken cancellationToken)
private async Task<string> PutObjectAsync(string bucketName, string objectName, string uploadId, int partNumber, byte[] data, Dictionary<string,string> metaData, CancellationToken cancellationToken)
{
var resource = "";
if (!string.IsNullOrEmpty(uploadId) && partNumber > 0)
{
resource += "?uploadId=" + uploadId + "&partNumber=" + partNumber;
}
if (string.IsNullOrWhiteSpace(contentType))
// For multi-part upload requests, metadata needs to be passed in the NewMultiPartUpload request
string contentType = metaData["Content-Type"];
if (uploadId != null)
{
contentType = "application/octet-stream";
metaData = null;
}
var request = await this.CreateRequest(Method.PUT, bucketName,
objectName: objectName,
contentType: contentType,
headerMap: metaData,
body: data,
resourcePath: resource
);
@ -688,27 +702,37 @@ namespace Minio
DateTime lastModified = new DateTime();
string etag = "";
string contentType = null;
Dictionary<string,string> metaData = new Dictionary<string, string>();
//Supported headers for object.
List<string> supportedHeaders = new List<string> { "cache-control", "content-encoding", "content-type" };
foreach (Parameter parameter in response.Headers)
{
switch (parameter.Name)
if (parameter.Name.Equals("Content-Length"))
{
size = long.Parse(parameter.Value.ToString());
}
else if (parameter.Name.Equals("Last-Modified"))
{
lastModified = DateTime.Parse(parameter.Value.ToString());
}
else if (parameter.Name.Equals("ETag"))
{
etag = parameter.Value.ToString().Replace("\"", "");
}
else if (parameter.Name.Equals("Content-Type"))
{
contentType = parameter.Value.ToString();
metaData["Content-Type"] = contentType;
}
else if (supportedHeaders.Contains(parameter.Name.ToLower()) || parameter.Name.ToLower().StartsWith("x-amz-meta-"))
{
case "Content-Length":
size = long.Parse(parameter.Value.ToString());
break;
case "Last-Modified":
lastModified = DateTime.Parse(parameter.Value.ToString());
break;
case "ETag":
etag = parameter.Value.ToString().Replace("\"", "");
break;
case "Content-Type":
contentType = parameter.Value.ToString();
break;
default:
break;
metaData[parameter.Name] = parameter.Value.ToString();
}
}
return new ObjectStat(objectName, size, lastModified, etag, contentType);
return new ObjectStat(objectName, size, lastModified, etag, contentType, metaData);
}

6
Minio/DataModel/ObjectStat.cs

@ -15,6 +15,7 @@
*/
using System;
using System.Collections.Generic;
namespace Minio.DataModel
{
@ -28,20 +29,21 @@ namespace Minio.DataModel
/// <param name="lastModified">Last when object was modified</param>
/// <param name="etag">Unique entity tag for the object</param>
/// <param name="contentType">Object content type</param>
public ObjectStat(string objectName, long size, DateTime lastModified, string etag, string contentType)
public ObjectStat(string objectName, long size, DateTime lastModified, string etag, string contentType, Dictionary<string, string> metadata)
{
this.ObjectName = objectName;
this.Size = size;
this.LastModified = lastModified;
this.ETag = etag;
this.ContentType = contentType;
this.metaData = metadata;
}
public string ObjectName { get; private set; }
public long Size { get; private set; }
public DateTime LastModified { get; private set; }
public string ETag { get; private set; }
public string ContentType { get; private set; }
public Dictionary<string, string> metaData { get; private set; }
public override string ToString()
{
return string.Format("{0} : Size({1}) LastModified({2}) ETag({3}) Content-Type({4})",this.ObjectName, this.Size, this.LastModified, this.ETag, this.ContentType);

9
Minio/MinioClient.cs

@ -111,7 +111,7 @@ namespace Minio
/// <returns>A RestRequest</returns>
internal async Task<RestRequest> CreateRequest(Method method, string bucketName, string objectName = null,
Dictionary<string, string> headerMap = null,
string contentType = "application/xml",
string contentType = "application/octet-stream",
Object body = null, string resourcePath = null, string region = null)
{
// Validate bucket name and object name
@ -206,14 +206,9 @@ namespace Minio
}
if (contentType != null)
{
request.AddHeader("Content-Type", contentType);
}
if (headerMap != null)
{
foreach (KeyValuePair<string, string> entry in headerMap)
foreach (var entry in headerMap)
{
request.AddHeader(entry.Key, entry.Value);
}

Loading…
Cancel
Save