using LCU.Api.Internal.Dispatcher.Configurations; using LCU.Api.Internal.DispatcherLib.Repositories; using LCU.Api.Internal.MaidBotLib.Models; using LCU.Library.Internal.DispatcherModels.Models.Results; using Newtonsoft.Json; using System.Threading; namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; public class CleanUpJobsService : ICleanUpJobsService { private readonly IJobService _jobService; private readonly ICleanUpJobSettingService _cleanUpJobSettingService; private readonly IUnitOfWork _unitOfWork; private readonly ApiConfiguration _apiConfiguration; public CleanUpJobsService(IJobService jobService, ICleanUpJobSettingService cleanUpJobSettingService, IUnitOfWork unitOfWork, ApiConfiguration apiConfiguration) { _jobService = jobService; _cleanUpJobSettingService = cleanUpJobSettingService; _unitOfWork = unitOfWork; _apiConfiguration = apiConfiguration; } public async Task PerformCleanUpJobs(CancellationToken cancellationToken) { try { var result = new CleanUpJobBatchResult(); result = await PerformSoftCleanUpJobTask(result); result = await PerformHardDeleteCleanUpJobTask(result); await _unitOfWork.CompleteAsync(); if (result.IsFailure) { Log.ForContext(nameof(PropertyEnum.Group), nameof(GroupEnum.ITDevelopment)) .Error("CleanUpJob Batch Result: {@CleanUpJobBatchResult}", result); } } catch (Exception ex) { Log.ForContext(nameof(PropertyEnum.Group), nameof(GroupEnum.ITDevelopment)) .Error(ex, $"Failed to perform Clean Up Jobs."); } } public async Task PerformSoftCleanUpJobTask(CleanUpJobBatchResult result) { var jobs = await _jobService.GetInactiveExcludeSoftDeletedTrackedAsync(); var cleanUpJobSettings = await _cleanUpJobSettingService.GetAsync(); foreach (var setting in cleanUpJobSettings) { if (setting.CleanUpJobTags == null || !setting.CleanUpJobTags.Any()) { continue; } foreach (var job in jobs) { result = PerformCleanUpTaskForJob(result, job, setting); } } return result; } public bool ValidateShouldSoftDelete(CleanUpJobSetting setting, Job job, CleanUpJobBatchResult result) { var shouldSoftDelete = ShouldSoftDeleteByTag(setting, job); if (!shouldSoftDelete) { return false; } return ShouldSoftDeleteByDate(setting, job, result); } public bool ShouldSoftDeleteByDate(CleanUpJobSetting setting, Job job, CleanUpJobBatchResult result) { try { if (!job.WorkItems.Any() || !job.WorkItems.FirstOrDefault().WorkItemExecutions.Any()) { result.AddToFailureCount($"Job would be cleaned but cannot determine if it is old enough to be cleaned due to not having any workItemExecutions. Job: {job.JobId} - {job.Name}"); return false; } var currentDate = DateTime.Now; var cutoffDate = currentDate.AddDays(setting.CompletionDaysPast); var jobLastCompletedTime = job.WorkItems.FirstOrDefault() .WorkItemExecutions.OrderByDescending(wie => wie.CompletedTimestamp).FirstOrDefault().CompletedTimestamp.Value; return DateTime.Compare(cutoffDate, jobLastCompletedTime) > 0; } catch (Exception ex) { var message = $"Error while attempting to compare CompletedTimestamp for SoftDeletion. Job: {job.JobId} - {job.Name}. ExeptionMessage: {ex.Message}"; Log.Error(message); throw new Exception(message); } } public bool ShouldSoftDeleteByTag(CleanUpJobSetting setting, Job job) { if (job.JobTags.Count != setting.CleanUpJobTags.Count) { return false; } var orderedJobTagIds = job.JobTags.Select(t => t.TagId).OrderBy(t => t).ToList(); var orderedCleanUpJobTagIds = setting.CleanUpJobTags.Select(t => t.TagId).OrderBy(t => t).ToList(); return orderedJobTagIds.SequenceEqual(orderedCleanUpJobTagIds); } public async Task PerformHardDeleteCleanUpJobTask(CleanUpJobBatchResult result) { var softDeletedJobs = await _jobService.GetSoftDeletedTrackedAsync(DateTime.Now.AddDays(_apiConfiguration.CleanUpJobsDaysToDelete)); foreach (var job in softDeletedJobs) { try { await _jobService.DeleteFromCleanupAsync(job); } catch (Exception ex) { Log.Error(ex.Message, "Failure to hard delete dispatcher job through Clean Up Job Task. Job: {@Job}", job); result.AddToFailureCount(ex.Message); continue; } } return result; } private CleanUpJobBatchResult PerformCleanUpTaskForJob(CleanUpJobBatchResult result, Job job, CleanUpJobSetting setting) { try { var shouldSoftDelete = ValidateShouldSoftDelete(setting, job, result); if (!shouldSoftDelete) { return result; } job.SoftDeleteDate = DateTime.Now; var updatedJob = _jobService.UpdateExcludingWorkItemsAsyncWithoutSave(job); if (updatedJob == null || updatedJob.Result.JobId != job.JobId) { throw new Exception($"Failure to setup soft delete update for job. Job:{JsonConvert.SerializeObject(job)}"); } } catch (Exception ex) { result.AddToFailureCount(ex.Message); } return result; } }