2024-12-03 22:52:14 +00:00
|
|
|
using BotSharp.Abstraction.Infrastructures;
|
2025-01-24 21:50:28 +00:00
|
|
|
using BotSharp.Abstraction.Infrastructures.Events;
|
2024-12-03 22:52:14 +00:00
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using NCrontab;
|
|
|
|
|
|
|
|
|
|
namespace BotSharp.Core.Crontab.Services;
|
|
|
|
|
|
|
|
|
|
public class CrontabWatcher : BackgroundService
|
|
|
|
|
{
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
private readonly IServiceProvider _services;
|
|
|
|
|
|
|
|
|
|
public CrontabWatcher(IServiceProvider services, ILogger<CrontabWatcher> logger)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_services = services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Crontab Watcher background service is running.");
|
|
|
|
|
|
|
|
|
|
using (var scope = _services.CreateScope())
|
|
|
|
|
{
|
|
|
|
|
var locker = scope.ServiceProvider.GetRequiredService<IDistributedLocker>();
|
|
|
|
|
|
2025-01-16 21:57:50 +00:00
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
2024-12-03 22:52:14 +00:00
|
|
|
{
|
2025-01-24 21:50:28 +00:00
|
|
|
var delay = Task.Delay(1000, stoppingToken);
|
2024-12-03 22:52:14 +00:00
|
|
|
|
2025-01-24 21:50:28 +00:00
|
|
|
await locker.LockAsync("CrontabWatcher:locker", async () =>
|
2024-12-03 22:52:14 +00:00
|
|
|
{
|
|
|
|
|
await RunCronChecker(scope.ServiceProvider);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await delay;
|
2025-01-16 21:57:50 +00:00
|
|
|
}
|
2024-12-03 22:52:14 +00:00
|
|
|
|
|
|
|
|
_logger.LogWarning("Crontab Watcher background service is stopped.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RunCronChecker(IServiceProvider services)
|
|
|
|
|
{
|
|
|
|
|
var cron = services.GetRequiredService<ICrontabService>();
|
|
|
|
|
var crons = await cron.GetCrontable();
|
2025-01-29 12:16:40 +00:00
|
|
|
var publisher = services.GetService<IEventPublisher>();
|
2025-01-24 21:50:28 +00:00
|
|
|
|
2024-12-03 22:52:14 +00:00
|
|
|
foreach (var item in crons)
|
|
|
|
|
{
|
2025-01-24 21:50:28 +00:00
|
|
|
_logger.LogDebug($"[{DateTime.UtcNow}] Cron task ({item.Title}, {item.Cron}), Last Execution Time: {item.LastExecutionTime}");
|
|
|
|
|
|
2024-12-10 21:16:03 +00:00
|
|
|
try
|
2024-12-03 22:52:14 +00:00
|
|
|
{
|
2024-12-11 22:02:22 +00:00
|
|
|
// strip seconds from cron expression
|
|
|
|
|
item.Cron = string.Join(" ", item.Cron.Split(' ').TakeLast(5));
|
2024-12-10 21:16:03 +00:00
|
|
|
var schedule = CrontabSchedule.Parse(item.Cron, new CrontabSchedule.ParseOptions
|
|
|
|
|
{
|
2024-12-11 22:02:22 +00:00
|
|
|
IncludingSeconds = false // Ensure you account for seconds
|
2024-12-10 21:16:03 +00:00
|
|
|
});
|
2024-12-03 22:52:14 +00:00
|
|
|
|
2024-12-10 21:16:03 +00:00
|
|
|
// Get the current time
|
|
|
|
|
var currentTime = DateTime.UtcNow;
|
2024-12-03 22:52:14 +00:00
|
|
|
|
2025-01-16 21:57:50 +00:00
|
|
|
// Get the last occurrence from the schedule
|
|
|
|
|
var lastOccurrence = GetLastOccurrence(schedule);
|
|
|
|
|
|
2024-12-10 21:16:03 +00:00
|
|
|
// Get the next occurrence from the schedule
|
|
|
|
|
var nextOccurrence = schedule.GetNextOccurrence(currentTime.AddSeconds(-1));
|
2024-12-03 22:52:14 +00:00
|
|
|
|
2025-01-16 21:57:50 +00:00
|
|
|
// Get the previous occurrence from the execution log
|
|
|
|
|
var previousOccurrence = item.LastExecutionTime;
|
|
|
|
|
|
|
|
|
|
// First check if this occurrence was already triggered
|
|
|
|
|
if (previousOccurrence.HasValue &&
|
|
|
|
|
previousOccurrence.Value >= lastOccurrence &&
|
|
|
|
|
previousOccurrence.Value < nextOccurrence.AddSeconds(1))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then check if the current time matches the schedule
|
2024-12-10 21:16:03 +00:00
|
|
|
bool matches = currentTime >= nextOccurrence && currentTime < nextOccurrence.AddSeconds(1);
|
2024-12-03 22:52:14 +00:00
|
|
|
|
2024-12-10 21:16:03 +00:00
|
|
|
if (matches)
|
|
|
|
|
{
|
2025-01-24 21:50:28 +00:00
|
|
|
_logger.LogInformation($"The current time matches the cron expression {item}");
|
|
|
|
|
|
2025-01-29 12:16:40 +00:00
|
|
|
if (publisher != null)
|
|
|
|
|
{
|
|
|
|
|
await publisher.PublishAsync($"Crontab:{item.Title}", item.Cron);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-04-17 07:25:24 +00:00
|
|
|
await HandleCrontabEvent(item);
|
2025-01-29 12:16:40 +00:00
|
|
|
}
|
2024-12-10 21:16:03 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
2024-12-03 22:52:14 +00:00
|
|
|
{
|
2025-01-16 21:57:50 +00:00
|
|
|
_logger.LogError($"Error when running cron task ({item.Title}, {item.Cron}): {ex.Message}");
|
2024-12-10 21:16:03 +00:00
|
|
|
continue;
|
2024-12-03 22:52:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-16 21:57:50 +00:00
|
|
|
|
2025-04-17 07:25:24 +00:00
|
|
|
private async Task HandleCrontabEvent(CrontabItem item)
|
|
|
|
|
{
|
|
|
|
|
using var scope = _services.CreateScope();
|
|
|
|
|
var cron = scope.ServiceProvider.GetRequiredService<ICrontabService>();
|
|
|
|
|
await cron.ScheduledTimeArrived(item);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 21:57:50 +00:00
|
|
|
private DateTime GetLastOccurrence(CrontabSchedule schedule)
|
|
|
|
|
{
|
|
|
|
|
var nextOccurrence = schedule.GetNextOccurrence(DateTime.UtcNow);
|
|
|
|
|
var afterNextOccurrence = schedule.GetNextOccurrence(nextOccurrence);
|
|
|
|
|
var interval = afterNextOccurrence - nextOccurrence;
|
2025-01-24 21:50:28 +00:00
|
|
|
if (interval.TotalMinutes < 1)
|
2025-01-16 21:57:50 +00:00
|
|
|
{
|
2025-01-24 21:50:28 +00:00
|
|
|
throw new ArgumentException("The minimum interval must be at least 1 minutes.");
|
2025-01-16 21:57:50 +00:00
|
|
|
}
|
|
|
|
|
return nextOccurrence - interval;
|
|
|
|
|
}
|
2024-12-03 22:52:14 +00:00
|
|
|
}
|