using System.Collections.Concurrent;
namespace SharepointToolbox.Web.Services.Reports;
///
/// Process-wide coordinator for scheduled-report execution. Covers two operator needs:
/// • Cancel an in-flight run — every active run registers a linked
/// keyed by schedule id, so the UI (or a
/// global stop) can abort a report that is currently executing, whether it was
/// started by the scheduler or by "Run now".
/// • Pause future runs — a global flag the background scheduler honours,
/// letting an admin suspend all cadence-triggered runs at once without toggling
/// each schedule's Enabled flag.
///
/// In-memory and singleton. The pause flag does NOT survive a process restart (a
/// restart resumes the scheduler); per-schedule Enabled flags persist and are
/// the durable way to keep a schedule off.
///
public sealed class ScheduledRunCoordinator
{
private readonly ConcurrentDictionary _active = new();
private volatile bool _paused;
/// True while the scheduler is globally paused (no schedules fire).
public bool IsPaused => _paused;
public void Pause() => _paused = true;
public void Resume() => _paused = false;
/// True while a run is registered for this schedule id.
public bool IsRunning(string scheduleId) => _active.ContainsKey(scheduleId);
/// Snapshot of schedule ids with a run in progress.
public IReadOnlyCollection RunningIds => _active.Keys.ToArray();
///
/// Registers a run for and returns a token that trips
/// when either the caller's token (e.g. app shutdown) or a
/// / call fires. Returns null if a
/// run is already registered for this schedule — callers must skip to avoid overlap.
/// Always pair a non-null return with in a finally.
///
public CancellationToken? TryBegin(string scheduleId, CancellationToken linked)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(linked);
if (!_active.TryAdd(scheduleId, cts)) { cts.Dispose(); return null; }
return cts.Token;
}
/// Deregisters and disposes the run for this schedule id.
public void Complete(string scheduleId)
{
if (_active.TryRemove(scheduleId, out var cts)) cts.Dispose();
}
/// Signals cancellation to the run for this schedule id. Returns false if none.
public bool Cancel(string scheduleId)
{
if (_active.TryGetValue(scheduleId, out var cts))
{
try { cts.Cancel(); return true; }
catch (ObjectDisposedException) { return false; } // completed between lookup and cancel
}
return false;
}
/// Signals cancellation to every run in progress. Returns the count signalled.
public int CancelAll()
{
int n = 0;
foreach (var cts in _active.Values)
{
try { cts.Cancel(); n++; }
catch (ObjectDisposedException) { /* completed concurrently */ }
}
return n;
}
}