using JSMR.Application.Enums; using JSMR.Application.Scanning; using JSMR.Application.Scanning.Ports; using JSMR.Domain.Enums; using JSMR.Infrastructure.Common.Time; using JSMR.Worker.Options; using JSMR.Worker.UI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Spectre.Console; using System.Globalization; namespace JSMR.Worker.Services; public sealed class PagedScanRunner( ILogger log, IServiceProvider serviceProvider, ICheckpointStore checkpoints) { public async Task RunAsync(ScanOptions options, CancellationToken cancellationToken) { if (Enum.TryParse(options.Locale, ignoreCase: true, out Locale locale) == false) throw new ArgumentException($"Unknown locale '{options.Locale}'."); int pageSize = options.PageSize ?? 100; int startPage = options.StartPage ?? (await checkpoints.GetLastPageAsync(options.Locale, cancellationToken)).GetValueOrDefault(0) + 1; ITimeProvider timeProvider = serviceProvider.GetRequiredService(); log.LogInformation("Scanning on {ScanTime}...", timeProvider.Now().DateTime.ToString(CultureInfo.CurrentCulture)); while (!cancellationToken.IsCancellationRequested) { int currentPage = startPage; int? end = options.EndPage; // Iterate until empty page or end reached for (; !cancellationToken.IsCancellationRequested && (!end.HasValue || currentPage <= end.Value); currentPage++) { using var scope = serviceProvider.CreateScope(); ScanVoiceWorksHandler handler = scope.ServiceProvider.GetRequiredService(); //log.LogInformation("Scanning page {Page} (size {Size}, locale {Locale})…", currentPage, pageSize, locale); CliUi.PageHeader(currentPage, end); //AnsiConsole.Status() //.Start($"[grey]Scanning page[/] [bold]{currentPage}[/] [grey]of[/] [bold]{end}[/][grey]...[/]", ctx => //{ // // Simulate grinding // Thread.Sleep(3000); //}); //AnsiConsole.MarkupLine($"[green]✓ Scanning page[/] [bold]{currentPage}[/] [grey]of[/] [bold]{end}[/][grey]... DONE[/]"); ScanVoiceWorksRequest request = new( PageNumber: currentPage, PageSize: 100, Locale: locale ); ScanVoiceWorksResponse response = await handler.HandleAsync(request, cancellationToken); int newUpcoming = response.Results.Count(x => x.UpdateStatus == VoiceWorkStatus.NewAndUpcoming); //if (newUpcoming > 0) // AnsiConsole.MarkupLine($" - {newUpcoming} new upcoming work(s)"); int newRelease = response.Results.Count(x => x.UpdateStatus == VoiceWorkStatus.NewRelease); //if (newRelease > 0) // updatedInfo.Add($"{newRelease} new work(s) on sale"); CliUi.PageHighlights(newUpcoming, newRelease); IEnumerable resultsWithIssues = response.Results.Where(x => x.Issues.Count > 0); // TODO: Later foreach (VoiceWorkUpsertResult resultWithIssues in resultsWithIssues) { //log.LogWarning($"PRoblem with {resultWithIssues.}") //string messageToDisplay = $"{scannedVoiceWork.ProductId} - {scannedVoiceWork.ProductName} -- {message}"; //Console.WriteLine(messageToDisplay); //messages.Add(messageToDisplay); string productId = resultWithIssues.ProductId; string title = resultWithIssues.Title; string[] messages = [.. resultWithIssues.Issues.Select(x => x.Message)]; CliUi.PageErrors($"{productId} - {title}", messages); } // Save checkpoint await checkpoints.SaveLastPageAsync(options.Locale, currentPage, cancellationToken); } if (!options.Watch) break; log.LogInformation("Watch mode: sleeping {Interval}…", options.Interval); await Task.Delay(options.Interval, cancellationToken); // Compute next “start” for next cycle: // - If you want to re-scan the latest N pages every loop to catch late updates, // modify logic here (e.g., start = Math.Max(1, current - 2)) startPage = currentPage; // continue from where we left off } } }