Skip to content

Commit afa9dad

Browse files
authored
Adds Func<Stream, Task> to replace Action<Stream> for GetObjectAsync() (#730)
1 parent c06d104 commit afa9dad

File tree

4 files changed

+147
-47
lines changed

4 files changed

+147
-47
lines changed

Minio.Functional.Tests/FunctionalTest.cs

Lines changed: 94 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717

1818
using System;
1919
using System.Collections.Generic;
20-
using System.ComponentModel;
2120
using System.Diagnostics;
2221
using System.Globalization;
2322
using System.IO;
2423
using System.Linq;
2524
using System.Net;
2625
using System.Net.Http;
27-
using System.Reflection;
2826
using System.Runtime.InteropServices;
2927
using System.Security.Cryptography;
3028
using System.Text;
@@ -273,6 +271,23 @@ public static string GetRandomName(int length = 5)
273271
return "minio-dotnet-example-" + result;
274272
}
275273

274+
internal static void generateRandomFile(string fileName)
275+
{
276+
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
277+
{
278+
var fileSize = 3L * 1024 * 1024 * 1024;
279+
var segments = fileSize / 10000;
280+
var last_seg = fileSize % 10000;
281+
var br = new BinaryWriter(fs);
282+
283+
for (long i = 0; i < segments; i++)
284+
br.Write(new byte[10000]);
285+
286+
br.Write(new byte[last_seg]);
287+
br.Close();
288+
}
289+
}
290+
276291
// Return true if running in Mint mode
277292
public static bool IsMintEnv()
278293
{
@@ -4023,36 +4038,6 @@ internal static async Task CopyObject_Test7(MinioClient minio)
40234038
}
40244039
}
40254040

4026-
public static void objPrint(object obj)
4027-
{
4028-
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj))
4029-
{
4030-
var name = descriptor.Name;
4031-
var value = descriptor.GetValue(obj);
4032-
Console.WriteLine("{0}={1}", name, value);
4033-
}
4034-
}
4035-
4036-
public static void Print(object obj)
4037-
{
4038-
foreach (var prop in obj.GetType()
4039-
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
4040-
{
4041-
var value = prop.GetValue(obj, new object[] { });
4042-
Console.WriteLine("{0} = {1}", prop.Name, value);
4043-
}
4044-
4045-
Console.WriteLine("DONE!\n\n");
4046-
}
4047-
4048-
public static void printDict(Dictionary<string, string> d)
4049-
{
4050-
if (d != null)
4051-
foreach (var kv in d)
4052-
Console.WriteLine(" {0} = {1}", kv.Key, kv.Value);
4053-
Console.WriteLine("DONE!\n\n");
4054-
}
4055-
40564041
internal static async Task CopyObject_Test8(MinioClient minio)
40574042
{
40584043
var startTime = DateTime.Now;
@@ -4788,6 +4773,83 @@ internal static async Task GetObject_3_OffsetLength_Tests(MinioClient minio)
47884773
}
47894774
}
47904775

