using Inspectron.Epson; using Inspectron.Epson.PrintServer.ConfigurationSources; using Inspectron.Epson.Queue; using Microsoft.Extensions.Logging; namespace EpsonPrintService; /// /// Background task that periodically discovers printers and notifies receivers when changes occur /// public class PrinterDiscoveryBackgroundTask { private readonly IDiscoveryService _discoveryService; private readonly IDiscoveredPrintersReceiver _receiver; private readonly ILogger _logger; private readonly PrintServer _printServer; private readonly TimeSpan _interval; private readonly TimeSpan _discoveryTimeout; private readonly TimeSpan _retentionPeriod; private readonly Dictionary _lastSeenTimes = new(); private readonly Dictionary _knownPrinters = new(); private HashSet _lastEffectiveIps = new(); public PrinterDiscoveryBackgroundTask( IDiscoveryService discoveryService, IDiscoveredPrintersReceiver receiver, ILogger logger, PrintServer printServer) { _discoveryService = discoveryService; _receiver = receiver; _logger = logger; _printServer = printServer; _interval = TimeSpan.FromSeconds(30); _discoveryTimeout = TimeSpan.FromSeconds(3); _retentionPeriod = TimeSpan.FromMinutes(2); } /// /// Starts the background discovery task /// /// Token to cancel the background task public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting printer discovery background task (interval: {Interval}s)", _interval.TotalSeconds); // Run immediately on start await DiscoverAndNotifyAsync(); // Then run periodically while (!cancellationToken.IsCancellationRequested) { try { await Task.Delay(_interval, cancellationToken); await DiscoverAndNotifyAsync(); } catch (TaskCanceledException) { break; } catch (Exception ex) { _logger.LogError(ex, "Error during printer discovery"); } } _logger.LogInformation("Printer discovery background task stopped"); } private async Task DiscoverAndNotifyAsync() { List printers; await _printServer.EnterDiscoveryLockAsync(); try { printers = await _discoveryService.DiscoverPrintersAsync(_discoveryTimeout); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to discover printers"); return; // Exit early on error } finally { _printServer.ExitDiscoveryLock(); } // Update last seen times for discovered printers DateTime now = DateTime.UtcNow; foreach (DiscoveredPrinter printer in printers) { _lastSeenTimes[printer.IPAddress] = now; _knownPrinters[printer.IPAddress] = printer; } // Remove printers not seen within the retention period List expiredIps = _lastSeenTimes .Where(kvp => now - kvp.Value > _retentionPeriod) .Select(kvp => kvp.Key) .ToList(); foreach (string expiredIp in expiredIps) { _logger.LogInformation("Printer {IP} not seen for over {Retention}s, removing", expiredIp, _retentionPeriod.TotalSeconds); _lastSeenTimes.Remove(expiredIp); _knownPrinters.Remove(expiredIp); } // Compute effective set and detect changes HashSet effectiveIps = _lastSeenTimes.Keys.ToHashSet(); bool configChanged = HasConfigurationChanged(effectiveIps); // Register newly appeared printers foreach (string ip in effectiveIps.Except(_lastEffectiveIps)) { _logger.LogDebug("Registering discovered printer with IP: {IP}", ip); _printServer.RegisterPrinter(ip); } // Unregister expired printers foreach (string ip in _lastEffectiveIps.Except(effectiveIps)) { _logger.LogDebug("Unregistering expired printer with IP: {IP}", ip); _printServer.UnregisterPrinter(ip); } if (configChanged) { _lastEffectiveIps = effectiveIps; List effectivePrinters = _knownPrinters.Values.ToList(); _logger.LogInformation("Discovered printer configuration changed: {Count} printer(s)", effectivePrinters.Count); await _receiver.OnPrintersDiscoveredAsync(effectivePrinters.AsReadOnly()); } } private bool HasConfigurationChanged(HashSet effectiveIps) { if (effectiveIps.Count != _lastEffectiveIps.Count) return true; return !effectiveIps.SetEquals(_lastEffectiveIps); } }