using Cleanuparr.Domain.Enums; using Cleanuparr.Infrastructure.Features.Context; using Cleanuparr.Infrastructure.Features.Jobs; using Cleanuparr.Infrastructure.Helpers; using Cleanuparr.Infrastructure.Hubs; using Cleanuparr.Infrastructure.Models; using Cleanuparr.Infrastructure.Services.Interfaces; using Cleanuparr.Persistence; using Cleanuparr.Persistence.Models.State; using Microsoft.AspNetCore.SignalR; using Quartz; using Serilog.Context; namespace Cleanuparr.Api.Jobs; [DisallowConcurrentExecution] public sealed class GenericJob : IJob where T : IHandler { private readonly ILogger> _logger; private readonly IServiceScopeFactory _scopeFactory; public GenericJob(ILogger> logger, IServiceScopeFactory scopeFactory) { _logger = logger; _scopeFactory = scopeFactory; } public async Task Execute(IJobExecutionContext context) { using var _ = LogContext.PushProperty("JobName", typeof(T).Name); Guid jobRunId = Guid.CreateVersion7(); JobType jobType = Enum.Parse(typeof(T).Name); JobRunStatus? status = null; try { await using var scope = _scopeFactory.CreateAsyncScope(); var eventsContext = scope.ServiceProvider.GetRequiredService(); var hubContext = scope.ServiceProvider.GetRequiredService>(); var jobManagementService = scope.ServiceProvider.GetRequiredService(); var jobRun = new JobRun { Id = jobRunId, Type = jobType }; eventsContext.JobRuns.Add(jobRun); await eventsContext.SaveChangesAsync(); ContextProvider.SetJobRunId(jobRunId); using var __ = LogContext.PushProperty(LogProperties.JobRunId, jobRunId.ToString()); await BroadcastJobStatus(hubContext, jobManagementService, jobType, false); var handler = scope.ServiceProvider.GetRequiredService(); await handler.ExecuteAsync(context.CancellationToken); status = JobRunStatus.Completed; await BroadcastJobStatus(hubContext, jobManagementService, jobType, true); } catch (Exception ex) { _logger.LogError(ex, "{name} failed", typeof(T).Name); status = JobRunStatus.Failed; } finally { await using var finalScope = _scopeFactory.CreateAsyncScope(); var eventsContext = finalScope.ServiceProvider.GetRequiredService(); var jobRun = await eventsContext.JobRuns.FindAsync(jobRunId); if (jobRun is not null) { jobRun.CompletedAt = DateTime.UtcNow; jobRun.Status = status; await eventsContext.SaveChangesAsync(); } } } private async Task BroadcastJobStatus(IHubContext hubContext, IJobManagementService jobManagementService, JobType jobType, bool isFinished) { try { JobInfo jobInfo = await jobManagementService.GetJob(jobType); if (isFinished) { jobInfo.Status = "Scheduled"; } await hubContext.Clients.All.SendAsync("JobStatusUpdate", jobInfo); } catch (Exception ex) { _logger.LogError(ex, "Failed to broadcast job status update"); } } }