Skip to content

Commit c593247

Browse files
committed
Add Progress listeners for initiated, complete, and failed for simple upload
update docs and function namse update comments add tests/update comments remove response from progressargs add missing properties update test Refactor test rearrange test update tests add commented out tests comments comments update comments fix spelling use interlocked read and fix build dev config update comment fix internals
1 parent 1924fee commit c593247

File tree

5 files changed

+534
-3
lines changed

5 files changed

+534
-3
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"services": [
3+
{
4+
"serviceName": "S3",
5+
"type": "patch",
6+
"changeLogMessages": [
7+
"Added progress tracking events to simple upload",
8+
"Added PutObjectResponse to TransferUtilityUploadResponse mapping"
9+
]
10+
}
11+
]
12+
}

sdk/src/Services/S3/Custom/Transfer/Internal/SimpleUploadCommand.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ internal partial class SimpleUploadCommand : BaseCommand
4141
IAmazonS3 _s3Client;
4242
TransferUtilityConfig _config;
4343
TransferUtilityUploadRequest _fileTransporterRequest;
44+
long _totalTransferredBytes;
4445

4546
internal SimpleUploadCommand(IAmazonS3 s3Client, TransferUtilityConfig config, TransferUtilityUploadRequest fileTransporterRequest)
4647
{
@@ -103,9 +104,48 @@ private PutObjectRequest ConstructRequest()
103104

104105
private void PutObjectProgressEventCallback(object sender, UploadProgressArgs e)
105106
{
106-
var progressArgs = new UploadProgressArgs(e.IncrementTransferred, e.TransferredBytes, e.TotalBytes,
107-
e.CompensationForRetry, _fileTransporterRequest.FilePath);
107+
// Keep track of the total transferred bytes so that we can also return this value in case of failure
108+
long transferredBytes = Interlocked.Add(ref _totalTransferredBytes, e.IncrementTransferred - e.CompensationForRetry);
109+
110+
var progressArgs = new UploadProgressArgs(e.IncrementTransferred, transferredBytes, e.TotalBytes,
111+
e.CompensationForRetry, _fileTransporterRequest.FilePath, _fileTransporterRequest);
108112
this._fileTransporterRequest.OnRaiseProgressEvent(progressArgs);
109113
}
114+
115+
private void FireTransferInitiatedEvent()
116+
{
117+
var initiatedArgs = new UploadInitiatedEventArgs(
118+
request: _fileTransporterRequest,
119+
filePath: _fileTransporterRequest.FilePath,
120+
totalBytes: _fileTransporterRequest.ContentLength
121+
);
122+
123+
_fileTransporterRequest.OnRaiseTransferInitiatedEvent(initiatedArgs);
124+
}
125+
126+
private void FireTransferCompletedEvent(TransferUtilityUploadResponse response)
127+
{
128+
var completedArgs = new UploadCompletedEventArgs(
129+
request: _fileTransporterRequest,
130+
response: response,
131+
filePath: _fileTransporterRequest.FilePath,
132+
transferredBytes: Interlocked.Read(ref _totalTransferredBytes),
133+
totalBytes: _fileTransporterRequest.ContentLength
134+
);
135+
136+
_fileTransporterRequest.OnRaiseTransferCompletedEvent(completedArgs);
137+
}
138+
139+
private void FireTransferFailedEvent()
140+
{
141+
var failedArgs = new UploadFailedEventArgs(
142+
request: _fileTransporterRequest,
143+
filePath: _fileTransporterRequest.FilePath,
144+
transferredBytes: Interlocked.Read(ref _totalTransferredBytes),
145+
totalBytes: _fileTransporterRequest.ContentLength
146+
);
147+
148+
_fileTransporterRequest.OnRaiseTransferFailedEvent(failedArgs);
149+
}
110150
}
111151
}

sdk/src/Services/S3/Custom/Transfer/Internal/_async/SimpleUploadCommand.async.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,20 @@ await this.AsyncThrottler.WaitAsync(cancellationToken)
3838
.ConfigureAwait(continueOnCapturedContext: false);
3939
}
4040

