using Bit.Seeder.Options; using Bit.Seeder.Pipeline; using Spectre.Console; namespace Bit.SeederUtility.Helpers; /// /// Renders into a Spectre.Console . /// One per phase name. /// /// /// Thread-safe: the task dictionary is guarded by a lock because events /// can arrive from worker threads. /// itself is safe to call concurrently per Spectre.Console's documentation. /// internal sealed class ConsoleProgressReporter(ProgressContext ctx) : IProgress { private readonly Dictionary _tasks = new(); private readonly object _lock = new(); public void Report(SeederProgressEvent value) { switch (value) { case PhaseStarted started: lock (_lock) { if (_tasks.ContainsKey(started.Phase)) { return; } var task = ctx.AddTask(started.Phase, maxValue: started.Total ?? 1d); if (started.Total is null) { task.IsIndeterminate = true; } _tasks[started.Phase] = task; } break; case PhaseAdvanced advanced: ProgressTask? advanceTask; lock (_lock) { _tasks.TryGetValue(advanced.Phase, out advanceTask); } advanceTask?.Increment(advanced.Delta); break; case PhaseCompleted completed: ProgressTask? completeTask; lock (_lock) { _tasks.TryGetValue(completed.Phase, out completeTask); } if (completeTask is not null) { completeTask.IsIndeterminate = false; completeTask.Value = completeTask.MaxValue; completeTask.StopTask(); } break; } } /// /// Runs inside a Spectre progress context, wiring a reporter /// into . The seeder's emitted events drive the live bars. /// /// /// Progress output is written to stderr so stdout remains clean for downstream consumers /// that pipe the final summary rows (org ID, counts, etc.) into other tools. /// internal static TResult RunWithProgress( SeederDependencies deps, Func seed) { var console = AnsiConsole.Create(new AnsiConsoleSettings { Out = new AnsiConsoleOutput(Console.Error), }); TResult result = default!; console.Progress() .Columns( new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new RemainingTimeColumn(), new SpinnerColumn()) .Start(ctx => { var reporter = new ConsoleProgressReporter(ctx); result = seed(deps with { Progress = reporter }); }); return result; } }