using System;
using System.Collections.Concurrent;
using System.Management.Automation;
using System.Threading;
namespace PSMinIO.Utils
{
///
/// Thread-safe progress data collector that accumulates progress updates from background threads
/// and allows the main thread to safely report them to PowerShell
///
/// CRITICAL THREADING RULE:
/// PowerShell cmdlets can ONLY call Write-Progress, Write-Verbose, Write-Object, Write-Error
/// from the main cmdlet thread - NEVER from background threads!
///
/// USAGE PATTERN:
/// - Background threads: Call QueueProgressUpdate(), QueueVerboseMessage() (thread-safe)
/// - Main thread ONLY: Call ProcessQueuedUpdates() to display queued updates
///
/// EXAMPLE:
/// Task.Run(() => {
/// collector.QueueProgressUpdate(1, "Processing", "Status", 50); // ✅ Safe
/// // WriteProgress(...); // ❌ THREADING ERROR!
/// });
/// collector.ProcessQueuedUpdates(); // ✅ Only from main thread
///
public class ThreadSafeProgressCollector
{
private readonly PSCmdlet _cmdlet;
private readonly ConcurrentQueue _progressQueue = new();
private readonly ConcurrentQueue _verboseQueue = new();
private readonly object _lockObject = new();
private volatile bool _isCompleted = false;
public ThreadSafeProgressCollector(PSCmdlet cmdlet)
{
_cmdlet = cmdlet ?? throw new ArgumentNullException(nameof(cmdlet));
}
///
/// Queues a progress update from a background thread
///
public void QueueProgressUpdate(int activityId, string activity, string statusDescription, int percentComplete, int parentActivityId = -1)
{
if (_isCompleted) return;
_progressQueue.Enqueue(new ProgressUpdate
{
ActivityId = activityId,
Activity = activity,
StatusDescription = statusDescription,
PercentComplete = percentComplete,
ParentActivityId = parentActivityId,
Timestamp = DateTime.UtcNow
});
}
///
/// Queues a progress completion from a background thread
///
public void QueueProgressCompletion(int activityId, string activity, int parentActivityId = -1)
{
if (_isCompleted) return;
_progressQueue.Enqueue(new ProgressUpdate
{
ActivityId = activityId,
Activity = activity,
StatusDescription = "Completed",
PercentComplete = 100,
ParentActivityId = parentActivityId,
IsCompleted = true,
Timestamp = DateTime.UtcNow
});
}
///
/// Queues a verbose message from a background thread
///
public void QueueVerboseMessage(string message, params object[] args)
{
if (_isCompleted) return;
_verboseQueue.Enqueue(new VerboseMessage
{
Message = args.Length > 0 ? string.Format(message, args) : message,
Timestamp = DateTime.UtcNow
});
}
///
/// Processes all queued updates from the main thread (safe to call PowerShell methods)
///
public void ProcessQueuedUpdates()
{
// Process verbose messages first
while (_verboseQueue.TryDequeue(out var verboseMessage))
{
MinIOLogger.WriteVerbose(_cmdlet, verboseMessage.Message);
}
// Process progress updates
while (_progressQueue.TryDequeue(out var progressUpdate))
{
var progressRecord = new ProgressRecord(
progressUpdate.ActivityId,
progressUpdate.Activity,
progressUpdate.StatusDescription)
{
PercentComplete = progressUpdate.PercentComplete
};
if (progressUpdate.ParentActivityId >= 0)
{
progressRecord.ParentActivityId = progressUpdate.ParentActivityId;
}
if (progressUpdate.IsCompleted)
{
progressRecord.RecordType = ProgressRecordType.Completed;
}
_cmdlet.WriteProgress(progressRecord);
}
}
///
/// Marks the collector as completed (no more updates will be accepted)
///
public void Complete()
{
_isCompleted = true;
// Process any remaining updates
ProcessQueuedUpdates();
}
///
/// Gets the number of pending progress updates
///
public int PendingProgressUpdates => _progressQueue.Count;
///
/// Gets the number of pending verbose messages
///
public int PendingVerboseMessages => _verboseQueue.Count;
///
/// Progress update data structure
///
private class ProgressUpdate
{
public int ActivityId { get; set; }
public string Activity { get; set; } = string.Empty;
public string StatusDescription { get; set; } = string.Empty;
public int PercentComplete { get; set; }
public int ParentActivityId { get; set; } = -1;
public bool IsCompleted { get; set; }
public DateTime Timestamp { get; set; }
}
///
/// Verbose message data structure
///
private class VerboseMessage
{
public string Message { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
}
}
}