using CredentialManager.Models;
using CredentialManager.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Data_Coupler.Services;
///
/// Servizio background che gestisce l'esecuzione automatica dei profili schedulati
/// Controlla ogni minuto se ci sono profili da eseguire secondo la schedulazione impostata
///
public class ScheduledExecutionBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
private readonly TimeSpan _checkInterval = TimeSpan.FromMinutes(1); // Controlla ogni minuto
public ScheduledExecutionBackgroundService(
IServiceProvider serviceProvider,
ILogger logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Servizio di schedulazione automatica avviato. Controllo ogni {CheckInterval} minuti.",
_checkInterval.TotalMinutes);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await CheckAndExecuteScheduledProfiles();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante il controllo delle schedulazioni");
}
// Attendi il prossimo controllo
await Task.Delay(_checkInterval, stoppingToken);
}
_logger.LogInformation("Servizio di schedulazione automatica arrestato.");
}
private async Task CheckAndExecuteScheduledProfiles()
{
using var scope = _serviceProvider.CreateScope();
var scheduleService = scope.ServiceProvider.GetRequiredService();
var executionService = scope.ServiceProvider.GetRequiredService();
var profileService = scope.ServiceProvider.GetRequiredService();
try
{
// Ottieni tutte le schedulazioni attive
var activeSchedules = await scheduleService.GetActiveSchedulesAsync();
var currentTime = DateTime.Now; // Usa l'ora locale per il confronto con le schedulazioni
_logger.LogDebug("Controllo schedulazioni: {ScheduleCount} schedulazioni attive alle {CurrentTime}",
activeSchedules.Count, DateTimeHelper.FormatDateTime24H(currentTime));
foreach (var schedule in activeSchedules)
{
try
{
if (ShouldExecuteSchedule(schedule, currentTime))
{
_logger.LogInformation("Esecuzione schedulata per profilo: {ProfileName} (Schedule: {ScheduleName}) - DeletionSync: {DeletionSync}",
schedule.Profile?.Name ?? "N/A", schedule.Name, schedule.EnableDeletionSync);
// Esegui il profilo con il flag deletion sync dalla schedulazione
var result = await executionService.ExecuteProfileAsync(schedule.ProfileId, schedule.EnableDeletionSync);
// Aggiorna la schedulazione
await UpdateScheduleAfterExecution(scheduleService, schedule, currentTime, result.Success);
// Log del risultato
if (result.Success)
{
_logger.LogInformation("Esecuzione schedulata completata con successo per {ProfileName}. " +
"Record processati: {RecordsProcessed}, Durata: {Duration}ms",
schedule.Profile?.Name ?? "N/A", result.RecordsProcessed, result.Duration.TotalMilliseconds);
}
else
{
_logger.LogError("Esecuzione schedulata fallita per {ProfileName}: {ErrorMessage}",
schedule.Profile?.Name ?? "N/A", result.Message);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante l'esecuzione della schedulazione {ScheduleName} per il profilo {ProfileName}",
schedule.Name, schedule.Profile?.Name ?? "N/A");
// Aggiorna la schedulazione anche in caso di errore
try
{
await UpdateScheduleAfterExecution(scheduleService, schedule, currentTime, false);
}
catch (Exception updateEx)
{
_logger.LogError(updateEx, "Errore durante l'aggiornamento della schedulazione {ScheduleName} dopo l'errore",
schedule.Name);
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante il recupero delle schedulazioni attive");
}
}
///
/// Determina se una schedulazione deve essere eseguita in base all'orario corrente
///
private bool ShouldExecuteSchedule(ProfileSchedule schedule, DateTime currentTime)
{
if (!schedule.IsEnabled)
{
return false;
}
// Controllo per evitare esecuzioni multiple nella stessa finestra temporale (tolleranza di 1 minuto)
if (schedule.LastExecutionTime.HasValue)
{
var timeSinceLastExecution = currentTime - schedule.LastExecutionTime.Value;
if (timeSinceLastExecution.TotalMinutes < 1)
{
return false;
}
}
return schedule.ScheduleType.ToLower() switch
{
"once" => ShouldExecuteOnceSchedule(schedule, currentTime),
"daily" => ShouldExecuteDailySchedule(schedule, currentTime),
"weekly" => ShouldExecuteWeeklySchedule(schedule, currentTime),
"monthly" => ShouldExecuteMonthlySchedule(schedule, currentTime),
_ => false
};
}
private bool ShouldExecuteOnceSchedule(ProfileSchedule schedule, DateTime currentTime)
{
if (!schedule.ScheduledDateTime.HasValue)
{
_logger.LogWarning("Schedulazione 'once' senza data/ora specificata: {ScheduleName}", schedule.Name);
return false;
}
var scheduledTime = schedule.ScheduledDateTime.Value;
// Esegui se l'orario programmato è passato e non è mai stato eseguito
return currentTime >= scheduledTime && !schedule.LastExecutionTime.HasValue;
}
private bool ShouldExecuteDailySchedule(ProfileSchedule schedule, DateTime currentTime)
{
if (string.IsNullOrEmpty(schedule.DailyTime))
{
_logger.LogWarning("Schedulazione 'daily' senza orario specificato: {ScheduleName}", schedule.Name);
return false;
}
if (!DateTimeHelper.TryParseTime24H(schedule.DailyTime, out var scheduledTime))
{
_logger.LogWarning("Formato orario non valido per schedulazione daily {ScheduleName}: {DailyTime} (formato richiesto: HH:mm)",
schedule.Name, schedule.DailyTime);
return false;
}
var currentTimeOfDay = currentTime.TimeOfDay;
var scheduledTimeOfDay = scheduledTime;
// Verifica se siamo nell'orario giusto (con tolleranza di ±1 minuto)
var timeDifference = Math.Abs((currentTimeOfDay - scheduledTimeOfDay).TotalMinutes);
if (timeDifference > 1)
{
return false;
}
// Se non è mai stato eseguito, esegui
if (!schedule.LastExecutionTime.HasValue)
{
return true;
}
// Se è stato eseguito, verifica che sia passato almeno un giorno
var lastExecutionDate = schedule.LastExecutionTime.Value.Date;
var currentDate = currentTime.Date;
return currentDate > lastExecutionDate;
}
private bool ShouldExecuteWeeklySchedule(ProfileSchedule schedule, DateTime currentTime)
{
if (!schedule.DayOfWeek.HasValue || string.IsNullOrEmpty(schedule.DailyTime))
{
_logger.LogWarning("Schedulazione 'weekly' senza giorno della settimana o orario specificato: {ScheduleName}",
schedule.Name);
return false;
}
if (!DateTimeHelper.TryParseTime24H(schedule.DailyTime, out var scheduledTime))
{
_logger.LogWarning("Formato orario non valido per schedulazione weekly {ScheduleName}: {DailyTime} (formato richiesto: HH:mm)",
schedule.Name, schedule.DailyTime);
return false;
}
// Verifica il giorno della settimana (0=Domenica, 1=Lunedì, etc.)
var currentDayOfWeek = (int)currentTime.DayOfWeek;
if (currentDayOfWeek != schedule.DayOfWeek.Value)
{
return false;
}
// Verifica l'orario (con tolleranza di ±1 minuto)
var currentTimeOfDay = currentTime.TimeOfDay;
var timeDifference = Math.Abs((currentTimeOfDay - scheduledTime).TotalMinutes);
if (timeDifference > 1)
{
return false;
}
// Se non è mai stato eseguito, esegui
if (!schedule.LastExecutionTime.HasValue)
{
return true;
}
// Se è stato eseguito, verifica che sia passata almeno una settimana
var daysSinceLastExecution = (currentTime.Date - schedule.LastExecutionTime.Value.Date).TotalDays;
return daysSinceLastExecution >= 7;
}
private bool ShouldExecuteMonthlySchedule(ProfileSchedule schedule, DateTime currentTime)
{
if (!schedule.DayOfMonth.HasValue || string.IsNullOrEmpty(schedule.DailyTime))
{
_logger.LogWarning("Schedulazione 'monthly' senza giorno del mese o orario specificato: {ScheduleName}",
schedule.Name);
return false;
}
if (!DateTimeHelper.TryParseTime24H(schedule.DailyTime, out var scheduledTime))
{
_logger.LogWarning("Formato orario non valido per schedulazione monthly {ScheduleName}: {DailyTime} (formato richiesto: HH:mm)",
schedule.Name, schedule.DailyTime);
return false;
}
// Verifica il giorno del mese
var currentDayOfMonth = currentTime.Day;
var scheduledDayOfMonth = schedule.DayOfMonth.Value;
// Gestione per mesi con meno giorni (es. 31 in febbraio diventa ultimo giorno del mese)
var daysInCurrentMonth = DateTime.DaysInMonth(currentTime.Year, currentTime.Month);
var effectiveScheduledDay = Math.Min(scheduledDayOfMonth, daysInCurrentMonth);
if (currentDayOfMonth != effectiveScheduledDay)
{
return false;
}
// Verifica l'orario (con tolleranza di ±1 minuto)
var currentTimeOfDay = currentTime.TimeOfDay;
var timeDifference = Math.Abs((currentTimeOfDay - scheduledTime).TotalMinutes);
if (timeDifference > 1)
{
return false;
}
// Se non è mai stato eseguito, esegui
if (!schedule.LastExecutionTime.HasValue)
{
return true;
}
// Se è stato eseguito, verifica che sia passato almeno un mese
var lastExecutionDate = schedule.LastExecutionTime.Value;
var monthsSinceLastExecution = ((currentTime.Year - lastExecutionDate.Year) * 12) + currentTime.Month - lastExecutionDate.Month;
return monthsSinceLastExecution >= 1;
}
///
/// Aggiorna la schedulazione dopo l'esecuzione
///
private async Task UpdateScheduleAfterExecution(IProfileScheduleService scheduleService,
ProfileSchedule schedule, DateTime executionTime, bool wasSuccessful)
{
try
{
// Aggiorna i dati della schedulazione
schedule.LastExecutionTime = executionTime;
schedule.ExecutionCount++;
// Calcola il prossimo tempo di esecuzione
schedule.NextExecutionTime = CalculateNextExecutionTime(schedule, executionTime);
// Per schedulazioni "once", disabilita dopo l'esecuzione
if (schedule.ScheduleType.ToLower() == "once")
{
schedule.IsEnabled = false;
_logger.LogInformation("Schedulazione 'once' {ScheduleName} disabilitata dopo l'esecuzione", schedule.Name);
}
await scheduleService.UpdateScheduleAsync(schedule);
_logger.LogDebug("Schedulazione {ScheduleName} aggiornata. Prossima esecuzione: {NextExecution}",
schedule.Name, schedule.NextExecutionTime.HasValue ? DateTimeHelper.FormatDateTime24H(schedule.NextExecutionTime.Value) : "N/A");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante l'aggiornamento della schedulazione {ScheduleName}", schedule.Name);
}
}
///
/// Calcola il prossimo tempo di esecuzione per una schedulazione
///
private DateTime? CalculateNextExecutionTime(ProfileSchedule schedule, DateTime lastExecution)
{
if (!schedule.IsEnabled)
{
return null;
}
return schedule.ScheduleType.ToLower() switch
{
"once" => null, // Schedulazione singola non ha prossima esecuzione
"daily" => CalculateNextDailyExecution(schedule, lastExecution),
"weekly" => CalculateNextWeeklyExecution(schedule, lastExecution),
"monthly" => CalculateNextMonthlyExecution(schedule, lastExecution),
_ => null
};
}
private DateTime? CalculateNextDailyExecution(ProfileSchedule schedule, DateTime lastExecution)
{
if (string.IsNullOrEmpty(schedule.DailyTime) || !DateTimeHelper.TryParseTime24H(schedule.DailyTime, out var scheduledTime))
{
return null;
}
var nextDate = lastExecution.Date.AddDays(1);
return nextDate.Add(scheduledTime);
}
private DateTime? CalculateNextWeeklyExecution(ProfileSchedule schedule, DateTime lastExecution)
{
if (!schedule.DayOfWeek.HasValue || string.IsNullOrEmpty(schedule.DailyTime) ||
!DateTimeHelper.TryParseTime24H(schedule.DailyTime, out var scheduledTime))
{
return null;
}
var nextDate = lastExecution.Date.AddDays(7);
return nextDate.Add(scheduledTime);
}
private DateTime? CalculateNextMonthlyExecution(ProfileSchedule schedule, DateTime lastExecution)
{
if (!schedule.DayOfMonth.HasValue || string.IsNullOrEmpty(schedule.DailyTime) ||
!DateTimeHelper.TryParseTime24H(schedule.DailyTime, out var scheduledTime))
{
return null;
}
var nextMonth = lastExecution.AddMonths(1);
var daysInNextMonth = DateTime.DaysInMonth(nextMonth.Year, nextMonth.Month);
var effectiveDay = Math.Min(schedule.DayOfMonth.Value, daysInNextMonth);
var nextDate = new DateTime(nextMonth.Year, nextMonth.Month, effectiveDay);
return nextDate.Add(scheduledTime);
}
}