Files
PSMinIO/src/Utils/MultiLayerProgressReporter.cs
T
PSMinIO Developer 6f693d7236 Implement comprehensive 3-layer progress tracking system
ThreadSafeProgressCollector utility:
   Thread-safe progress data collection from background threads
   Concurrent queues for progress updates and verbose messages
   Safe PowerShell method calls only from main thread
   Automatic completion handling and queue processing

 MultiLayerProgressReporter for 3-layer progress:
   Layer 1: Collection progress (overall files with ETA)
   Layer 2: File progress (current file with size info)
   Layer 3: Transfer progress (bytes with speed calculation)
   Hierarchical progress reporting with parent-child relationships
   Real-time speed and ETA calculations

 Enhanced New-MinIOObject cmdlet integration:
   UploadSingleFileWithProgress method with progress integration
   Multi-file upload with comprehensive progress tracking
   Background thread safety for progress callbacks
   Verbose message queuing and processing
   Graceful error handling with progress completion

 Progress tracking features:
   Real-time transfer speed calculation
   Estimated time remaining (ETA) for collections
   Hierarchical progress bars in PowerShell
   Thread-safe verbose logging
   Automatic progress completion on success/failure

 Architecture improvements:
   Separation of concerns: progress collection vs reporting
   Background thread compatibility
   Memory-efficient concurrent collections
   Comprehensive error handling and cleanup

This provides enterprise-grade progress tracking for large file operations with
detailed visibility into collection, file, and transfer-level progress.
2025-07-11 17:35:17 -04:00

200 lines
7.4 KiB
C#