41+
FireTransferInitiatedEvent();
42+
4143
var putRequest = ConstructRequest();
42-
await _s3Client.PutObjectAsync(putRequest, cancellationToken)
44+
var response = await _s3Client.PutObjectAsync(putRequest, cancellationToken)
4345
.ConfigureAwait(continueOnCapturedContext: false);
46+
47+
var mappedResponse = ResponseMapper.MapPutObjectResponse(response);
48+
49+
FireTransferCompletedEvent(mappedResponse);
50+
}
51+
catch (Exception)
52+
{
53+
FireTransferFailedEvent();
54+
throw;
4455
}
4556
finally
4657
{

sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadRequest.cs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using System.IO;
2626
using System.Text;
2727

28+
using Amazon.Runtime;
2829
using Amazon.Runtime.Internal;
2930
using Amazon.S3.Model;
3031
using Amazon.Util;
@@ -411,6 +412,132 @@ public List<Tag> TagSet
411412
/// </remarks>
412413
public event EventHandler<UploadProgressArgs> UploadProgressEvent;
413414

415+
/// <summary>
416+
/// The event for UploadInitiatedEvent notifications. All
417+
/// subscribers will be notified when a transfer operation
418+
/// starts.
419+
/// <para>
420+
/// The UploadInitiatedEvent is fired exactly once when
421+
/// a transfer operation begins. The delegates attached to the event
422+
/// will be passed information about the upload request and
423+
/// total file size, but no progress information.
424+
/// </para>
425+
/// </summary>
426+
/// <remarks>
427+
/// Subscribe to this event if you want to receive
428+
/// UploadInitiatedEvent notifications. Here is how:<br />
429+
/// 1. Define a method with a signature similar to this one:
430+
/// <code>
431+
/// private void uploadStarted(object sender, UploadInitiatedEventArgs args)
432+
/// {
433+
/// Console.WriteLine($"Upload started: {args.FilePath}");
434+
/// Console.WriteLine($"Total size: {args.TotalBytes} bytes");
435+
/// Console.WriteLine($"Bucket: {args.Request.BucketName}");
436+
/// Console.WriteLine($"Key: {args.Request.Key}");
437+
/// }
438+
/// </code>
439+
/// 2. Add this method to the UploadInitiatedEvent delegate's invocation list
440+
/// <code>
441+
/// TransferUtilityUploadRequest request = new TransferUtilityUploadRequest();
442+
/// request.UploadInitiatedEvent += uploadStarted;
443+
/// </code>
444+
/// </remarks>
445+
public event EventHandler<UploadInitiatedEventArgs> UploadInitiatedEvent;
446+
447+
/// <summary>
448+
/// The event for UploadCompletedEvent notifications. All
449+
/// subscribers will be notified when a transfer operation
450+
/// completes successfully.
451+
/// <para>
452+
/// The UploadCompletedEvent is fired exactly once when
453+
/// a transfer operation completes successfully. The delegates attached to the event
454+
/// will be passed information about the completed upload including
455+
/// the final response from S3 with ETag, VersionId, and other metadata.
456+
/// </para>
457+
/// </summary>
458+
/// <remarks>
459+
/// Subscribe to this event if you want to receive
460+
/// UploadCompletedEvent notifications. Here is how:<br />
461+
/// 1. Define a method with a signature similar to this one:
462+
/// <code>
463+
/// private void uploadCompleted(object sender, UploadCompletedEventArgs args)
464+
/// {
465+
/// Console.WriteLine($"Upload completed: {args.FilePath}");
466+
/// Console.WriteLine($"Transferred: {args.TransferredBytes} bytes");
467+
/// Console.WriteLine($"ETag: {args.Response.ETag}");
468+
/// Console.WriteLine($"S3 Key: {args.Response.Key}");
469+
/// Console.WriteLine($"Version ID: {args.Response.VersionId}");
470+
/// }
471+
/// </code>
472+
/// 2. Add this method to the UploadCompletedEvent delegate's invocation list
473+
/// <code>
474+
/// TransferUtilityUploadRequest request = new TransferUtilityUploadRequest();
475+
/// request.UploadCompletedEvent += uploadCompleted;
476+
/// </code>
477+
/// </remarks>
478+
public event EventHandler<UploadCompletedEventArgs> UploadCompletedEvent;
479+
480+
/// <summary>
481+
/// The event for UploadFailedEvent notifications. All
482+
/// subscribers will be notified when a transfer operation
483+
/// fails.
484+
/// <para>
485+
/// The UploadFailedEvent is fired exactly once when
486+
/// a transfer operation fails. The delegates attached to the event
487+
/// will be passed information about the failed upload including
488+
/// partial progress information, but no response data since the upload failed.
489+
/// </para>
490+
/// </summary>
491+
/// <remarks>
492+
/// Subscribe to this event if you want to receive
493+
/// UploadFailedEvent notifications. Here is how:<br />
494+
/// 1. Define a method with a signature similar to this one:
495+
/// <code>
496+
/// private void uploadFailed(object sender, UploadFailedEventArgs args)
497+
/// {
498+
/// Console.WriteLine($"Upload failed: {args.FilePath}");
499+
/// Console.WriteLine($"Partial progress: {args.TransferredBytes} / {args.TotalBytes} bytes");
500+
/// var percent = (double)args.TransferredBytes / args.TotalBytes * 100;
501+
/// Console.WriteLine($"Completion: {percent:F1}%");
502+
/// Console.WriteLine($"Bucket: {args.Request.BucketName}");
503+
/// Console.WriteLine($"Key: {args.Request.Key}");
504+
/// }
505+
/// </code>
506+
/// 2. Add this method to the UploadFailedEvent delegate's invocation list
507+
/// <code>
508+
/// TransferUtilityUploadRequest request = new TransferUtilityUploadRequest();
509+
/// request.UploadFailedEvent += uploadFailed;
510+
/// </code>
511+
/// </remarks>
512+
public event EventHandler<UploadFailedEventArgs> UploadFailedEvent;
513+
514+
/// <summary>
515+
/// Causes the UploadInitiatedEvent event to be fired.
516+
/// </summary>
517+
/// <param name="args">UploadInitiatedEventArgs args</param>
518+
internal void OnRaiseTransferInitiatedEvent(UploadInitiatedEventArgs args)
519+
{
520+
AWSSDKUtils.InvokeInBackground(UploadInitiatedEvent, args, this);
521+
}
522+
523+
/// <summary>
524+
/// Causes the UploadCompletedEvent event to be fired.
525+
/// </summary>
526+
/// <param name="args">UploadCompletedEventArgs args</param>
527+
internal void OnRaiseTransferCompletedEvent(UploadCompletedEventArgs args)
528+
{
529+
AWSSDKUtils.InvokeInBackground(UploadCompletedEvent, args, this);
530+
}
531+
532+
/// <summary>
533+
/// Causes the UploadFailedEvent event to be fired.
534+
/// </summary>
535+
/// <param name="args">UploadFailedEventArgs args</param>
536+
internal void OnRaiseTransferFailedEvent(UploadFailedEventArgs args)
537+
{
538+
AWSSDKUtils.InvokeInBackground(UploadFailedEvent, args, this);
539+
}
540+
414541

415542
/// <summary>
416543
/// Causes the UploadProgressEvent event to be fired.
@@ -835,11 +962,164 @@ internal UploadProgressArgs(long incrementTransferred, long transferred, long to
835962
this.CompensationForRetry = compensationForRetry;
836963
}
837964

965+
/// <summary>
966+
/// Constructor for upload progress with request
967+
/// </summary>
968+
/// <param name="incrementTransferred">The how many bytes were transferred since last event.</param>
969+
/// <param name="transferred">The number of bytes transferred</param>
970+
/// <param name="total">The total number of bytes to be transferred</param>
971+
/// <param name="compensationForRetry">A compensation for any upstream aggregators if this event to correct their totalTransferred count,
972+
/// in case the underlying request is retried.</param>
973+
/// <param name="filePath">The file being uploaded</param>
974+
/// <param name="request">The original TransferUtilityUploadRequest created by the user</param>
975+
internal UploadProgressArgs(long incrementTransferred, long transferred, long total, long compensationForRetry, string filePath, TransferUtilityUploadRequest request)
976+
: base(incrementTransferred, transferred, total)
977+
{
978+
this.FilePath = filePath;
979+
this.CompensationForRetry = compensationForRetry;
980+
this.Request = request;
981+
}
982+
838983
/// <summary>
839984
/// Gets the FilePath.
840985
/// </summary>
841986
public string FilePath { get; private set; }
842987

843988
internal long CompensationForRetry { get; set; }
989+
990+
/// <summary>
991+
/// The original TransferUtilityUploadRequest created by the user.
992+
/// </summary>
993+
public TransferUtilityUploadRequest Request { get; internal set; }
994+
}
995+
996+
/// <summary>
997+
/// Encapsulates the information needed when a transfer operation is initiated.
998+
/// Provides access to the original request and total file size without any progress information.
999+
/// </summary>
1000+
public class UploadInitiatedEventArgs : EventArgs
1001+
{
1002+
/// <summary>
1003+
/// Initializes a new instance of the UploadInitiatedEventArgs class.
1004+
/// </summary>
1005+
/// <param name="request">The original TransferUtilityUploadRequest created by the user</param>
1006+
/// <param name="filePath">The file being uploaded</param>
1007+
/// <param name="totalBytes">The total number of bytes to be transferred</param>
1008+
internal UploadInitiatedEventArgs(TransferUtilityUploadRequest request, string filePath, long totalBytes)
1009+
{
1010+
Request = request;
1011+
FilePath = filePath;
1012+
TotalBytes = totalBytes;
1013+
}
1014+
1015+
/// <summary>
1016+
/// The original TransferUtilityUploadRequest created by the user.
1017+
/// Contains all the upload parameters and configuration.
1018+
/// </summary>
1019+
public TransferUtilityUploadRequest Request { get; private set; }
1020+
1021+
/// <summary>
1022+
/// Gets the file path being uploaded.
1023+
/// </summary>
1024+
public string FilePath { get; private set; }
1025+
1026+
/// <summary>
1027+
/// Gets the total number of bytes to be transferred.
1028+
/// </summary>
1029+
public long TotalBytes { get; private set; }
1030+
}
1031+
1032+
/// <summary>
1033+
/// Encapsulates the information needed when a transfer operation completes successfully.
1034+
/// Provides access to the original request, final response, and completion details.
1035+
/// </summary>
1036+
public class UploadCompletedEventArgs : EventArgs
1037+
{
1038+
/// <summary>
1039+
/// Initializes a new instance of the UploadCompletedEventArgs class.
1040+
/// </summary>
1041+
/// <param name="request">The original TransferUtilityUploadRequest created by the user</param>
1042+
/// <param name="response">The unified response from Transfer Utility</param>
1043+
/// <param name="filePath">The file that was uploaded</param>
1044+
/// <param name="transferredBytes">The total number of bytes transferred</param>
1045+
/// <param name="totalBytes">The total number of bytes that were transferred</param>
1046+
internal UploadCompletedEventArgs(TransferUtilityUploadRequest request, TransferUtilityUploadResponse response, string filePath, long transferredBytes, long totalBytes)
1047+
{
1048+
Request = request;
1049+
Response = response;
1050+
FilePath = filePath;
1051+
TransferredBytes = transferredBytes;
1052+
TotalBytes = totalBytes;
1053+
}
1054+
1055+
/// <summary>
1056+
/// The original TransferUtilityUploadRequest created by the user.
1057+
/// Contains all the upload parameters and configuration.
1058+
/// </summary>
1059+
public TransferUtilityUploadRequest Request { get; private set; }
1060+
1061+
/// <summary>
1062+
/// The unified response from Transfer Utility after successful upload completion.
1063+
/// Contains mapped fields from either PutObjectResponse (simple uploads) or CompleteMultipartUploadResponse (multipart uploads).
1064+
/// </summary>
1065+
public TransferUtilityUploadResponse Response { get; private set; }
1066+
1067+
/// <summary>
1068+
/// Gets the file path that was uploaded.
1069+
/// </summary>
1070+
public string FilePath { get; private set; }
1071+
1072+
/// <summary>
1073+
/// Gets the total number of bytes that were successfully transferred.
1074+
/// </summary>
1075+
public long TransferredBytes { get; private set; }
1076+
1077+
/// <summary>
1078+
/// Gets the total number of bytes that were transferred (should equal TransferredBytes for successful uploads).
1079+
/// </summary>
1080+
public long TotalBytes { get; private set; }
1081+
}
1082+
1083+
/// <summary>
1084+
/// Encapsulates the information needed when a transfer operation fails.
1085+
/// Provides access to the original request and partial progress information.
1086+
/// </summary>
1087+
public class UploadFailedEventArgs : EventArgs
1088+
{
1089+
/// <summary>
1090+
/// Initializes a new instance of the UploadFailedEventArgs class.
1091+
/// </summary>
1092+
/// <param name="request">The original TransferUtilityUploadRequest created by the user</param>
1093+
/// <param name="filePath">The file that was being uploaded</param>
1094+
/// <param name="transferredBytes">The number of bytes transferred before failure</param>
1095+
/// <param name="totalBytes">The total number of bytes that should have been transferred</param>
1096+
internal UploadFailedEventArgs(TransferUtilityUploadRequest request, string filePath, long transferredBytes, long totalBytes)
1097+
{
1098+
Request = request;
1099+
FilePath = filePath;
1100+
TransferredBytes = transferredBytes;
1101+
TotalBytes = totalBytes;
1102+
}
1103+
1104+
/// <summary>
1105+
/// The original TransferUtilityUploadRequest created by the user.
1106+
/// Contains all the upload parameters and configuration.
1107+
/// </summary>
1108+
public TransferUtilityUploadRequest Request { get; private set; }
1109+
1110+
/// <summary>
1111+
/// Gets the file path that was being uploaded.
1112+
/// </summary>
1113+
public string FilePath { get; private set; }
1114+
1115+
/// <summary>
1116+
/// Gets the number of bytes that were transferred before the failure occurred.
1117+
/// </summary>
1118+
public long TransferredBytes { get; private set; }
1119+
1120+
/// <summary>
1121+
/// Gets the total number of bytes that should have been transferred.
1122+
/// </summary>
1123+
public long TotalBytes { get; private set; }
8441124
}
8451125
}

0 commit comments

Comments
 (0)