4776+
internal static async Task GetObject_AsyncCallback_Test1(MinioClient minio)
4777+
{
4778+
var startTime = DateTime.Now;
4779+
var bucketName = GetRandomName(15);
4780+
var objectName = GetRandomObjectName(10);
4781+
string contentType = null;
4782+
var fileName = GetRandomName(10);
4783+
var destFileName = GetRandomName(10);
4784+
var args = new Dictionary<string, string>
4785+
{
4786+
{ "bucketName", bucketName },
4787+
{ "objectName", objectName },
4788+
{ "contentType", contentType }
4789+
};
4790+
4791+
try
4792+
{
4793+
// Create a large local file
4794+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) generateRandomFile(fileName);
4795+
else Bash("truncate -s 2G " + fileName);
4796+
4797+
// Create the bucket
4798+
await Setup_Test(minio, bucketName);
4799+
4800+
using (var filestream = new FileStream(File.OpenHandle(fileName), FileAccess.Read))
4801+
{
4802+
// Upload the large file, "fileName", into the bucket
4803+
var size = filestream.Length;
4804+
long file_read_size = 0;
4805+
var putObjectArgs = new PutObjectArgs()
4806+
.WithBucket(bucketName)
4807+
.WithObject(objectName)
4808+
.WithStreamData(filestream)
4809+
.WithObjectSize(filestream.Length)
4810+
.WithContentType(contentType);
4811+
4812+
await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false);
4813+
4814+
var callbackAsync = async delegate(Stream stream, CancellationToken cancellationToken)
4815+
{
4816+
using (var dest = new FileStream(destFileName, FileMode.Create, FileAccess.Write))
4817+
{
4818+
await stream.CopyToAsync(dest);
4819+
}
4820+
};
4821+
4822+
var getObjectArgs = new GetObjectArgs()
4823+
.WithBucket(bucketName)
4824+
.WithObject(objectName)
4825+
.WithCallbackStream(async (stream, cancellationToken) => await callbackAsync(stream, default));
4826+
4827+
await minio.GetObjectAsync(getObjectArgs).ConfigureAwait(false);
4828+
var writtenInfo = new FileInfo(destFileName);
4829+
file_read_size = writtenInfo.Length;
4830+
Assert.AreEqual(size, file_read_size);
4831+
4832+
new MintLogger("GetObject_LargeFile_Test0", getObjectSignature,
4833+
"Tests whether GetObject as stream works",
4834+
TestStatus.PASS, DateTime.Now - startTime, args: args).Log();
4835+
}
4836+
}
4837+
catch (Exception ex)
4838+
{
4839+
new MintLogger("GetObject_LargeFile_Test0", getObjectSignature, "Tests whether GetObject as stream works",
4840+
TestStatus.FAIL, DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log();
4841+
throw;
4842+
}
4843+
finally
4844+
{
4845+
if (File.Exists(fileName))
4846+
File.Delete(fileName);
4847+
if (File.Exists(destFileName))
4848+
File.Delete(destFileName);
4849+
await TearDown(minio, bucketName);
4850+
}
4851+
}
4852+
47914853
internal static async Task FGetObject_Test1(MinioClient minio)
47924854
{
47934855
var startTime = DateTime.Now;

Minio.Functional.Tests/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ public static void Main(string[] args)
141141
// and length parameters. Tests will be reported as GetObject_Test3,
142142
// GetObject_Test4 and GetObject_Test5.
143143
FunctionalTest.GetObject_3_OffsetLength_Tests(minioClient).Wait();
144+
// Test async callback function to download an object
145+
FunctionalTest.GetObject_AsyncCallback_Test1(minioClient).Wait();
144146

145147
// Test File GetObject and PutObject functions
146148
FunctionalTest.FGetObject_Test1(minioClient).Wait();

Minio/DataModel/ObjectOperationsArgs.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
using System.Net.Http;
2323
using System.Security.Cryptography;
2424
using System.Text;
25+
using System.Threading;
26+
using System.Threading.Tasks;
2527
using System.Xml;
2628
using System.Xml.Linq;
2729
using Minio.DataModel;
@@ -509,6 +511,7 @@ public GetObjectArgs()
509511
}
510512

511513
internal Action<Stream> CallBack { get; private set; }
514+
internal Func<Stream, CancellationToken, Task> FuncCallBack { get; private set; }
512515
internal long ObjectOffset { get; private set; }
513516
internal long ObjectLength { get; private set; }
514517
internal string FileName { get; private set; }
@@ -517,7 +520,7 @@ public GetObjectArgs()
517520
internal override void Validate()
518521
{
519522
base.Validate();
520-
if (CallBack == null && string.IsNullOrEmpty(FileName))
523+
if (CallBack == null && FuncCallBack == null && string.IsNullOrEmpty(FileName))
521524
throw new MinioException("Atleast one of " + nameof(CallBack) + ", CallBack method or " + nameof(FileName) +
522525
" file path to save need to be set for GetObject operation.");
523526
if (OffsetLengthSet)
@@ -551,7 +554,10 @@ private void Populate()
551554
internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder)
552555
{
553556
if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", $"{VersionId}");
554-
requestMessageBuilder.ResponseWriter = CallBack;
557+
558+
if (CallBack is not null) requestMessageBuilder.ResponseWriter = CallBack;
559+
else requestMessageBuilder.FunctionResponseWriter = FuncCallBack;
560+
555561
if (Headers.ContainsKey(S3ZipExtractKey))
556562
requestMessageBuilder.AddQueryParameter(S3ZipExtractKey, Headers[S3ZipExtractKey]);
557563

@@ -565,6 +571,12 @@ public GetObjectArgs WithCallbackStream(Action<Stream> cb)
565571
return this;
566572
}
567573

