using System.Threading.Tasks; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; using Microsoft.Extensions.Logging; using System; using Microsoft.EntityFrameworkCore; using System.Linq; namespace AyaNova.Biz { /// /// Handle attachment file related cleanup and checking /// internal class AttachmentBiz : BizObject, IJobObject { internal AttachmentBiz(AyContext dbcontext, long currentUserId, AuthorizationRoles userRoles) { ct = dbcontext; UserId = currentUserId; CurrentUserRoles = userRoles; BizType = AyaType.FileAttachment; } //////////////////////////////////////////////////////////////////////////////////////////////// //JOB / OPERATIONS // public async Task HandleJobAsync(OpsJob job) { //Hand off the particular job to the corresponding processing code //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so //basically any error condition during job processing should throw up an exception if it can't be handled //There might be future other job types so doing it like this for all biz job handlers for now switch (job.JobType) { case JobType.AttachmentMaintenance: await ProcessAttachmentMaintenanceAsync(job); break; default: throw new System.ArgumentOutOfRangeException($"AttachmentBiz.HandleJobAsync -> Invalid job type{job.JobType.ToString()}"); } } /// /// Handle the job /// /// private async Task ProcessAttachmentMaintenanceAsync(OpsJob job) { ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("AttachmentMaintenanceJob"); ApiServerState apiServerState = (ApiServerState)ServiceProviderProvider.Provider.GetService(typeof(ApiServerState)); //get the current server state so can set back to it later ApiServerState.ServerState wasServerState = apiServerState.GetState(); string wasReason = apiServerState.Reason; try { await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); await JobsBiz.LogJobAsync(job.GId, $"Starting..."); apiServerState.SetOpsOnly("Attachment file maintenance"); // EXISTENCE CHECK //iterate all records in chunks bool moreRecords = true; int skip = 0; int chunkSize = 100; do { var chunk = await ct.FileAttachment.AsNoTracking().OrderBy(z => z.Id).Skip(skip).Take(chunkSize).Select(z => new NameIdItem { Id = z.Id, Name = z.StoredFileName }).ToListAsync(); if (chunk.Count < chunkSize) { //we've reached the end moreRecords = false; } skip += chunkSize; foreach (NameIdItem i in chunk) { //Does file exists where it's supposed to be? if (!FileUtil.AttachmentFileExists(i.Name)) { var f = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == i.Id); if (f != null) { f.Exists = false; await ct.SaveChangesAsync(); } } } } while (moreRecords); // long totalRecs = await ct.FileAttachment.LongCountAsync(); var allAttachments = FileUtil.GetAllAttachmentFilePaths(); // iterate FileAttachment records, if physically present then flag as such // make sure it has a short circuit that if there are NO files physically then all are out of sync // this scenario is someone moving a db and not moving physical files // ORPHANED FILES CHECK // These should never be out of sync due to timing issues, the file would be deleted before teh db record // iterate physical files, if not in db then make a FileAttachment record for it AyaType nothing id 0 // if user want's to move them, they can download and then attach and then remove the generated attachment (Nothing id 0) the holder of orphaned files // (Or move feature) // DE-ORPHANIZE ACTION // iterate orphaned file attachments to NOTHING type, if found to be attached to any other object remove the orphaned object attachement await JobsBiz.LogJobAsync(job.GId, "Finished."); await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); } catch (Exception ex) { log.LogError(ex, "AttachmentMaintenanceJob error during ops"); await JobsBiz.LogJobAsync(job.GId, $"AttachmentMaintenanceJob error during ops\r\n{ex.Message}"); throw ex; } finally { log.LogInformation($"AttachmentMaintenanceJob: setting server state back to {wasServerState.ToString()}"); apiServerState.SetState(wasServerState, wasReason); } } //Other job handlers here... ///////////////////////////////////////////////////////////////////// }//eoc }//eons