2025-10-28 12:56:42 -03:00
|
|
|
// 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;
|
|
|
|
|
|
2025-10-28 14:12:05 -03:00
|
|
|
public class ProcesoScrapingWorker : BackgroundService, IDisposable
|
2025-10-28 12:56:42 -03:00
|
|
|
{
|
|
|
|
|
private readonly ILogger<ProcesoScrapingWorker> _logger;
|
|
|
|
|
private readonly IServiceProvider _serviceProvider;
|
2025-10-29 11:36:20 -03:00
|
|
|
private readonly EstadoProcesoService _estadoService;
|
2025-10-28 14:12:05 -03:00
|
|
|
private CancellationTokenSource? _delayCts;
|
2025-10-28 12:56:42 -03:00
|
|
|
|
2025-10-29 11:36:20 -03:00
|
|
|
public ProcesoScrapingWorker(ILogger<ProcesoScrapingWorker> logger, IServiceProvider serviceProvider, EstadoProcesoService estadoService)
|
2025-10-28 12:56:42 -03:00
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_serviceProvider = serviceProvider;
|
2025-10-29 11:36:20 -03:00
|
|
|
_estadoService = estadoService;
|
|
|
|
|
|
|
|
|
|
// Nos suscribimos al evento del servicio de estado
|
|
|
|
|
_estadoService.OnStateChanged += OnEstadoProcesoChanged;
|
2025-10-28 14:12:05 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-29 11:36:20 -03:00
|
|
|
// Este método se ejecutará cuando el evento OnStateChanged se dispare
|
|
|
|
|
private void OnEstadoProcesoChanged()
|
2025-10-28 14:12:05 -03:00
|
|
|
{
|
2025-10-29 11:36:20 -03:00
|
|
|
_logger.LogInformation("El estado del proceso ha cambiado. Interrumpiendo la espera actual.");
|
2025-10-28 14:12:05 -03:00
|
|
|
_delayCts?.Cancel();
|
2025-10-28 12:56:42 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
{
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
|
|
|
{
|
2025-10-29 11:36:20 -03:00
|
|
|
using (var scope = _serviceProvider.CreateScope())
|
2025-10-28 12:56:42 -03:00
|
|
|
{
|
2025-10-29 11:36:20 -03:00
|
|
|
var configRepositorio = scope.ServiceProvider.GetRequiredService<ConfiguracionRepositorio>();
|
|
|
|
|
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
|
2025-10-28 12:56:42 -03:00
|
|
|
{
|
|
|
|
|
var repositorio = scope.ServiceProvider.GetRequiredService<TitularRepositorio>();
|
|
|
|
|
var scrapingService = scope.ServiceProvider.GetRequiredService<ScrapingService>();
|
|
|
|
|
var hubContext = scope.ServiceProvider.GetRequiredService<IHubContext<TitularesHub>>();
|
2025-10-28 13:19:24 -03:00
|
|
|
var csvService = scope.ServiceProvider.GetRequiredService<CsvService>();
|
2025-10-28 12:56:42 -03:00
|
|
|
|
2025-10-29 11:36:20 -03:00
|
|
|
var articulosScrapeados = await scrapingService.ObtenerUltimosTitulares(config.CantidadTitularesAScrapear);
|
|
|
|
|
await repositorio.SincronizarDesdeScraping(articulosScrapeados, config.CantidadTitularesAScrapear);
|
2025-10-28 12:56:42 -03:00
|
|
|
|
2025-10-28 14:12:05 -03:00
|
|
|
var titularesActualizados = await repositorio.ObtenerTodosAsync();
|
|
|
|
|
await hubContext.Clients.All.SendAsync("TitularesActualizados", titularesActualizados, stoppingToken);
|
2025-10-28 12:56:42 -03:00
|
|
|
|
2025-10-29 11:36:20 -03:00
|
|
|
await csvService.GenerarCsvAsync(titularesActualizados, config);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Ocurrió un error durante el proceso de scraping.");
|
|
|
|
|
}
|
2025-10-28 14:12:05 -03:00
|
|
|
|
2025-10-29 11:36:20 -03:00
|
|
|
await EsperarIntervaloAsync(config.IntervaloMinutos, stoppingToken);
|
2025-10-28 14:12:05 -03:00
|
|
|
}
|
2025-10-28 12:56:42 -03:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-28 14:12:05 -03:00
|
|
|
|
2025-10-29 11:36:20 -03:00
|
|
|
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
|
2025-10-28 14:12:05 -03:00
|
|
|
public override void Dispose()
|
|
|
|
|
{
|
2025-10-29 11:36:20 -03:00
|
|
|
_estadoService.OnStateChanged -= OnEstadoProcesoChanged;
|
2025-10-28 14:12:05 -03:00
|
|
|
_delayCts?.Dispose();
|
|
|
|
|
base.Dispose();
|
|
|
|
|
}
|
2025-10-28 12:56:42 -03:00
|
|
|
}
|