Files
Data-Coupler/CredentialManager/Services/ProfileScheduleService.cs
Alessio Dal Santo a5f8943c72
Build and Push Docker Images / Build Linux Container (push) Successful in 8m59s
Build and Push Docker Images / Build Windows Container (push) Successful in 9m35s
Build and Push Docker Images / Create Multi-Platform Manifest (push) Failing after 25s
[Feature] Implementata schedulazione completa per file CSV/Excel
- Aggiunta validazione percorsi file prima del salvataggio profili
- Implementati metodi di lettura file CSV e Excel per schedulazioni
- Supporto doppia modalità: caricamento browser (preview) e percorso manuale (schedulazione)
- Gestione completa deletion sync anche per file CSV/Excel
- Rilevamento automatico separatori CSV (virgola, punto e virgola, tab, pipe)
- Supporto formati Excel legacy (.xls) e moderni (.xlsx)
- Abilitati profili file nella UI di schedulazione
- Logging dettagliato per troubleshooting
- Documentazione completa in CSV_SCHEDULING_IMPLEMENTATION.md
- Aggiornati README.md e copilot-instructions.md con nuove feature
- Rimosso testo 'TEST' dalla pagina di login
2026-01-25 12:45:32 +01:00

355 lines
12 KiB
C#

using CredentialManager.Data;
using CredentialManager.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CredentialManager.Services;
public class ProfileScheduleService : IProfileScheduleService
{
private readonly CredentialDbContext _context;
private readonly ILogger<ProfileScheduleService> _logger;
public ProfileScheduleService(CredentialDbContext context, ILogger<ProfileScheduleService> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<List<ProfileSchedule>> GetAllSchedulesAsync()
{
try
{
return await _context.ProfileSchedules
.Include(s => s.Profile)
.OrderBy(s => s.Name)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero di tutte le schedulazioni");
throw;
}
}
public async Task<ProfileSchedule?> GetScheduleByIdAsync(int id)
{
try
{
return await _context.ProfileSchedules
.Include(s => s.Profile)
.FirstOrDefaultAsync(s => s.Id == id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero della schedulazione con ID {Id}", id);
throw;
}
}
public async Task<ProfileSchedule> CreateScheduleAsync(ProfileSchedule schedule)
{
try
{
schedule.CreatedAt = DateTime.UtcNow;
schedule.UpdatedAt = DateTime.UtcNow;
schedule.CreatedBy = Environment.UserName;
// Calcola la prossima esecuzione
schedule.NextExecutionTime = schedule.CalculateNextExecution();
_context.ProfileSchedules.Add(schedule);
await _context.SaveChangesAsync();
_logger.LogInformation("Schedulazione creata: {Name} per il profilo {ProfileId}", schedule.Name, schedule.ProfileId);
return schedule;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione della schedulazione {Name}", schedule.Name);
throw;
}
}
public async Task<ProfileSchedule> UpdateScheduleAsync(ProfileSchedule schedule)
{
try
{
var existingSchedule = await _context.ProfileSchedules.FindAsync(schedule.Id);
if (existingSchedule == null)
throw new InvalidOperationException($"Schedulazione con ID {schedule.Id} non trovata");
// Aggiorna i campi
existingSchedule.Name = schedule.Name;
existingSchedule.Description = schedule.Description;
existingSchedule.ProfileId = schedule.ProfileId;
existingSchedule.IsEnabled = schedule.IsEnabled;
existingSchedule.ScheduleType = schedule.ScheduleType;
existingSchedule.ScheduledDateTime = schedule.ScheduledDateTime;
existingSchedule.DailyTime = schedule.DailyTime;
existingSchedule.DayOfWeek = schedule.DayOfWeek;
existingSchedule.DayOfMonth = schedule.DayOfMonth;
existingSchedule.IntervalValue = schedule.IntervalValue;
existingSchedule.IntervalUnit = schedule.IntervalUnit;
existingSchedule.IsActive = schedule.IsActive;
existingSchedule.EnableDeletionSync = schedule.EnableDeletionSync;
existingSchedule.SourceDatabaseOverride = schedule.SourceDatabaseOverride;
existingSchedule.DestinationDatabaseOverride = schedule.DestinationDatabaseOverride;
existingSchedule.UpdatedAt = DateTime.UtcNow;
// Ricalcola la prossima esecuzione
existingSchedule.NextExecutionTime = existingSchedule.CalculateNextExecution();
await _context.SaveChangesAsync();
_logger.LogInformation("Schedulazione aggiornata: {Name}", existingSchedule.Name);
return existingSchedule;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento della schedulazione con ID {Id}", schedule.Id);
throw;
}
}
public async Task<bool> DeleteScheduleAsync(int id)
{
try
{
var schedule = await _context.ProfileSchedules.FindAsync(id);
if (schedule == null)
return false;
_context.ProfileSchedules.Remove(schedule);
await _context.SaveChangesAsync();
_logger.LogInformation("Schedulazione eliminata: {Name} (ID: {Id})", schedule.Name, id);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'eliminazione della schedulazione con ID {Id}", id);
throw;
}
}
public async Task<List<ProfileSchedule>> GetActiveSchedulesAsync()
{
try
{
return await _context.ProfileSchedules
.Include(s => s.Profile)
.Where(s => s.IsActive && s.IsEnabled)
.OrderBy(s => s.NextExecutionTime)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero delle schedulazioni attive");
throw;
}
}
public async Task<List<ProfileSchedule>> GetPendingExecutionsAsync()
{
try
{
var now = DateTime.Now;
return await _context.ProfileSchedules
.Include(s => s.Profile)
.Where(s => s.IsActive &&
s.IsEnabled &&
s.NextExecutionTime.HasValue &&
s.NextExecutionTime <= now &&
s.LastExecutionStatus != "running")
.OrderBy(s => s.NextExecutionTime)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero delle schedulazioni in attesa di esecuzione");
throw;
}
}
public async Task<bool> UpdateExecutionStatusAsync(int scheduleId, string status, string? message = null, int? recordCount = null)
{
try
{
var schedule = await _context.ProfileSchedules.FindAsync(scheduleId);
if (schedule == null)
return false;
schedule.LastExecutionStatus = status;
schedule.LastExecutionMessage = message;
schedule.LastExecutionTime = DateTime.Now;
if (recordCount.HasValue)
schedule.LastExecutionRecordCount = recordCount;
if (status == "success" || status == "failed")
{
schedule.ExecutionCount++;
// Calcola la prossima esecuzione solo se completata (successo o errore)
schedule.NextExecutionTime = schedule.CalculateNextExecution();
}
await _context.SaveChangesAsync();
_logger.LogInformation("Status esecuzione aggiornato per schedulazione {ScheduleId}: {Status}", scheduleId, status);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento dello status per schedulazione {ScheduleId}", scheduleId);
throw;
}
}
public async Task UpdateNextExecutionTimeAsync(int scheduleId)
{
try
{
var schedule = await _context.ProfileSchedules.FindAsync(scheduleId);
if (schedule == null)
return;
// Per schedulazioni a intervallo, calcola dalla ultima esecuzione
if (schedule.ScheduleType == "interval")
{
schedule.NextExecutionTime = schedule.CalculateNextExecutionFromLast();
}
else
{
schedule.NextExecutionTime = schedule.CalculateNextExecution();
}
await _context.SaveChangesAsync();
_logger.LogDebug("Prossima esecuzione aggiornata per schedulazione {ScheduleId}: {NextExecution} (tipo: {Type})",
scheduleId, schedule.NextExecutionTime, schedule.ScheduleType);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento della prossima esecuzione per schedulazione {ScheduleId}", scheduleId);
throw;
}
}
public async Task<List<DataCouplerProfile>> GetAvailableProfilesAsync()
{
try
{
return await _context.DataCouplerProfiles
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero dei profili disponibili");
throw;
}
}
// Implementazione metodi per lo storico delle esecuzioni
public async Task<ScheduleExecutionHistory> CreateExecutionHistoryAsync(ScheduleExecutionHistory history)
{
try
{
history.CreatedAt = DateTime.UtcNow;
_context.ScheduleExecutionHistories.Add(history);
await _context.SaveChangesAsync();
_logger.LogInformation("Storico esecuzione creato: ID {HistoryId} per schedulazione {ScheduleId}",
history.Id, history.ScheduleId);
return history;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione dello storico esecuzione per schedulazione {ScheduleId}",
history.ScheduleId);
throw;
}
}
public async Task<ScheduleExecutionHistory> UpdateExecutionHistoryAsync(ScheduleExecutionHistory history)
{
try
{
_context.ScheduleExecutionHistories.Update(history);
await _context.SaveChangesAsync();
_logger.LogDebug("Storico esecuzione aggiornato: ID {HistoryId}", history.Id);
return history;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento dello storico esecuzione ID {HistoryId}", history.Id);
throw;
}
}
public async Task<List<ScheduleExecutionHistory>> GetExecutionHistoryAsync(int scheduleId, int? limit = null)
{
try
{
var query = _context.ScheduleExecutionHistories
.Where(h => h.ScheduleId == scheduleId)
.OrderByDescending(h => h.StartTime);
if (limit.HasValue)
{
query = (IOrderedQueryable<ScheduleExecutionHistory>)query.Take(limit.Value);
}
return await query.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero dello storico per schedulazione {ScheduleId}", scheduleId);
throw;
}
}
public async Task<List<ScheduleExecutionHistory>> GetRecentExecutionsAsync(int limit = 50)
{
try
{
return await _context.ScheduleExecutionHistories
.Include(h => h.Schedule)
.OrderByDescending(h => h.StartTime)
.Take(limit)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero delle esecuzioni recenti");
throw;
}
}
public async Task<ScheduleExecutionHistory?> GetExecutionByIdAsync(int executionId)
{
try
{
return await _context.ScheduleExecutionHistories
.Include(h => h.Schedule)
.FirstOrDefaultAsync(h => h.Id == executionId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero dell'esecuzione ID {ExecutionId}", executionId);
throw;
}
}
}