using Extrudex.Domain.Enums; using Extrudex.Domain.Interfaces; using Extrudex.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Extrudex.Infrastructure.Configuration; /// /// Service that syncs filament usage data from Moonraker printers into the /// Extrudex database. Queries all active Moonraker printers, fetches their /// current filament usage metrics, and updates spool remaining weights and /// print job records. /// public class FilamentUsageSyncService : IFilamentUsageSyncService { private readonly ExtrudexDbContext _dbContext; private readonly IMoonrakerClient _moonrakerClient; private readonly ILogger _logger; /// /// Creates a new FilamentUsageSyncService. /// /// The EF Core database context for persisting updates. /// The Moonraker HTTP client for fetching printer data. /// Logger for diagnostic output. public FilamentUsageSyncService( ExtrudexDbContext dbContext, IMoonrakerClient moonrakerClient, ILogger logger) { _dbContext = dbContext; _moonrakerClient = moonrakerClient; _logger = logger; } /// public async Task SyncAllAsync(CancellationToken cancellationToken = default) { _logger.LogInformation("Starting filament usage sync cycle"); var printers = await _dbContext.Printers .Where(p => p.IsActive && p.ConnectionType == ConnectionType.Moonraker) .Include(p => p.AmsUnits) .ThenInclude(u => u.Slots) .ThenInclude(s => s.Spool) .ToListAsync(cancellationToken); if (printers.Count == 0) { _logger.LogInformation("No active Moonraker printers found — skipping sync"); return 0; } _logger.LogInformation("Found {PrinterCount} active Moonraker printer(s) to sync", printers.Count); var syncedCount = 0; foreach (var printer in printers) { try { var usageData = await _moonrakerClient.GetFilamentUsageAsync( printer.HostnameOrIp, printer.Port, printer.ApiKey, cancellationToken); if (usageData.Count == 0) { _logger.LogWarning( "No usage data returned from printer {PrinterName} ({Host}:{Port})", printer.Name, printer.HostnameOrIp, printer.Port); continue; } // Update spool remaining weights from AMS data UpdateSpoolWeights(printer, usageData); // Mark printer as seen and idle (reachable = idle, not printing) printer.LastSeenAt = DateTime.UtcNow; printer.Status = PrinterStatus.Idle; syncedCount++; _logger.LogInformation( "Successfully synced filament usage from printer {PrinterName}", printer.Name); } catch (Exception ex) { _logger.LogError(ex, "Error syncing filament usage from printer {PrinterName} ({Host}:{Port})", printer.Name, printer.HostnameOrIp, printer.Port); } } await _dbContext.SaveChangesAsync(cancellationToken); _logger.LogInformation( "Filament usage sync cycle complete — {SyncedCount}/{TotalCount} printers synced", syncedCount, printers.Count); return syncedCount; } /// /// Updates spool remaining weights based on usage data received from Moonraker. /// For printers with AMS units, updates the remaining weight on each slot's spool. /// private void UpdateSpoolWeights( Domain.Entities.Printer printer, Dictionary usageData) { // Update AMS slot remaining weights if available foreach (var amsUnit in printer.AmsUnits) { foreach (var slot in amsUnit.Slots) { if (slot.Spool != null && slot.RemainingWeightG.HasValue) { // Sync the AMS-reported remaining weight to the spool slot.Spool.WeightRemainingGrams = slot.RemainingWeightG.Value; _logger.LogDebug( "Updated spool {SpoolSerial} remaining weight to {Weight}g", slot.Spool.SpoolSerial, slot.RemainingWeightG.Value); } } } // If usage data contains extruded mm, log it for observability if (usageData.TryGetValue("mm_extruded", out var mmExtruded) && mmExtruded > 0) { _logger.LogInformation( "Printer {PrinterName} reports {MmExtruded}mm filament extruded in latest job", printer.Name, mmExtruded); } } }