using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using StellaOps.Scanner.Core.Contracts; using StellaOps.Scanner.Reachability; namespace StellaOps.Scanner.Worker.Processing; public sealed class ScanJobProcessor { private readonly IReadOnlyDictionary _executors; private readonly ScanProgressReporter _progressReporter; private readonly ILogger _logger; private readonly IReachabilityUnionPublisherService _reachabilityPublisher; private readonly Replay.ReplayBundleFetcher _replayBundleFetcher; public ScanJobProcessor( IEnumerable executors, ScanProgressReporter progressReporter, IReachabilityUnionPublisherService reachabilityPublisher, Replay.ReplayBundleFetcher replayBundleFetcher, ILogger logger) { _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); _reachabilityPublisher = reachabilityPublisher ?? throw new ArgumentNullException(nameof(reachabilityPublisher)); _replayBundleFetcher = replayBundleFetcher ?? throw new ArgumentNullException(nameof(replayBundleFetcher)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); var map = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var executor in executors ?? Array.Empty()) { if (executor is null || string.IsNullOrWhiteSpace(executor.StageName)) { continue; } map[executor.StageName] = executor; } foreach (var stage in ScanStageNames.Ordered) { if (map.ContainsKey(stage)) { continue; } map[stage] = new NoOpStageExecutor(stage); _logger.LogDebug("No executor registered for stage {Stage}; using no-op placeholder.", stage); } _executors = map; } public async ValueTask ExecuteAsync(ScanJobContext context, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); await EnsureReplayBundleFetchedAsync(context, cancellationToken).ConfigureAwait(false); foreach (var stage in ScanStageNames.Ordered) { cancellationToken.ThrowIfCancellationRequested(); if (!_executors.TryGetValue(stage, out var executor)) { continue; } await _progressReporter.ExecuteStageAsync( context, stage, executor.ExecuteAsync, cancellationToken).ConfigureAwait(false); } } private async Task EnsureReplayBundleFetchedAsync(ScanJobContext context, CancellationToken cancellationToken) { if (context.Analysis.TryGet(ScanAnalysisKeys.ReplaySealedBundleMetadata, out var sealedMetadata) && sealedMetadata is not null) { // Already fetched in this context if (!string.IsNullOrWhiteSpace(context.ReplayBundlePath) && File.Exists(context.ReplayBundlePath)) { return; } var path = await _replayBundleFetcher.FetchAsync(sealedMetadata, cancellationToken).ConfigureAwait(false); context.ReplayBundlePath = path; } } }