2026-06-03 08:18:19 +03:30
|
|
|
namespace JobsMedical.Web.Services.Scraping;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-06-04 00:44:11 +03:30
|
|
|
/// Periodically runs the ingestion engine when the admin has turned auto-ingest ON
|
|
|
|
|
/// (AppSetting.AutoIngestEnabled) — read fresh from the DB each cycle, so it can be toggled at
|
|
|
|
|
/// runtime from the admin panel with no redeploy. When off, it idles and re-checks.
|
2026-06-03 08:18:19 +03:30
|
|
|
/// </summary>
|
|
|
|
|
public class IngestionWorker : BackgroundService
|
|
|
|
|
{
|
|
|
|
|
private readonly IServiceScopeFactory _scopes;
|
|
|
|
|
private readonly ILogger<IngestionWorker> _log;
|
|
|
|
|
|
2026-06-04 00:44:11 +03:30
|
|
|
public IngestionWorker(IServiceScopeFactory scopes, ILogger<IngestionWorker> log)
|
2026-06-03 08:18:19 +03:30
|
|
|
{
|
|
|
|
|
_scopes = scopes;
|
|
|
|
|
_log = log;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
{
|
2026-06-04 00:44:11 +03:30
|
|
|
// Small startup delay so the DB/migrations are ready.
|
|
|
|
|
try { await Task.Delay(TimeSpan.FromSeconds(20), stoppingToken); }
|
|
|
|
|
catch (OperationCanceledException) { return; }
|
2026-06-03 08:18:19 +03:30
|
|
|
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
|
|
|
{
|
2026-06-04 00:44:11 +03:30
|
|
|
var idleMinutes = 10;
|
2026-06-03 08:18:19 +03:30
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var scope = _scopes.CreateScope();
|
2026-06-04 09:57:06 +03:30
|
|
|
|
|
|
|
|
// Always archive stale listings (independent of ingestion being on).
|
|
|
|
|
await scope.ServiceProvider.GetRequiredService<ListingArchiver>().ArchiveStaleAsync(stoppingToken);
|
|
|
|
|
|
2026-06-04 00:44:11 +03:30
|
|
|
var settings = await scope.ServiceProvider
|
|
|
|
|
.GetRequiredService<SettingsService>().GetAsync();
|
|
|
|
|
|
|
|
|
|
if (settings.AutoIngestEnabled)
|
|
|
|
|
{
|
|
|
|
|
var svc = scope.ServiceProvider.GetRequiredService<IngestionService>();
|
|
|
|
|
var summary = await svc.RunAsync(stoppingToken);
|
|
|
|
|
_log.LogInformation("Auto-ingest: queued={Q} published={P} flagged={F} spam={S} dupes={D}",
|
|
|
|
|
summary.TotalQueued, summary.TotalPublished, summary.TotalFlagged,
|
|
|
|
|
summary.TotalSpam, summary.TotalDuplicates);
|
|
|
|
|
idleMinutes = Math.Max(1, settings.IngestIntervalMinutes);
|
|
|
|
|
}
|
2026-06-03 08:18:19 +03:30
|
|
|
}
|
|
|
|
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
|
|
|
|
{
|
2026-06-04 00:44:11 +03:30
|
|
|
_log.LogError(ex, "Auto-ingest cycle failed");
|
2026-06-03 08:18:19 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-04 00:44:11 +03:30
|
|
|
try { await Task.Delay(TimeSpan.FromMinutes(idleMinutes), stoppingToken); }
|
2026-06-03 08:18:19 +03:30
|
|
|
catch (OperationCanceledException) { break; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|