Implement 3-layer progress tracking for multipart uploads

� 3-LAYER PROGRESS TRACKING:

 ENHANCED PROGRESS VISIBILITY:
  • Layer 1: Collection Progress - Overall multipart upload operation
  • Layer 2: File Progress - Current file being uploaded with parts
  • Layer 3: Chunk Progress - Individual chunk upload with streaming

� STREAMING IMPLEMENTATION:
  • Created ProgressTrackingStream for real-time chunk upload progress
  • Replaced ByteArrayContent with StreamContent for better memory usage
  • Maintains MD5 calculation for data integrity
  • Progress updates during actual data transfer

� PROGRESS HIERARCHY:
  • Collection Progress (ID: 1) - 'Multipart Upload Collection'
  • File Progress (ID: 2, Parent: 1) - 'File Upload' with speed/ETA
  • Chunk Progress (ID: 3, Parent: 2) - 'Uploading Chunk' with bytes transferred

 TECHNICAL IMPROVEMENTS:
  • Real-time progress during HTTP upload (not just after completion)
  • Proper parent-child progress relationship
  • Memory-efficient streaming approach
  • Maintains existing MD5 integrity checking
  • Compatible with parallel chunk uploads

� USER EXPERIENCE:
  • See overall multipart upload progress
  • Track current file upload with speed metrics
  • Watch individual chunks upload in real-time
  • Better visibility into long-running operations

Now multipart uploads show comprehensive 3-layer progress tracking!
This commit is contained in:
PSMinIO Developer
2025-07-14 21:29:10 -04:00
parent 9e55bab583
commit 27c2aca2a9
3 changed files with 126 additions and 14 deletions
+82
View File
@@ -0,0 +1,82 @@
using System;
using System.IO;
namespace PSMinIO.Utils
{
/// <summary>
/// Stream wrapper that tracks read progress and reports it via callback
/// </summary>
public class ProgressTrackingStream : Stream
{
private readonly Stream _baseStream;
private readonly long _totalBytes;
private readonly Action<long, long> _progressCallback;
private long _bytesRead = 0;
private readonly long _startPosition;
public ProgressTrackingStream(Stream baseStream, long totalBytes, Action<long, long> progressCallback)
{
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
_totalBytes = totalBytes;
_progressCallback = progressCallback ?? throw new ArgumentNullException(nameof(progressCallback));
_startPosition = baseStream.Position;
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _totalBytes;
public override long Position
{
get => _bytesRead;
set => throw new NotSupportedException("Setting position is not supported");
}
public override int Read(byte[] buffer, int offset, int count)
{
// Limit read to remaining bytes
var remainingBytes = _totalBytes - _bytesRead;
var bytesToRead = (int)Math.Min(count, remainingBytes);
if (bytesToRead <= 0)
return 0;
var bytesRead = _baseStream.Read(buffer, offset, bytesToRead);
_bytesRead += bytesRead;
// Report progress
_progressCallback(_bytesRead, _totalBytes);
return bytesRead;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("Writing is not supported");
}
public override void Flush()
{
_baseStream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException("Seeking is not supported");
}
public override void SetLength(long value)
{
throw new NotSupportedException("Setting length is not supported");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Don't dispose the base stream - let the caller handle it
}
base.Dispose(disposing);
}
}
}