e35de1614f
- Disabilitata completamente la sincronizzazione eliminazioni nei trasferimenti manuali (DataCoupler.razor.cs) - Aggiunto campo EnableDeletionSync al modello ProfileSchedule (default: false) - Implementata logica condizionale in ScheduledProfileExecutionService per deletion sync - Aggiunta sezione 'Opzioni Avanzate' nell'interfaccia schedulazione con warning - Creata migration Entity Framework AddEnableDeletionSyncToProfileSchedule - Aggiornato BackupModels per supporto backup/restore del nuovo campo - Aggiornata documentazione README.md e copilot-instructions.md - La deletion sync è ora disponibile solo per schedulazioni con configurazione esplicita per massima sicurezza
388 lines
15 KiB
C#
388 lines
15 KiB
C#
using CredentialManager.Models;
|
|
using CredentialManager.Services;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Data_Coupler.Services;
|
|
|
|
/// <summary>
|
|
/// Servizio background che gestisce l'esecuzione automatica dei profili schedulati
|
|
/// Controlla ogni minuto se ci sono profili da eseguire secondo la schedulazione impostata
|
|
/// </summary>
|
|
public class ScheduledExecutionBackgroundService : BackgroundService
|
|
{
|
|
private readonly IServiceProvider _serviceProvider;
|
|
private readonly ILogger<ScheduledExecutionBackgroundService> _logger;
|
|
private readonly TimeSpan _checkInterval = TimeSpan.FromMinutes(1); // Controlla ogni minuto
|
|
|
|
public ScheduledExecutionBackgroundService(
|
|
IServiceProvider serviceProvider,
|
|
ILogger<ScheduledExecutionBackgroundService> 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<IProfileScheduleService>();
|
|
var executionService = scope.ServiceProvider.GetRequiredService<IScheduledProfileExecutionService>();
|
|
var profileService = scope.ServiceProvider.GetRequiredService<IDataCouplerProfileService>();
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determina se una schedulazione deve essere eseguita in base all'orario corrente
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiorna la schedulazione dopo l'esecuzione
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calcola il prossimo tempo di esecuzione per una schedulazione
|
|
/// </summary>
|
|
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);
|
|
}
|
|
} |