using System;
using System.Management.Automation;
namespace PSMinIO.Utils
{
/// <summary>
/// Multi-layer progress reporter for 3-level progress tracking:
/// Collection progress (overall files) > File progress (current file) > Transfer progress (bytes)
/// </summary>
public class MultiLayerProgressReporter
{
private readonly PSCmdlet _cmdlet;
private readonly ThreadSafeProgressCollector _progressCollector;
// Activity IDs for the three layers
private const int CollectionActivityId = 1;
private const int FileActivityId = 2;
private const int TransferActivityId = 3;
// Collection-level tracking
private int _totalFiles;
private int _currentFileIndex;
private string _collectionOperation = "Processing Files";
// File-level tracking
private string _currentFileName = string.Empty;
private long _currentFileSize;
private long _currentFileBytesTransferred;
// Transfer-level tracking
private long _totalBytesTransferred;
private DateTime _startTime;
public MultiLayerProgressReporter(PSCmdlet cmdlet, int totalFiles, string operation = "Processing Files")
{
_cmdlet = cmdlet ?? throw new ArgumentNullException(nameof(cmdlet));
_progressCollector = new ThreadSafeProgressCollector(cmdlet);
_totalFiles = totalFiles;
_collectionOperation = operation;
_startTime = DateTime.UtcNow;
// Initialize collection progress
UpdateCollectionProgress();
}
/// <summary>
/// Starts processing a new file
/// </summary>
/// <param name="fileName">Name of the file being processed</param>
/// <param name="fileSize">Size of the file in bytes</param>
public void StartNewFile(string fileName, long fileSize)
{
_currentFileIndex++;
_currentFileName = fileName;
_currentFileSize = fileSize;
_currentFileBytesTransferred = 0;
// Update collection progress
UpdateCollectionProgress();
// Initialize file progress
UpdateFileProgress();
// Initialize transfer progress
UpdateTransferProgress();
// Process any queued updates
_progressCollector.ProcessQueuedUpdates();
}
/// <summary>
/// Updates transfer progress for the current file
/// </summary>
/// <param name="bytesTransferred">Bytes transferred for current file</param>
public void UpdateTransferProgress(long bytesTransferred)
{
_currentFileBytesTransferred = bytesTransferred;
UpdateTransferProgress();
}
/// <summary>
/// Completes the current file and updates overall progress
/// </summary>
public void CompleteCurrentFile()
{
_totalBytesTransferred += _currentFileSize;
// Complete transfer progress
_progressCollector.QueueProgressCompletion(TransferActivityId, "Transfer Progress", FileActivityId);
// Complete file progress
_progressCollector.QueueProgressCompletion(FileActivityId, "File Progress", CollectionActivityId);
// Update collection progress
UpdateCollectionProgress();
// Process updates
_progressCollector.ProcessQueuedUpdates();
}
/// <summary>
/// Completes all progress reporting
/// </summary>
public void Complete()
{
// Complete collection progress
_progressCollector.QueueProgressCompletion(CollectionActivityId, _collectionOperation);
// Process final updates and complete
_progressCollector.Complete();
}
/// <summary>
/// Processes any queued progress updates
/// </summary>
public void ProcessQueuedUpdates()
{
_progressCollector.ProcessQueuedUpdates();
}
/// <summary>
/// Updates collection-level progress
/// </summary>
private void UpdateCollectionProgress()
{
var percentage = _totalFiles > 0 ? (int)((double)_currentFileIndex / _totalFiles * 100) : 0;
var status = $"Processing file {_currentFileIndex} of {_totalFiles}";
if (_currentFileIndex > 0)
{
var elapsed = DateTime.UtcNow - _startTime;
var avgTimePerFile = elapsed.TotalSeconds / _currentFileIndex;
var remainingFiles = _totalFiles - _currentFileIndex;
var estimatedTimeRemaining = TimeSpan.FromSeconds(avgTimePerFile * remainingFiles);
status += $" (ETA: {SizeFormatter.FormatDuration(estimatedTimeRemaining)})";
}
_progressCollector.QueueProgressUpdate(CollectionActivityId, _collectionOperation, status, percentage);
}
/// <summary>
/// Updates file-level progress
/// </summary>
private void UpdateFileProgress()
{
var percentage = _currentFileSize > 0 ? (int)((double)_currentFileBytesTransferred / _currentFileSize * 100) : 0;
var status = $"Processing: {_currentFileName} ({SizeFormatter.FormatBytes(_currentFileBytesTransferred)}/{SizeFormatter.FormatBytes(_currentFileSize)})";
_progressCollector.QueueProgressUpdate(FileActivityId, "File Progress", status, percentage, CollectionActivityId);
}
/// <summary>
/// Updates transfer-level progress
/// </summary>
private void UpdateTransferProgress()
{
var percentage = _currentFileSize > 0 ? (int)((double)_currentFileBytesTransferred / _currentFileSize * 100) : 0;
var status = $"Transferring: {SizeFormatter.FormatBytes(_currentFileBytesTransferred)}/{SizeFormatter.FormatBytes(_currentFileSize)}";
if (_currentFileBytesTransferred > 0)
{
var elapsed = DateTime.UtcNow - _startTime;
if (elapsed.TotalSeconds > 0)
{
var speed = _currentFileBytesTransferred / elapsed.TotalSeconds;
status += $" at {SizeFormatter.FormatSpeed(speed)}";
}
}
_progressCollector.QueueProgressUpdate(TransferActivityId, "Transfer Progress", status, percentage, FileActivityId);
}
/// <summary>
/// Queues a verbose message
/// </summary>
/// <param name="message">Message to log</param>
/// <param name="args">Message arguments</param>
public void QueueVerboseMessage(string message, params object[] args)
{
_progressCollector.QueueVerboseMessage(message, args);
}
/// <summary>
/// Gets the current collection progress percentage
/// </summary>
public int CollectionProgressPercentage => _totalFiles > 0 ? (int)((double)_currentFileIndex / _totalFiles * 100) : 0;
/// <summary>
/// Gets the current file progress percentage
/// </summary>
public int FileProgressPercentage => _currentFileSize > 0 ? (int)((double)_currentFileBytesTransferred / _currentFileSize * 100) : 0;
/// <summary>
/// Gets the total bytes transferred across all files
/// </summary>
public long TotalBytesTransferred => _totalBytesTransferred + _currentFileBytesTransferred;
}
}