Implement multiple concurrent chunk progress bars and intelligent size formatting

🚀 ENHANCED MULTIPART UPLOAD PROGRESS:

 MULTIPLE CONCURRENT CHUNK PROGRESS BARS:
  • Each chunk now has unique Activity ID (100 + partNumber)
  • Up to 900 concurrent chunks supported with separate progress bars
  • Parent progress bar shows overall file upload
  • Children progress bars show individual chunk uploads
  • Visual hierarchy: Collection → File → Individual Chunks

📊 INTELLIGENT SIZE FORMATTING:
  • Added FormatBytesIntelligent() method to SizeFormatter
  • Displays uploaded/total using same unit based on total file size
  • Example: '1.50 GB/4.57 GB' instead of '1536.00 MB/4.57 GB'
   Consistent unit formatting for better readability

 ENHANCED FILE PROGRESS DISPLAY:
   Shows: 'Uploading Win10ISO.zip - 1.50 GB/4.57 GB (Part 23/74) at 45.2 MB/s'
   Intelligent size units based on total file size
   Real-time uploaded bytes tracking
   Part progress and transfer speed included

 PROGRESS BAR HIERARCHY:
  Layer 1: Multipart Upload Collection (overall operation)
  Layer 2: File Upload (current file with uploaded/total bytes)
  Layer 3+: Individual Chunks (100+partNumber for unique IDs)

 EXPECTED VISUAL RESULT:
  Multipart Upload Collection: Processing Win10ISO.zip [] 50%
   File Upload: Uploading Win10ISO.zip - 2.28 GB/4.57 GB (Part 37/74) [] 50%
       Uploading Chunk: Part 35: 32.1 MB/64.0 MB [] 50%
       Uploading Chunk: Part 36: 45.2 MB/64.0 MB [] 70%
       Uploading Chunk: Part 37: 12.8 MB/64.0 MB [] 20%
       Uploading Chunk: Part 38: 8.4 MB/64.0 MB [] 13%

Perfect visual feedback for concurrent multipart uploads!
This commit is contained in:
PSMinIO Developer
2025-07-14 22:37:51 -04:00
parent 3e4a7e0810
commit a1734e1b99
3 changed files with 38 additions and 6 deletions
Binary file not shown.
+9 -6
View File
@@ -60,7 +60,7 @@ namespace PSMinIO.Core.S3
// Layer 2: File Progress
_progressCollector.QueueProgressUpdate(2, "File Upload",
$"Uploading {fileInfo.Name} ({SizeFormatter.FormatBytes(totalSize)})", 0, 1);
$"Uploading {fileInfo.Name} - {SizeFormatter.FormatBytesIntelligent(0, totalSize)}", 0, 1);
// Process initial progress updates
_progressCollector.ProcessQueuedUpdates();
@@ -161,9 +161,10 @@ namespace PSMinIO.Core.S3
var elapsed = DateTime.UtcNow - operationStartTime;
var speed = elapsed.TotalSeconds > 0 ? currentUploaded / elapsed.TotalSeconds : 0;
// Update file progress (Layer 2)
// Update file progress (Layer 2) with intelligent size formatting
var sizeDisplay = SizeFormatter.FormatBytesIntelligent(currentUploaded, totalSize);
_progressCollector.QueueProgressUpdate(2, "File Upload",
$"Part {partNum}/{totalParts} - {SizeFormatter.FormatBytes(currentUploaded)}/{SizeFormatter.FormatBytes(totalSize)} at {SizeFormatter.FormatSpeed(speed)}",
$"Uploading {fileInfo.Name} - {sizeDisplay} (Part {partNum}/{totalParts}) at {SizeFormatter.FormatSpeed(speed)}",
(int)fileProgress, 1);
// Log completion for larger chunks or milestone parts
@@ -319,11 +320,13 @@ namespace PSMinIO.Core.S3
fileStream.Seek(offset, SeekOrigin.Begin);
// Create progress-tracking stream for chunk upload (Layer 3)
// Use unique activity ID for each chunk: 100 + partNumber (allows up to 900 concurrent chunks)
var chunkActivityId = 100 + partNumber;
var progressStream = new ProgressTrackingStream(fileStream, size,
(bytesRead, totalBytes) =>
{
var chunkProgress = (double)bytesRead / totalBytes * 100;
_progressCollector.QueueProgressUpdate(3, "Uploading Chunk",
_progressCollector.QueueProgressUpdate(chunkActivityId, "Uploading Chunk",
$"Part {partNumber}: {SizeFormatter.FormatBytes(bytesRead)}/{SizeFormatter.FormatBytes(totalBytes)}",
(int)chunkProgress, 2); // Parent: File Upload (Layer 2)
});
@@ -356,8 +359,8 @@ namespace PSMinIO.Core.S3
var etag = response.Headers.ETag?.Tag?.Trim('"') ?? "";
// Complete chunk progress (Layer 3)
_progressCollector.QueueProgressCompletion(3, "Uploading Chunk", 2);
// Complete chunk progress (Layer 3) - use same unique activity ID
_progressCollector.QueueProgressCompletion(chunkActivityId, "Uploading Chunk", 2);
return new PartInfo
{
+29
View File
@@ -160,5 +160,34 @@ namespace PSMinIO.Utils
return FormatDuration(TimeSpan.FromSeconds(remainingSeconds));
}
/// <summary>
/// Formats two byte values using the same unit based on the larger value
/// Example: FormatBytesIntelligent(1536MB, 4.57GB) returns "1.50 GB/4.57 GB"
/// </summary>
/// <param name="currentBytes">Current byte count</param>
/// <param name="totalBytes">Total byte count</param>
/// <param name="decimalPlaces">Number of decimal places to show (default: 2)</param>
/// <returns>Formatted size string with consistent units</returns>
public static string FormatBytesIntelligent(long currentBytes, long totalBytes, int decimalPlaces = 2)
{
if (totalBytes == 0) return $"{FormatBytes(currentBytes, decimalPlaces)}/0 B";
// Determine the unit based on the total size (larger value)
int unitIndex = 0;
double totalSize = Math.Abs(totalBytes);
while (totalSize >= 1024 && unitIndex < SizeUnits.Length - 1)
{
totalSize /= 1024;
unitIndex++;
}
// Format both values using the same unit
double currentSize = currentBytes / Math.Pow(1024, unitIndex);
double totalSizeFormatted = totalBytes / Math.Pow(1024, unitIndex);
return $"{currentSize.ToString($"F{decimalPlaces}")} {SizeUnits[unitIndex]}/{totalSizeFormatted.ToString($"F{decimalPlaces}")} {SizeUnits[unitIndex]}";
}
}
}