// backend/src/Titulares.Api/Workers/ProcesoScrapingWorker.cs using Microsoft.AspNetCore.SignalR; using Titulares.Api.Data; using Titulares.Api.Hubs; using Titulares.Api.Services; namespace Titulares.Api.Workers; public class ProcesoScrapingWorker : BackgroundService, IDisposable { private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly EstadoProcesoService _estadoService; private CancellationTokenSource? _delayCts; public ProcesoScrapingWorker(ILogger logger, IServiceProvider serviceProvider, EstadoProcesoService estadoService) { _logger = logger; _serviceProvider = serviceProvider; _estadoService = estadoService; // Nos suscribimos al evento del servicio de estado _estadoService.OnStateChanged += OnEstadoProcesoChanged; } // Este método se ejecutará cuando el evento OnStateChanged se dispare private void OnEstadoProcesoChanged() { _logger.LogInformation("El estado del proceso ha cambiado. Interrumpiendo la espera actual."); _delayCts?.Cancel(); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using (var scope = _serviceProvider.CreateScope()) { var configRepositorio = scope.ServiceProvider.GetRequiredService(); var config = await configRepositorio.ObtenerAsync(); if (!_estadoService.EstaActivo()) { _logger.LogInformation("El scraping está desactivado. Entrando en modo de espera."); await EsperarIntervaloAsync(config.IntervaloMinutos, stoppingToken); continue; } _logger.LogInformation("Iniciando ciclo de scraping con cantidad: {cantidad}", config.CantidadTitularesAScrapear); try { var repositorio = scope.ServiceProvider.GetRequiredService(); var scrapingService = scope.ServiceProvider.GetRequiredService(); var hubContext = scope.ServiceProvider.GetRequiredService>(); var csvService = scope.ServiceProvider.GetRequiredService(); var articulosScrapeados = await scrapingService.ObtenerUltimosTitulares(config.CantidadTitularesAScrapear); await repositorio.SincronizarDesdeScraping(articulosScrapeados, config.CantidadTitularesAScrapear); var titularesActualizados = await repositorio.ObtenerTodosAsync(); await hubContext.Clients.All.SendAsync("TitularesActualizados", titularesActualizados, stoppingToken); await csvService.GenerarCsvAsync(titularesActualizados, config); } catch (Exception ex) { _logger.LogError(ex, "Ocurrió un error durante el proceso de scraping."); } await EsperarIntervaloAsync(config.IntervaloMinutos, stoppingToken); } } } private async Task EsperarIntervaloAsync(int minutos, CancellationToken stoppingToken) { _logger.LogInformation("Proceso en espera por {minutos} minutos.", minutos); try { _delayCts = new CancellationTokenSource(); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, _delayCts.Token); await Task.Delay(TimeSpan.FromMinutes(minutos), linkedCts.Token); } catch (TaskCanceledException) { _logger.LogInformation("La espera fue interrumpida. Reiniciando el ciclo."); } finally { _delayCts?.Dispose(); _delayCts = null; } } // Es crucial desuscribirse del evento para evitar fugas de memoria public override void Dispose() { _estadoService.OnStateChanged -= OnEstadoProcesoChanged; _delayCts?.Dispose(); base.Dispose(); } }