Files
ConnectionsAPI/SyncScheduler.cs

74 lines
3.1 KiB
C#

using ConnectionsAPI.Events;
using Cronos;
using System.Diagnostics.CodeAnalysis;
namespace ConnectionsAPI
{
public class SyncScheduler(ILogger<SyncScheduler> logger, IConfiguration configuration) : BackgroundService
{
private readonly ILogger<SyncScheduler> _logger = logger;
private readonly IConfiguration _configuration = configuration;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// get and parse the cron expression
string configCronStr = _configuration.GetValue<string>("Sync:ScheduleCron") ?? string.Empty;
if (!TryParseCronExpression(configCronStr, out var cron))
{
cron = CronExpression.Hourly;
_logger.LogWarning("Passed CRON expression was invalid ({configCron}); Defaulting to {newCron}", configCronStr, cron.ToString());
}
_logger.LogInformation("Starting Sync Scheduler with CRON expression {cron}", cron.ToString());
// get and parse if immediate execution is wanted
bool runImmediately = _configuration.GetValue<bool>("Sync:RunImmediately", false);
if (runImmediately)
{
_logger.LogInformation("Immediate execution enabled; Sending sync command.");
await SendSyncEvent(stoppingToken, wait: true);
}
// run the loop for executions
while (!stoppingToken.IsCancellationRequested)
{
// wait for next scheduled run
await WaitForNextSchedule(cron, stoppingToken);
// run the sync
_logger.LogInformation("Sending sync command at: {currentTime}", DateTimeOffset.UtcNow);
await SendSyncEvent(stoppingToken, wait: false);
}
}
// this method exists because the Cronos TryParse for some reason throws an exception on nulls and empty strings
// completely defeating the purpose of the TryParse pattern
private static bool TryParseCronExpression(string expression, [NotNullWhen(true)] out CronExpression? cron)
{
try
{
cron = CronExpression.Parse(expression);
return true;
}
catch
{
cron = null;
return false;
}
}
private static Task SendSyncEvent(CancellationToken stoppingToken, bool wait = false) =>
new ConnectionsSyncEvent { }.PublishAsync(wait ? Mode.WaitForAll : Mode.WaitForNone, stoppingToken);
private async Task WaitForNextSchedule(CronExpression cron, CancellationToken ct)
{
var currentUtcTime = DateTimeOffset.UtcNow.UtcDateTime;
var nextOccurrenceTime = cron.GetNextOccurrence(currentUtcTime);
var delay = nextOccurrenceTime.GetValueOrDefault() - currentUtcTime;
_logger.LogInformation("Run delayed for {delay}. Next occurrence: {nextOccurrence}; Current time: {currentTime}", delay, nextOccurrenceTime, currentUtcTime);
await Task.Delay(delay, ct);
}
}
}