574+
public GetObjectArgs WithCallbackStream(Func<Stream, CancellationToken, Task> cb)
575+
{
576+
FuncCallBack = cb;
577+
return this;
578+
}
579+
568580
public GetObjectArgs WithOffsetAndLength(long offset, long length)
569581
{
570582
OffsetLengthSet = true;

Minio/Helper/OperationsHelper.cs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ private async Task<ObjectStat> getObjectHelper(GetObjectArgs args, CancellationT
5353
args.Validate();
5454
if (args.FileName != null)
5555
await getObjectFileAsync(args, objStat, cancellationToken);
56-
else
56+
else if (args.CallBack is not null)
5757
await getObjectStreamAsync(args, objStat, args.CallBack, cancellationToken);
58+
else await getObjectStreamAsync(args, objStat, args.FuncCallBack, cancellationToken);
5859
return objStat;
5960
}
6061

@@ -70,26 +71,26 @@ private Task getObjectFileAsync(GetObjectArgs args, ObjectStat objectStat,
7071
var length = objectStat.Size;
7172
var etag = objectStat.ETag;
7273

73-
long tempFileSize = 0;
7474
var tempFileName = $"{args.FileName}.{etag}.part.minio";
7575
if (!string.IsNullOrEmpty(args.VersionId)) tempFileName = $"{args.FileName}.{etag}.{args.VersionId}.part.minio";
7676
if (File.Exists(args.FileName)) File.Delete(args.FileName);
7777

7878
utils.ValidateFile(tempFileName);
7979
if (File.Exists(tempFileName)) File.Delete(tempFileName);
8080

81-
args = args.WithCallbackStream(stream =>
81+
var callbackAsync = async delegate(Stream stream, CancellationToken cancellationToken)
8282
{
83-
var fileStream = File.Create(tempFileName);
84-
stream.CopyTo(fileStream);
85-
fileStream.Dispose();
86-
var writtenInfo = new FileInfo(tempFileName);
87-
var writtenSize = writtenInfo.Length;
88-
if (writtenSize != length - tempFileSize)
89-
throw new IOException(tempFileName +
90-
": Unexpected data written. Expected = "
91-
+ (length - tempFileSize)
92-
+ ", written = " + writtenSize);
83+
using (var dest = new FileStream(tempFileName, FileMode.Create, FileAccess.Write))
84+
{
85+
await stream.CopyToAsync(dest);
86+
}
87+
};
88+
89+
var cts = new CancellationTokenSource();
90+
cts.CancelAfter(TimeSpan.FromMilliseconds(15));
91+
args.WithCallbackStream(async (stream, cancellationToken) =>
92+
{
93+
await callbackAsync(stream, cts.Token);
9394
utils.MoveWithReplace(tempFileName, args.FileName);
9495
});
9596
return getObjectStreamAsync(args, objectStat, null, cancellationToken);
@@ -114,6 +115,29 @@ private async Task getObjectStreamAsync(GetObjectArgs args, ObjectStat objectSta
114115
.ConfigureAwait(false);
115116
}
116117

118+
/// <summary>
119+
/// private helper method. It returns the specified portion or full object from the bucket
120+
/// </summary>
121+
/// <param name="args">GetObjectArgs Arguments Object encapsulates information like - bucket name, object name etc </param>
122+
/// <param name="objectStat">
123+
/// ObjectStat object encapsulates information like - object name, size, etag etc, represents
124+
/// Object Information
125+
/// </param>
126+
/// <param name="cb">
127+
/// Callback function to send/process Object contents using
128+
/// async Func object which takes Stream and CancellationToken as input
129+
/// and Task as output, if assigned
130+
/// </param>
131+
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
132+
private async Task getObjectStreamAsync(GetObjectArgs args, ObjectStat objectStat,
133+
Func<Stream, CancellationToken, Task> cb,
134+
CancellationToken cancellationToken = default)
135+
{
136+
var requestMessageBuilder = await CreateRequest(args).ConfigureAwait(false);
137+
using var response = await ExecuteTaskAsync(NoErrorHandlers, requestMessageBuilder, cancellationToken)
138+
.ConfigureAwait(false);
139+
}
140+
117141
/// <summary>
118142
/// private helper method to remove list of objects from bucket
119143
/// </summary>

0 commit comments

Comments
 (0)