using FamilyNido.Api.Options;
using Microsoft.Extensions.Options;
namespace FamilyNido.Api.Features.Calendar;
///
/// Hosted service that runs on a
/// fixed cadence (). Sleeps with a
/// so the loop wakes up promptly on shutdown.
///
public sealed class CalendarSyncBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IOptionsMonitor _options;
private readonly ILogger _logger;
/// Primary constructor.
public CalendarSyncBackgroundService(
IServiceScopeFactory scopeFactory,
IOptionsMonitor options,
ILogger logger)
{
_scopeFactory = scopeFactory;
_options = options;
_logger = logger;
}
///
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var initialDelay = TimeSpan.FromSeconds(30);
try
{
// Don't pile work onto a cold-starting app. A short head-start means the API is
// already serving traffic when the first sync runs.
await Task.Delay(initialDelay, stoppingToken);
}
catch (OperationCanceledException)
{
return;
}
while (!stoppingToken.IsCancellationRequested)
{
await SyncOnceAsync(stoppingToken);
var interval = _options.CurrentValue.SyncInterval;
if (interval <= TimeSpan.Zero)
{
interval = TimeSpan.FromMinutes(15);
}
try
{
await Task.Delay(interval, stoppingToken);
}
catch (OperationCanceledException)
{
return;
}
}
}
private async Task SyncOnceAsync(CancellationToken stoppingToken)
{
try
{
await using var scope = _scopeFactory.CreateAsyncScope();
var synchronizer = scope.ServiceProvider.GetRequiredService();
await synchronizer.SyncAllAsync(stoppingToken);
}
catch (OperationCanceledException)
{
// Shutting down; do not log.
}
catch (Exception ex)
{
_logger.LogError(ex, "Calendar sync iteration failed at the top level.");
}
}
}