using System; using Svrnty.CQRS.Events.Configuration; using Svrnty.CQRS.Events.Abstractions.Storage; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Svrnty.CQRS.Events.Abstractions; namespace Svrnty.CQRS.Events.Services; /// /// Background service that periodically cleans up old read receipts. /// public class ReadReceiptCleanupService : BackgroundService { private readonly IReadReceiptStore _readReceiptStore; private readonly ILogger _logger; private readonly ReadReceiptOptions _options; public ReadReceiptCleanupService( IReadReceiptStore readReceiptStore, ILogger logger, IOptions options) { _readReceiptStore = readReceiptStore ?? throw new ArgumentNullException(nameof(readReceiptStore)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (!_options.EnableAutoCleanup) { _logger.LogInformation("Read receipt auto-cleanup is disabled"); return; } _logger.LogInformation( "Read receipt cleanup service started (Interval: {Interval}, Retention: {Retention})", _options.CleanupInterval, _options.RetentionPeriod); while (!stoppingToken.IsCancellationRequested) { try { await Task.Delay(_options.CleanupInterval, stoppingToken); if (stoppingToken.IsCancellationRequested) break; await PerformCleanupAsync(stoppingToken); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { // Normal shutdown break; } catch (Exception ex) { _logger.LogError(ex, "Error during read receipt cleanup"); // Continue running despite errors } } _logger.LogInformation("Read receipt cleanup service stopped"); } private async Task PerformCleanupAsync(CancellationToken cancellationToken) { var olderThan = DateTimeOffset.UtcNow.Subtract(_options.RetentionPeriod); _logger.LogDebug("Starting read receipt cleanup (deleting receipts older than {OlderThan})", olderThan); var deletedCount = await _readReceiptStore.CleanupAsync(olderThan, cancellationToken); if (deletedCount > 0) { _logger.LogInformation("Cleaned up {DeletedCount} old read receipts", deletedCount); } else { _logger.LogDebug("No old read receipts to clean up"); } } }