149 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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;
 | |
| using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
 | |
| using Mirea.Api.Endpoint.Sync;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Threading;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks;
 | |
| 
 | |
| public class ScheduleSyncService : IHostedService, IDisposable
 | |
| {
 | |
|     private Timer? _timer;
 | |
|     private string _cronUpdate;
 | |
|     private List<ScheduleSettings.CronUpdateSkip> _cronUpdateSkip;
 | |
|     private readonly ILogger<ScheduleSyncService> _logger;
 | |
|     private CancellationTokenSource _cancellationTokenSource = new();
 | |
|     private readonly IServiceProvider _serviceProvider;
 | |
|     private readonly IDisposable? _onChangeUpdateCron;
 | |
| 
 | |
|     public ScheduleSyncService(IOptionsMonitor<GeneralConfig> generalConfigMonitor, ILogger<ScheduleSyncService> logger, IServiceProvider serviceProvider)
 | |
|     {
 | |
|         _logger = logger;
 | |
|         _serviceProvider = serviceProvider;
 | |
|         _cronUpdate = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSchedule;
 | |
|         _cronUpdateSkip = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSkipDateList;
 | |
| 
 | |
|         ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested;
 | |
|         _onChangeUpdateCron = generalConfigMonitor.OnChange((config) =>
 | |
|         {
 | |
|             var updated = false;
 | |
|             if (config.ScheduleSettings?.CronUpdateSchedule != null && _cronUpdate != config.ScheduleSettings.CronUpdateSchedule)
 | |
|             {
 | |
|                 _cronUpdate = config.ScheduleSettings.CronUpdateSchedule;
 | |
|                 updated = true;
 | |
|             }
 | |
| 
 | |
|             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();
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     private void OnForceSyncRequested()
 | |
|     {
 | |
|         _logger.LogInformation("It was requested to synchronize the data immediately.");
 | |
|         StopAsync(CancellationToken.None).ContinueWith(_ =>
 | |
|         {
 | |
|             _cancellationTokenSource = new CancellationTokenSource();
 | |
|             ExecuteTask(null);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     private void OnUpdateIntervalRequested()
 | |
|     {
 | |
|         _logger.LogInformation("It was requested to update the time interval immediately.");
 | |
|         StopAsync(CancellationToken.None).ContinueWith(_ =>
 | |
|         {
 | |
|             StartAsync(CancellationToken.None);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     private void ScheduleNextRun()
 | |
|     {
 | |
|         if (string.IsNullOrEmpty(_cronUpdate))
 | |
|         {
 | |
|             _logger.LogWarning("Cron expression is not set. The scheduled task will not run.");
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         var expression = CronExpression.Parse(_cronUpdate);
 | |
| 
 | |
|         var nextRunTime = _cronUpdateSkip.GetNextTask(expression).FirstOrDefault();
 | |
| 
 | |
|         if (nextRunTime == default)
 | |
|         {
 | |
|             _logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}",
 | |
|                 TimeZoneInfo.Local.DisplayName);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         _logger.LogInformation("Next task run in {Time}", nextRunTime.ToString("G"));
 | |
| 
 | |
|         var delay = (nextRunTime - DateTimeOffset.Now).TotalMilliseconds;
 | |
| 
 | |
|         // The chance is small, but it's better to check
 | |
|         if (delay <= 0)
 | |
|             delay = 1;
 | |
| 
 | |
|         _cancellationTokenSource = new CancellationTokenSource();
 | |
|         _timer = new Timer(ExecuteTask, null, delay > int.MaxValue ? int.MaxValue : (int)delay, Timeout.Infinite);
 | |
|     }
 | |
| 
 | |
|     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()
 | |
|     {
 | |
|         StopAsync(CancellationToken.None).GetAwaiter().GetResult();
 | |
|         _timer?.Dispose();
 | |
|         ScheduleSyncManager.OnForceSyncRequested -= OnForceSyncRequested;
 | |
|         _onChangeUpdateCron?.Dispose();
 | |
|         _cancellationTokenSource.Dispose();
 | |
| 
 | |
|         GC.SuppressFinalize(this);
 | |
|     }
 | |
| } |