using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Tango.BL; using Tango.BL.Builders; using Tango.BL.DTO; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core; using Tango.Core.ExtensionMethods; using Tango.Integration.Operation; using Tango.PMR.MachineStatus; using Tango.PMR.Printing; namespace Tango.Integration.JobRuns { /// /// Represents a basic database job runs logger. /// /// public class BasicJobRunsLogger : ExtendedObject, IJobRunsLogger { private Job _job; private Machine _defaultMachine; private List _currentJobEvents; private MachineStatus _startMachineStatus; private JobTicket _jobTicket; public event EventHandler JobRunAvailable; #region Properties /// /// Gets the machine operator. /// public IMachineOperator MachineOperator { get; private set; } /// /// Gets a value indicating whether this instance is started. /// public bool IsStarted { get; private set; } /// /// Gets or sets the job designations of which the logger should log (supports multiple flags). /// public JobDesignations JobDesignationFilter { get; set; } /// /// Gets or sets the job run source when logging job runs. /// public JobSource JobSource { get; set; } /// /// Gets or sets a value indicating whether create job run files with job ticket information and more. /// public bool CreateJobRunsFiles { get; set; } /// /// Gets or sets the job runs files folder. /// public String JobRunsFolder { get; set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The machine operator. public BasicJobRunsLogger(IMachineOperator machineOperator) { JobDesignationFilter = JobDesignations.Default | JobDesignations.SampleDye | JobDesignations.FineTuning; MachineOperator = machineOperator; Init(); } #endregion #region Private Methods /// /// Initializes this instance. /// private void Init() { MachineOperator.PrintingStarted -= Machine_PrintingStarted; MachineOperator.PrintingStarted += Machine_PrintingStarted; MachineOperator.PrintingCompleted -= Machine_PrintingCompleted; MachineOperator.PrintingCompleted += Machine_PrintingCompleted; MachineOperator.PrintingAborted -= Machine_PrintingAborted; MachineOperator.PrintingAborted += Machine_PrintingAborted; MachineOperator.PrintingFailed -= Machine_PrintingFailed; MachineOperator.PrintingFailed += Machine_PrintingFailed; MachineOperator.HeadCleaningEnded += MachineOperator_HeadCleaningEnded; MachineOperator.MachineEventsStateProvider.EventsReceived -= MachineEventsStateProvider_EventsReceived; MachineOperator.MachineEventsStateProvider.EventsReceived += MachineEventsStateProvider_EventsReceived; _currentJobEvents = new List(); JobRunsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "JobRuns Extended Info"); } private void MachineEventsStateProvider_EventsReceived(object sender, IEnumerable events) { foreach (var item in events.ToList()) { _currentJobEvents.Add(item); } } private bool ShouldLog() { return IsStarted && _job != null && JobDesignationFilter.HasFlag(_job.Designation); } private void InsertJobRun(PrintingEventArgs e, JobRunStatus status, Exception exception) { if (!e.IsResumingJob) { if (ShouldLog()) { if (e.Job.Guid == _job.Guid) { Task.Factory.StartNew(() => { try { using (var db = ObservablesContext.CreateDefault()) { var colorSpaces = db.ColorSpaces.ToList(); JobRun run = new JobRun(); run.UserGuid = _job.UserGuid; run.StartDate = e.StartDate; run.UploadingStartDate = e.UploadingStartTime; run.HeatingStartDate = e.HeatingStartTime; run.ActualStartDate = e.ActualStartTime; run.EndDate = DateTime.UtcNow; run.JobName = _job.Name; run.Source = JobSource; run.Designation = _job.Designation; run.JobGuid = _job.Guid; run.RmlGuid = _job.RmlGuid; run.MachineGuid = _job.MachineGuid; run.JobRunStatus = status; run.EndPosition = e.JobHandler.Status.Progress; run.NumberOfUnits = _job.NumberOfUnits; run.JobLength = e.JobHandler.Status.TotalProgress; run.JobLogicalLength = e.Job.Length; run.LiquidQuantities = e.LiquidQuantities; run.IsGradient = _job.Segments.Any(x => x.BrushStops.Count > 1); run.GradientResolutionCm = MachineOperator.GradientGenerationConfiguration.ResolutionCM; run.ActualStartPosition = e.Job.ResumeStartPosition; run.ActualEndPosition = e.JobHandler.Status.ProgressMinusSettingUp; if (_defaultMachine != null) { run.MachineType = _defaultMachine.MachineType; } else if (_job.Machine != null) { run.MachineType = _job.Machine.MachineType; } var jobFile = e.Job.ToJobFileWhenLoaded(); try { if (_job.Designation == JobDesignations.FineTuning) { jobFile.Segments.First().BrushStops.First().ColorSpaceGuid = colorSpaces.First(x => x.Code == (int)ColorSpaces.LAB).Guid; } } catch (Exception ex) { LogManager.Log(ex, "Error setting brush stop color space to LAB on fine tuning job run (JobFileString)."); } run.JobString = jobFile.ToString(); run.ApplicationVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(); run.FirmwareVersion = MachineOperator.DeviceInformation?.Version; run.CeVersion = _job.Rml.ColorConversionVersion.ToString(); run.ProcessParametersTableGuid = MachineOperator.CurrentProcessParameters?.Guid; //Set liquid quantities SetJobRunLiquidQuantities(run, run.LiquidQuantities); if (exception != null) { run.FailedMessage = exception.FlattenMessage(); } if (_job.Designation == JobDesignations.FineTuning) { try { run.FineTuningString = JsonConvert.SerializeObject(_job.VectorFineTuningRunModel); } catch (Exception ex) { LogManager.Log(ex, "Error serializing fine tuning model for job run."); } } db.JobRuns.Add(run); e.Job.LastRun = DateTime.UtcNow; _job.LastRun = DateTime.UtcNow; var job = db.Jobs.SingleOrDefault(x => x.Guid == _job.Guid); if (job != null) { job.LastRun = DateTime.UtcNow; } LogManager.Log($"Inserting job run for '{run.JobName}'...\n{run.ToJsonString(nameof(JobRun.JobString), nameof(JobRun.LiquidQuantityString))}"); db.SaveChanges(); JobRunInfo jobRunInfo = new JobRunInfo(); jobRunInfo.JobRunID = run.ID; jobRunInfo.JobTicket = _jobTicket; jobRunInfo.Events = _currentJobEvents.Select(x => MachinesEventDTO.FromObservable(x)).ToList(); jobRunInfo.StartMachineStatus = _startMachineStatus; jobRunInfo.EndMachineStatus = MachineOperator.MachineStatus?.Clone(); if (CreateJobRunsFiles) { try { Directory.CreateDirectory(JobRunsFolder); String json = jobRunInfo.ToJsonString(); File.WriteAllText(Path.Combine(JobRunsFolder, $"{run.ID}.run"), json); LogManager.Log($"JobRun extended info file '{run.ID}' created."); } catch (Exception ex) { LogManager.Log(ex, "Error creating job run extended info file."); } } JobRunAvailable?.Invoke(this, new JobRunAvailableEventArgs() { JobRunInfo = jobRunInfo, JobRun = run }); } } catch (Exception ex) { LogManager.Log(ex, "Error logging the last job run to the database."); } }); } } } else { using (ObservablesContext db = ObservablesContext.CreateDefault()) { try { var run = db.JobRuns.OrderByDescending(x => x.LastUpdated).FirstOrDefault(x => x.JobGuid == _job.Guid); if (run == null) { LogManager.Log($"Error updating job run by resumed job. Could not locate the existing job run by job guid '{_job.Guid}'."); return; } run.LastUpdated = DateTime.UtcNow; run.EndDate = DateTime.UtcNow; run.JobRunStatus = status; run.EndPosition = e.JobHandler.Status.Progress; run.JobLength = e.JobHandler.Status.TotalProgress; run.ActualEndPosition = e.JobHandler.Status.ProgressMinusSettingUp; if (exception != null) { run.FailedMessage = exception.FlattenMessage(); } else { run.FailedMessage = null; } var newQuantities = e.LiquidQuantities.ToList(); var oldQuantities = run.LiquidQuantities.ToList(); SetJobRunLiquidQuantities(run, newQuantities, update: true); //Append liquid quantities string. foreach (var newQuantity in newQuantities) { var oldQuantity = oldQuantities.FirstOrDefault(x => x.LiquidType == newQuantity.LiquidType); if (oldQuantity != null) { oldQuantity.Quantity += newQuantity.Quantity; } else { oldQuantities.Add(newQuantity); } } run.LiquidQuantities = oldQuantities; db.SaveChanges(); //TODO: Needs to resubmit this to Azure! Then in Azure Distinct by job run ID, by job guid, by latest! JobRunInfo jobRunInfo = null; if (CreateJobRunsFiles) { try { String jobInfoPath = Path.Combine(JobRunsFolder, $"{run.ID}.run"); if (File.Exists(jobInfoPath)) { String json = File.ReadAllText(jobInfoPath); jobRunInfo = JsonConvert.DeserializeObject(json); jobRunInfo.Events.AddRange(_currentJobEvents.Select(x => MachinesEventDTO.FromObservable(x)).ToList()); jobRunInfo.EndMachineStatus = MachineOperator.MachineStatus?.Clone(); File.WriteAllText(jobInfoPath, jobRunInfo.ToJsonString()); } } catch (Exception ex) { LogManager.Log(ex, "Error while trying to update jobrun info file after job resume."); } } JobRunAvailable?.Invoke(this, new JobRunAvailableEventArgs() { JobRunInfo = jobRunInfo, JobRun = run }); } catch (Exception ex) { LogManager.Log(ex, "Error while trying to update jobrun by resumed job."); } } } } private void InsertHeadCleaningJobRun(HeadCleaningEndedEventArgs e) { if (IsStarted && _defaultMachine != null) { Task.Factory.StartNew(() => { try { using (var db = ObservablesContext.CreateDefault()) { JobRun run = new JobRun(); run.IsHeadCleaning = true; run.StartDate = e.StartDate; run.UploadingStartDate = e.StartDate; run.HeatingStartDate = e.StartDate; run.ActualStartDate = e.StartDate; run.EndDate = DateTime.UtcNow; run.JobName = "HEAD CLEANING"; run.Source = JobSource; run.MachineGuid = _defaultMachine.Guid; run.JobRunStatus = e.Status; run.EndPosition = e.EndPosition; run.JobLength = e.Length; run.LiquidQuantities = e.LiquidQuantities; SetJobRunLiquidQuantities(run, run.LiquidQuantities); //if (exception != null) //{ // run.FailedMessage = exception.FlattenMessage(); //} db.JobRuns.Add(run); LogManager.Log($"Inserting head cleaning job run...\n{run.ToJsonString(nameof(JobRun.JobString), nameof(JobRun.LiquidQuantityString))}"); db.SaveChanges(); } } catch (Exception ex) { LogManager.Log(ex, "Error logging the last head cleaning job run to the database."); } }); } } private void SetJobRunLiquidQuantities(JobRun run, List quantities, bool update = false) { if (run == null || quantities == null) return; // Map each LiquidType to the corresponding JobRun property updater var setters = new Dictionary> { { LiquidTypes.Cyan, q => run.CyanQuantity = update ? run.CyanQuantity + q : q }, { LiquidTypes.Magenta, q => run.MagentaQuantity = update ? run.MagentaQuantity + q : q }, { LiquidTypes.Yellow, q => run.YellowQuantity = update ? run.YellowQuantity + q : q }, { LiquidTypes.Black, q => run.BlackQuantity = update ? run.BlackQuantity + q : q }, { LiquidTypes.TransparentInk, q => run.TransparentQuantity = update ? run.TransparentQuantity + q : q }, { LiquidTypes.Lubricant, q => run.LubricantQuantity = update ? run.LubricantQuantity + q : q }, { LiquidTypes.Cleaner, q => run.CleanerQuantity = update ? run.CleanerQuantity + q : q }, { LiquidTypes.LightCyan, q => run.LightCyanQuantity = update ? run.LightCyanQuantity + q : q }, { LiquidTypes.LightMagenta, q => run.LightMagentaQuantity = update ? run.LightMagentaQuantity + q : q }, { LiquidTypes.LightYellow, q => run.LightYellowQuantity = update ? run.LightYellowQuantity + q : q }, { LiquidTypes.Blue, q => run.BlueQuantity = update ? run.BlueQuantity + q : q }, { LiquidTypes.LightBlue, q => run.LightBlueQuantity = update ? run.LightBlueQuantity + q : q }, { LiquidTypes.Orange, q => run.OrangeQuantity = update ? run.OrangeQuantity + q : q }, { LiquidTypes.LightOrange, q => run.LightOrangeQuantity = update ? run.LightOrangeQuantity + q : q }, { LiquidTypes.Rubine, q => run.RubineQuantity = update ? run.RubineQuantity + q : q }, { LiquidTypes.LightRubine, q => run.LightRubineQuantity = update ? run.LightRubineQuantity + q : q }, { LiquidTypes.Navy, q => run.NavyQuantity = update ? run.NavyQuantity + q : q }, { LiquidTypes.Violet, q => run.VioletQuantity = update ? run.VioletQuantity + q : q } }; foreach (var liquidType in setters.Keys) { var quantityObj = quantities.SingleOrDefault(x => x.LiquidType == liquidType); var quantity = quantityObj?.Quantity ?? 0; setters[liquidType](quantity); } } #endregion #region Public Methods /// /// Starts the logger. /// public void Start() { IsStarted = true; } /// /// Stops the logger. /// public void Stop() { IsStarted = false; } /// /// Sets the head cleaning parameters. /// /// The machine. public void SetDefaultMachine(Machine machine) { _defaultMachine = machine; } #endregion #region Event Handlers private void Machine_PrintingFailed(object sender, PrintingFailedEventArgs e) { InsertJobRun(e, JobRunStatus.Failed, e.Exception); } private void Machine_PrintingAborted(object sender, PrintingEventArgs e) { InsertJobRun(e, JobRunStatus.Aborted, null); } private void Machine_PrintingCompleted(object sender, PrintingEventArgs e) { InsertJobRun(e, JobRunStatus.Completed, null); } private void Machine_PrintingStarted(object sender, PrintingEventArgs e) { _job = e.Job; _currentJobEvents = new List(); _startMachineStatus = MachineOperator.MachineStatus?.Clone(); _jobTicket = e.JobHandler.JobTicket; } private void MachineOperator_HeadCleaningEnded(object sender, HeadCleaningEndedEventArgs e) { InsertHeadCleaningJobRun(e); } #endregion } }