2024-10-27 05:42:50 +03:00
|
|
|
|
using Cronos;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using Mirea.Api.Endpoint.Common.Services;
|
|
|
|
|
using Mirea.Api.Endpoint.Configuration.Model;
|
2025-02-02 03:30:52 +03:00
|
|
|
|
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
|
2024-10-27 05:42:50 +03:00
|
|
|
|
using Mirea.Api.Endpoint.Sync;
|
|
|
|
|
using System;
|
2025-02-02 03:30:52 +03:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2024-10-27 05:42:50 +03:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks;
|
|
|
|
|
|
|
|
|
|
public class ScheduleSyncService : IHostedService, IDisposable
|
|
|
|
|
{
|
|
|
|
|
private Timer? _timer;
|
2025-02-01 21:23:51 +03:00
|
|
|
|
private string _cronUpdate;
|
2025-02-02 03:30:52 +03:00
|
|
|
|
private List<ScheduleSettings.CronUpdateSkip> _cronUpdateSkip;
|
2024-10-27 05:42:50 +03:00
|
|
|
|
private readonly ILogger<ScheduleSyncService> _logger;
|
|
|
|
|
private CancellationTokenSource _cancellationTokenSource = new();
|
|
|
|
|
private readonly IServiceProvider _serviceProvider;
|
2025-02-01 21:23:51 +03:00
|
|
|
|
private readonly IDisposable? _onChangeUpdateCron;
|
2024-10-27 05:42:50 +03:00
|
|
|
|
|
|
|
|
|
public ScheduleSyncService(IOptionsMonitor<GeneralConfig> generalConfigMonitor, ILogger<ScheduleSyncService> logger, IServiceProvider serviceProvider)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_serviceProvider = serviceProvider;
|
2025-02-01 21:23:51 +03:00
|
|
|
|
_cronUpdate = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSchedule;
|
2025-02-02 03:30:52 +03:00
|
|
|
|
_cronUpdateSkip = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSkipDateList;
|
2024-10-27 05:42:50 +03:00
|
|
|
|
|
|
|
|
|
ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested;
|
2025-02-01 21:23:51 +03:00
|
|
|
|
_onChangeUpdateCron = generalConfigMonitor.OnChange((config) =>
|
|
|
|
|
{
|
2025-02-02 03:30:52 +03:00
|
|
|
|
var updated = false;
|
|
|
|
|
if (config.ScheduleSettings?.CronUpdateSchedule != null && _cronUpdate != config.ScheduleSettings.CronUpdateSchedule)
|
|
|
|
|
{
|
|
|
|
|
_cronUpdate = config.ScheduleSettings.CronUpdateSchedule;
|
|
|
|
|
updated = true;
|
|
|
|
|
}
|
2025-02-01 21:23:51 +03:00
|
|
|
|
|
2025-02-02 03:30:52 +03:00
|
|
|
|
if (config.ScheduleSettings?.CronUpdateSkipDateList != null && !config.ScheduleSettings.CronUpdateSkipDateList.SequenceEqual(_cronUpdateSkip))
|
|
|
|
|
{
|
|
|
|
|
_cronUpdateSkip = config.ScheduleSettings.CronUpdateSkipDateList
|
|
|
|
|
.OrderBy(x => x.End ?? x.Date)
|
|
|
|
|
.ToList();
|
|
|
|
|
updated = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated)
|
|
|
|
|
OnUpdateIntervalRequested();
|
2025-02-01 21:23:51 +03:00
|
|
|
|
});
|
2024-10-27 05:42:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnForceSyncRequested()
|
|
|
|
|
{
|
2025-02-01 21:23:51 +03:00
|
|
|
|
_logger.LogInformation("It was requested to synchronize the data immediately.");
|
2024-12-25 05:43:30 +03:00
|
|
|
|
StopAsync(CancellationToken.None).ContinueWith(_ =>
|
2024-10-27 05:42:50 +03:00
|
|
|
|
{
|
|
|
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
|
ExecuteTask(null);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnUpdateIntervalRequested()
|
|
|
|
|
{
|
2025-02-01 21:23:51 +03:00
|
|
|
|
_logger.LogInformation("It was requested to update the time interval immediately.");
|
2024-12-25 05:43:30 +03:00
|
|
|
|
StopAsync(CancellationToken.None).ContinueWith(_ =>
|
2024-10-27 05:42:50 +03:00
|
|
|
|
{
|
2024-12-25 05:43:30 +03:00
|
|
|
|
StartAsync(CancellationToken.None);
|
2024-10-27 05:42:50 +03:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ScheduleNextRun()
|
|
|
|
|
{
|
2025-02-01 21:23:51 +03:00
|
|
|
|
if (string.IsNullOrEmpty(_cronUpdate))
|
2024-10-27 05:42:50 +03:00
|
|
|
|
{
|
|
|
|
|
_logger.LogWarning("Cron expression is not set. The scheduled task will not run.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-02 03:30:52 +03:00
|
|
|
|
var expression = CronExpression.Parse(_cronUpdate);
|
|
|
|
|
|
|
|
|
|
var nextRunTime = _cronUpdateSkip.GetNextTask(expression).FirstOrDefault();
|
2024-10-27 05:42:50 +03:00
|
|
|
|
|
2025-02-02 03:30:52 +03:00
|
|
|
|
if (nextRunTime == default)
|
2024-10-27 05:42:50 +03:00
|
|
|
|
{
|
2025-02-02 03:30:52 +03:00
|
|
|
|
_logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}",
|
|
|
|
|
TimeZoneInfo.Local.DisplayName);
|
2024-10-27 05:42:50 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-02 03:30:52 +03:00
|
|
|
|
_logger.LogInformation("Next task run in {Time}", nextRunTime.ToString("G"));
|
2024-10-27 05:42:50 +03:00
|
|
|
|
|
2025-02-02 03:30:52 +03:00
|
|
|
|
var delay = (nextRunTime - DateTimeOffset.Now).TotalMilliseconds;
|
2024-10-27 05:42:50 +03:00
|
|
|
|
|
|
|
|
|
// The chance is small, but it's better to check
|
|
|
|
|
if (delay <= 0)
|
|
|
|
|
delay = 1;
|
|
|
|
|
|
|
|
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
2025-02-02 04:50:04 +03:00
|
|
|
|
_timer = new Timer(ExecuteTask, null, delay > int.MaxValue ? int.MaxValue : (int)delay, Timeout.Infinite);
|
2024-10-27 05:42:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async void ExecuteTask(object? state)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var scope = _serviceProvider.CreateScope();
|
|
|
|
|
var syncService = ActivatorUtilities.GetServiceOrCreateInstance<ScheduleSynchronizer>(scope.ServiceProvider);
|
|
|
|
|
await syncService.StartSync(_cancellationTokenSource.Token);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error occurred during schedule synchronization.");
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
ScheduleNextRun();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
ScheduleNextRun();
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
_cancellationTokenSource.Cancel();
|
|
|
|
|
_timer?.Change(Timeout.Infinite, 0);
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2024-12-25 05:43:30 +03:00
|
|
|
|
StopAsync(CancellationToken.None).GetAwaiter().GetResult();
|
2024-10-27 05:42:50 +03:00
|
|
|
|
_timer?.Dispose();
|
|
|
|
|
ScheduleSyncManager.OnForceSyncRequested -= OnForceSyncRequested;
|
2025-02-01 21:23:51 +03:00
|
|
|
|
_onChangeUpdateCron?.Dispose();
|
2024-10-27 05:42:50 +03:00
|
|
|
|
_cancellationTokenSource.Dispose();
|
|
|
|
|
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
}
|