using System.Collections.Concurrent; using System.Collections.Generic; using StellaOps.Scanner.WebService.Domain; using StellaOps.Scanner.WebService.Utilities; namespace StellaOps.Scanner.WebService.Services; public sealed class InMemoryScanCoordinator : IScanCoordinator { private sealed record ScanEntry(ScanSnapshot Snapshot); private readonly ConcurrentDictionary scans = new(StringComparer.OrdinalIgnoreCase); private readonly TimeProvider timeProvider; private readonly IScanProgressPublisher progressPublisher; public InMemoryScanCoordinator(TimeProvider timeProvider, IScanProgressPublisher progressPublisher) { this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); this.progressPublisher = progressPublisher ?? throw new ArgumentNullException(nameof(progressPublisher)); } public ValueTask SubmitAsync(ScanSubmission submission, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(submission); var normalizedTarget = submission.Target.Normalize(); var metadata = submission.Metadata ?? new Dictionary(StringComparer.OrdinalIgnoreCase); var scanId = ScanIdGenerator.Create(normalizedTarget, submission.Force, submission.ClientRequestId, metadata); var now = timeProvider.GetUtcNow(); var eventData = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["force"] = submission.Force, }; foreach (var pair in metadata) { eventData[$"meta.{pair.Key}"] = pair.Value; } ScanEntry entry = scans.AddOrUpdate( scanId.Value, _ => new ScanEntry(new ScanSnapshot( scanId, normalizedTarget, ScanStatus.Pending, now, now, null)), (_, existing) => { if (submission.Force) { var snapshot = existing.Snapshot with { Status = ScanStatus.Pending, UpdatedAt = now, FailureReason = null }; return new ScanEntry(snapshot); } return existing; }); var created = entry.Snapshot.CreatedAt == now; var state = entry.Snapshot.Status.ToString(); progressPublisher.Publish(scanId, state, created ? "queued" : "requeued", eventData); return ValueTask.FromResult(new ScanSubmissionResult(entry.Snapshot, created)); } public ValueTask GetAsync(ScanId scanId, CancellationToken cancellationToken) { if (scans.TryGetValue(scanId.Value, out var entry)) { return ValueTask.FromResult(entry.Snapshot); } return ValueTask.FromResult(null); } }