using DotBased.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Manager.App.Services; public abstract class ExtendedBackgroundService(string name, string description, ILogger logger, TimeSpan? executeInterval = null) : BackgroundService { private TaskCompletionSource _resumeSignal = new(TaskCreationOptions.RunContinuationsAsynchronously); public ServiceState State { get; private set; } = ServiceState.Stopped; public CircularBuffer ProgressEvents { get; } = new(500); public string Name { get; } = name; public string Description { get; set; } = description; public TimeSpan ExecuteInterval { get; set; } = executeInterval ?? TimeSpan.FromMinutes(1); protected sealed override async Task ExecuteAsync(CancellationToken stoppingToken) { State = ServiceState.Running; logger.LogInformation("Initializing background service: {ServiceName}", Name); await InitializeAsync(stoppingToken); try { logger.LogInformation("Running background service: {ServiceName}", Name); while (!stoppingToken.IsCancellationRequested) { if (State == ServiceState.Paused) { _resumeSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); await _resumeSignal.Task.WaitAsync(stoppingToken); } await ExecuteServiceAsync(stoppingToken); await Task.Delay(ExecuteInterval, stoppingToken); } } catch (Exception e) { if (e is not OperationCanceledException) { State = ServiceState.Faulted; logger.LogError(e,"Background service {ServiceName} faulted!", Name); throw; } logger.LogInformation(e,"Service {ServiceName} received cancellation", Name); } finally { State = ServiceState.Stopped; } } protected void LogEvent(string message, LogSeverity severity = LogSeverity.Info) => ProgressEvents.Add(new ServiceEvent(string.Intern(Name), message, DateTime.UtcNow, severity)); public void Pause() { if (State == ServiceState.Running) { State = ServiceState.Paused; LogEvent("Service paused."); } } public void Resume() { if (State == ServiceState.Paused) { State = ServiceState.Running; _resumeSignal.TrySetResult(); LogEvent("Service resumed."); } } protected abstract Task InitializeAsync(CancellationToken stoppingToken); protected abstract Task ExecuteServiceAsync(CancellationToken stoppingToken); public override bool Equals(object? obj) { return obj is ExtendedBackgroundService bgService && bgService.Name.Equals(Name, StringComparison.OrdinalIgnoreCase); } public override int GetHashCode() { return Name.GetHashCode(); } } public enum ServiceState { Stopped, Faulted, Running, Paused } public record struct ServiceEvent(string Source, string Message, DateTime DateUtc, LogSeverity Severity);