using SharepointToolbox.Core.Models;
namespace SharepointToolbox.Services;
public static class BulkOperationRunner
{
///
/// Runs a bulk operation with continue-on-error semantics, per-item result tracking,
/// and cancellation support. OperationCanceledException propagates immediately.
///
/// Progress is reported AFTER each item completes (success or failure), so the bar
/// reflects actual work done rather than work queued. A final "Complete" report
/// guarantees 100% when the total was determinate.
///
/// Set > 1 to run items in parallel. Callers must
/// ensure processItem is safe to invoke concurrently (e.g. each invocation uses its
/// own CSOM ClientContext — a shared CSOM context is NOT thread-safe).
///
public static async Task> RunAsync(
IReadOnlyList items,
Func processItem,
IProgress progress,
CancellationToken ct,
int maxConcurrency = 1)
{
if (items.Count == 0)
{
progress.Report(new OperationProgress(0, 0, "Nothing to do."));
return new BulkOperationSummary(Array.Empty>());
}
progress.Report(new OperationProgress(0, items.Count, $"Processing 1/{items.Count}..."));
var results = new BulkItemResult[items.Count];
int completed = 0;
async Task RunOne(int i, CancellationToken token)
{
try
{
await processItem(items[i], i, token);
results[i] = BulkItemResult.Success(items[i]);
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
results[i] = BulkItemResult.Failed(items[i], ex.Message);
}
finally
{
int done = Interlocked.Increment(ref completed);
progress.Report(new OperationProgress(done, items.Count,
$"Processed {done}/{items.Count}"));
}
}
if (maxConcurrency <= 1)
{
for (int i = 0; i < items.Count; i++)
{
ct.ThrowIfCancellationRequested();
await RunOne(i, ct);
}
}
else
{
var options = new ParallelOptions
{
MaxDegreeOfParallelism = maxConcurrency,
CancellationToken = ct
};
await Parallel.ForEachAsync(Enumerable.Range(0, items.Count), options,
async (i, token) => await RunOne(i, token));
}
progress.Report(new OperationProgress(items.Count, items.Count, "Complete."));
return new BulkOperationSummary(results);
}
}