Files
Extrudex/backend/API/Jobs/FilamentUsageSyncJob.cs
Dex 6e0ca7f425
Some checks failed
Dev Build / build-test (pull_request) Failing after 2m22s
CUB-33: Integrate Moonraker filament usage polling with UsageLog persistence
2026-04-29 13:13:12 -04:00

87 lines
3.4 KiB
C#

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;
/// <summary>
/// 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.
/// </summary>
public class FilamentUsageSyncJob : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly FilamentUsageSyncOptions _options;
private readonly ILogger<FilamentUsageSyncJob> _logger;
/// <summary>
/// Creates a new FilamentUsageSyncJob.
/// </summary>
/// <param name="scopeFactory">Factory for creating DI scopes to resolve scoped services.</param>
/// <param name="options">Configuration options for polling interval and timeouts.</param>
/// <param name="logger">Logger for diagnostic output.</param>
public FilamentUsageSyncJob(
IServiceScopeFactory scopeFactory,
IOptions<FilamentUsageSyncOptions> options,
ILogger<FilamentUsageSyncJob> logger)
{
_scopeFactory = scopeFactory;
_options = options.Value;
_logger = logger;
}
/// <inheritdoc />
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<IFilamentUsageSyncService>();
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");
}
}