using Extrudex.Domain.Interfaces; using Extrudex.Infrastructure.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Extrudex.API.Jobs; /// /// Background job that periodically syncs filament usage data from /// Moonraker printers. Runs as a hosted service and polls all active /// Moonraker printers on a configurable interval to persist usage /// data to the Extrudex database. /// /// Configuration is bound from the "FilamentUsageSync" section in /// appsettings.json. Set Enabled=false to disable without removing /// the service registration. /// /// Uses an IServiceScopeFactory to resolve scoped dependencies /// (IFilamentUsageSyncService, IUsageLogService) on each sync cycle, /// avoiding captive-dependency issues from injecting scoped services /// into the singleton BackgroundService lifetime. /// public class FilamentUsageSyncJob : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly FilamentUsageSyncOptions _options; private readonly ILogger _logger; /// /// Creates a new FilamentUsageSyncJob. /// /// Factory for creating DI scopes to resolve scoped services. /// Configuration options for polling interval and timeouts. /// Logger for diagnostic output. public FilamentUsageSyncJob( IServiceScopeFactory scopeFactory, IOptions options, ILogger logger) { _scopeFactory = scopeFactory; _options = options.Value; _logger = logger; } /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (!_options.Enabled) { _logger.LogInformation("Filament usage sync job is disabled via configuration — exiting"); return; } _logger.LogInformation( "Filament usage sync job starting — polling every {Interval}", _options.PollingInterval); // Delay briefly on startup to allow the web host to fully initialize await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); while (!stoppingToken.IsCancellationRequested) { try { using var scope = _scopeFactory.CreateScope(); var syncService = scope.ServiceProvider.GetRequiredService(); var syncedCount = await syncService.SyncAllAsync(stoppingToken); _logger.LogInformation( "Filament usage sync completed — {SyncedCount} printer(s) synced. Next sync in {Interval}", syncedCount, _options.PollingInterval); } catch (Exception ex) when (ex is not OperationCanceledException) { _logger.LogError(ex, "Error during filament usage sync cycle — will retry in {Interval}", _options.PollingInterval); } await Task.Delay(_options.PollingInterval, stoppingToken); } _logger.LogInformation("Filament usage sync job shutting down"); } }