using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core; using Tango.Logging; using Tango.PMR.Printing; using static Tango.Integration.Operation.AdditionalJobConfiguration; namespace Tango.Integration.Operation { /// /// Represents a job handler. /// /// public class JobHandler : ExtendedObject { protected Action _cancelAction; protected List _effectiveSegments; protected String _lastStatusMessage; protected int _last_unit; protected bool _finalizing; protected JobHandlerModes _mode; protected double _last_progress; protected const int PROGRESS_REPORT_RANGE_METERS = 5; protected bool loggedContinueMessage; private DateTime _lastProgressLogDateTime; #region Events /// /// Occurs when a job status has been received. /// public event EventHandler StatusChanged; /// /// Occurs when the job has failed. /// public event EventHandler Failed; /// /// Occurs when the job has completed successfully. /// public event EventHandler Completed; /// /// Occurs when the job has been canceled. /// public event EventHandler Canceled; /// /// Occurs when the job has been canceled, failed or completed. /// public event EventHandler Stopped; /// /// Occurs when rolling the dryer buffer. /// public event EventHandler Finalizing; /// /// Occurs when a segment has started. /// public event EventHandler SegmentStarted; /// /// Occurs when a segment has completed. /// public event EventHandler SegmentCompleted; /// /// Occurs when a unit completes. /// public event EventHandler UnitCompleted; /// /// Occurs when the job is set to 1 segment per spool and it's time to replace the spool. /// public event EventHandler SpoolChangeRequired; /// /// Occurs when has changed. /// public event EventHandler CanCancelChanged; #endregion #region Properties private JobStatus _jobStatus; /// /// Gets or sets the current job status that was used to invalidate this handler. /// public JobStatus JobStatus { get { return _jobStatus; } set { _jobStatus = value; RaisePropertyChangedAuto(); } } /// /// Gets a value indicating whether this handler job has been canceled. /// public bool IsCanceled { get; internal set; } private bool _canCancel; /// /// Gets a value indicating whether the job can be canceled. /// public bool CanCancel { get { return _canCancel; } internal set { _canCancel = value; RaisePropertyChangedAuto(); CanCancelChanged?.Invoke(this, new EventArgs()); } } /// /// Gets the process parameters. /// public ProcessParametersTable ProcessParameters { get; private set; } private RunningJobStatus _Status; /// /// Gets the current status. /// public RunningJobStatus Status { get { return _Status; } internal set { _Status = value; RaisePropertyChangedAuto(); } } /// /// Gets the job. /// public Job Job { get; private set; } /// /// Gets the PMR job ticket. /// public JobTicket JobTicket { get; private set; } public ResumeConfiguration ResumeConfig { get; private set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public JobHandler() { CanCancel = true; } /// /// Initializes a new instance of the class. /// /// The cancel action. public JobHandler(Action cancelAction, Job job, JobTicket jobTicket, ProcessParametersTable processParameters, JobHandlerModes mode, ResumeConfiguration resumeConfig = null) : this() { _mode = mode; ProcessParameters = processParameters; Job = job; JobTicket = jobTicket; ResumeConfig = resumeConfig; foreach (var s in Job.Segments) { s.Started = false; s.Completed = false; s.RemainingTime = TimeSpan.FromSeconds(0); } _effectiveSegments = Job.EffectiveSegments.ToList(); _cancelAction = () => { cancelAction(); }; Status = new RunningJobStatus(); Status.TotalTime = job.GetEstimatedDuration(processParameters); Status.RemainingUnits = job.NumberOfUnits; Status.TotalProgress = Job.LengthIncludingNumberOfUnits + processParameters.DryerBufferLengthMeters; Status.FinalizingTotalProgress = processParameters.DryerBufferLengthMeters; Status.TotalProgressWithoutFinalization = Job.LengthIncludingNumberOfUnits; Status.Progress = 0; Status.RemainingTime = Status.TotalTime; Status.RemainingProgress = Status.TotalProgress; Status.CurrentUnitSegments = _effectiveSegments.ToList(); Status.SettingUpTotalProgress = processParameters.DryerBufferLengthMeters; if (resumeConfig != null && resumeConfig.GlobalStartPosition > 0) { //Status.SettingUpTotalProgress = resumeConfig.GlobalStartPosition; //Status.CurrentUnitProgress = ResumeConfig.FirstUnitStartPosition; } Status.TotalProgressMinusSettingUp = Job.LengthIncludingNumberOfUnits; Status.IsSettingUp = true; if (mode == JobHandlerModes.Finalization) { Status.CurrentUnitTotalProgress = Job.Length; } else { Status.CurrentUnitTotalProgress = Status.SettingUpTotalProgress; } if (Job.EnableInterSegment && Job.NumberOfUnits > 1) { Status.CurrentUnitSegments.Add(Job.CreateInterSegment(Job.InterSegmentLength)); } //Create all segments int segment_index = 1; for (int j = 0; j < Math.Max(Job.NumberOfUnits, 1); j++) { for (int i = 0; i < _effectiveSegments.Count; i++) { Segment seg = _effectiveSegments[i].Clone(); seg.EstimatedDuration = seg.GetEstimatedDuration(processParameters); seg.SegmentIndex = segment_index++; Status.Segments.Add(seg); } if (Job.EnableInterSegment) { var inter_segment = Job.CreateInterSegment(Job.InterSegmentLength); inter_segment.SegmentIndex = segment_index++; Status.Segments.Add(inter_segment); } } var last_segment = Status.Segments.Last(); if (last_segment.IsInterSegment) { Status.Segments.Remove(last_segment); } Status.CurrentSegment = Status.Segments.First(); } #endregion #region Internal Methods /// /// Raises the status received event. /// /// The status. public void RaiseStatusReceived(JobStatus status) { InvalidateJobProgress(status); } /// /// Raises the failed event. /// /// The ex. public void RaiseFailed(Exception ex) { LogManager.Log($"Job failed at position {Status.Progress}/{Status.TotalProgress}..."); Status.IsFailed = true; RaiseStatusChanged(); RaisePropertyChanged(nameof(Status)); Failed?.Invoke(this, ex); Stopped?.Invoke(this, new EventArgs()); } /// /// Raises the completed event. /// public void RaiseCompleted() { //This will compensate on any missing progress from Shlomo, but also will tell the wrong progress if job is really completed with a large progress mistake. // Might be worth to compensate only on small drifts like the below (ProgressMinusSettingsUp)... //InvalidateJobProgress(new JobStatus() //{ // Progress = Status.TotalProgress, // CurrentSegmentIndex = 0, //}); LogManager.Log($"Job completed at position {Status.Progress}/{Status.TotalProgress}..."); //If drift is smaller than 10cm auto correct it. if (Math.Abs(Status.TotalProgressMinusSettingUp - Status.ProgressMinusSettingUp) < 0.1) { LogManager.Log($"Job completed with a small drift in the progress minus setting up calculation. ({Status.ProgressMinusSettingUp}/{Status.TotalProgressMinusSettingUp}). Compensating..."); Status.ProgressMinusSettingUp = Status.TotalProgressMinusSettingUp; } //If the overall progress is correct? Fix the minus setting up. There is a problem with the delta calc! else if (Status.Progress == Status.TotalProgress) { LogManager.Log($"Job completed with a small drift in the progress minus setting up calculation but the overall progress seems OK. ({Status.ProgressMinusSettingUp}/{Status.TotalProgressMinusSettingUp}). Compensating..."); Status.ProgressMinusSettingUp = Status.TotalProgressMinusSettingUp; } Status.Segments.Last().Completed = true; Status.RemainingUnits = 0; Status.IsFinalizing = false; Status.IsCompleted = true; RaiseStatusChanged(); RaisePropertyChanged(nameof(Status)); Completed?.Invoke(this, new EventArgs()); Stopped?.Invoke(this, new EventArgs()); } /// /// Raises the canceled event. /// public void RaiseCanceled() { LogManager.Log($"Job canceled at position {Status.Progress}/{Status.TotalProgress}..."); Status.IsCanceled = true; RaiseStatusChanged(); RaisePropertyChanged(nameof(Status)); Canceled?.Invoke(this, new EventArgs()); Stopped?.Invoke(this, new EventArgs()); } /// /// Raises the spool change required event. /// internal void RaiseSpoolChangeRequired(Action confirmAction, Action abortAction) { SpoolChangeRequired?.Invoke(this, new SpoolChangeRequiredEventArgs(confirmAction, abortAction) { CurrentSegment = Status.CurrentSegment.SegmentIndex, TotalSegments = Status.Segments.Count, }); } #endregion #region Private Methods protected virtual void InvalidateJobProgress(JobStatus s) { JobStatus = s; if (DateTime.UtcNow > _lastProgressLogDateTime.AddSeconds(5)) { _lastProgressLogDateTime = DateTime.UtcNow; if (LogManager.Categories.Exists(x => x == LogCategory.Debug)) { LogManager.Log($"Updating job progress {s.Progress}/{Status.TotalProgress}..."); } else { if (s.Progress <= PROGRESS_REPORT_RANGE_METERS || s.Progress >= Status.TotalProgress - PROGRESS_REPORT_RANGE_METERS) { LogManager.Log($"Updating job progress {s.Progress}/{Status.TotalProgress}..."); } else if (!loggedContinueMessage) { loggedContinueMessage = true; LogManager.Log($"Progress logging will continue {PROGRESS_REPORT_RANGE_METERS} meters before completion..."); } } } if (s.Progress < 0) { LogManager.Log($"Invalid job progress received '{s.Progress}'.", LogCategory.Error); return; } if (s.Progress > Status.TotalProgress) { LogManager.Log($"Invalid job progress received '{s.Progress}' while total progress is '{Status.TotalProgress}'.", LogCategory.Error); return; } if (s.Progress < _last_progress) { LogManager.Log($"Invalid job progress received '{s.Progress}' while last progress was '{_last_progress}'."); } _last_progress = s.Progress; List unit_segments = new List(); Status.Progress = s.Progress; Status.RemainingTime = Status.TotalTime - Job.TranslateProgressToTime(Status.Progress, ProcessParameters); Status.RemainingProgress = Status.TotalProgress - Status.Progress; if ((s.Progress < Status.SettingUpTotalProgress) || (Status.SettingUpProgress < Status.SettingUpTotalProgress)) { Status.SettingUpProgress = Math.Min(s.Progress, this.Status.SettingUpTotalProgress); Status.IsSettingUp = true; //LogManager.Log($" Status.IsSettingUp = true , Status.SettingUpProgress = {Status.SettingUpProgress}' Status.SettingUpTotalProgress = {Status.SettingUpTotalProgress}."); } if (s.Progress >= ProcessParameters.DryerBufferLengthMeters)//Status.SettingUpTotalProgress) { if (Status.IsSettingUp && Status.Progress > 0) { Status.IsSettingUp = false; //LogManager.Log($" Status.IsSettingUp = false , Status.SettingUpProgress = {Status.SettingUpProgress}'."); } if (ResumeConfig != null && ResumeConfig.GlobalStartPosition > 0) { Status.ProgressMinusSettingUp = s.Progress - ProcessParameters.DryerBufferLengthMeters; //LogManager.Log($" JOB HANDLER Status.ProgressMinusSettingUp {Status.ProgressMinusSettingUp} progress = {s.Progress}"); } else { Status.ProgressMinusSettingUp = s.Progress - this.Status.SettingUpTotalProgress; } } int units = (int)Math.Max(Job.NumberOfUnits, 1); if (s.Progress < Job.LengthIncludingNumberOfUnits || _mode == JobHandlerModes.SettingUp) { Status.ProgressWithoutFinalization = s.Progress; unit_segments = _effectiveSegments.ToList(); double previousUnitsLengthWithoutThis = 0.0; int currentUnit = Status.CurrentUnit; double currentUnitProgress = Status.CurrentUnitProgress; double jobLength = Job.Length; for (int index = 0; index < units; ++index) { currentUnit = index; double unitLength = !Job.EnableInterSegment || index >= units - 1 ? jobLength : jobLength + Job.InterSegmentLength; if (_mode == JobHandlerModes.Finalization) { if (s.Progress < unitLength + previousUnitsLengthWithoutThis) { currentUnitProgress = s.Progress - previousUnitsLengthWithoutThis; break; } } else if (ResumeConfig != null && ResumeConfig.GlobalStartPosition > 0) { if (!Status.IsSettingUp && s.Progress <= previousUnitsLengthWithoutThis + unitLength + ProcessParameters.DryerBufferLengthMeters) { currentUnitProgress = s.Progress - previousUnitsLengthWithoutThis - ProcessParameters.DryerBufferLengthMeters; //LogManager.Log($" JOB HANDLER currentUnitProgress before ={currentUnitProgress} progress = {s.Progress}"); break; } } else if (s.Progress <= previousUnitsLengthWithoutThis + unitLength + Status.SettingUpProgress) { if (!Status.IsSettingUp) { currentUnitProgress = s.Progress - previousUnitsLengthWithoutThis - this.Status.SettingUpProgress; break; } break; } previousUnitsLengthWithoutThis += unitLength; } Status.CurrentUnit = currentUnit; Status.CurrentUnitProgress = currentUnitProgress; //LogManager.Log($" JOB HANDLER CurrentUnit {Status.CurrentUnit} currentUnitProgress {Status.CurrentUnitProgress} "); Status.RemainingUnits = this.Job.NumberOfUnits - this.Status.CurrentUnit; if (Job.EnableInterSegment && Job.NumberOfUnits > 1 && Status.RemainingUnits > 1) { unit_segments.Add(Job.CreateInterSegment(Job.InterSegmentLength)); } if (unit_segments.Count != Status.CurrentUnitSegments.Count) { Status.CurrentUnitSegments = unit_segments; } Status.CurrentUnitTotalProgress = Status.RemainingUnits > 1 && Job.EnableInterSegment ? Job.Length + (Job.InterSegmentLength) : Job.Length; if (s.Message != _lastStatusMessage) { Status.Message = s.Message; } _lastStatusMessage = s.Message; RaiseStatusChanged(); //Segments Completion if (Status.CurrentUnit > _last_unit) { foreach (var segment in Status.CurrentUnitSegments) { segment.Started = false; segment.Completed = false; } if (Job.NumberOfUnits > 1) { RaiseUnitCompleted(_last_unit); } } _last_unit = Status.CurrentUnit; for (int i = 0; i < Status.CurrentUnitSegments.Count; i++) { Segment segment = Status.CurrentUnitSegments[i]; double previousSegmentsLengthWithThis = Status.CurrentUnitSegments.Take(i + 1).Sum(x => x.LengthWithFactor); TimeSpan segmentsDuration = Job.TranslateProgressToTime(previousSegmentsLengthWithThis, ProcessParameters); TimeSpan segmentRemainingTime = segmentsDuration - Job.TranslateProgressToTime(Status.CurrentUnitProgress, ProcessParameters); if (i == 0 && Status.CurrentUnitProgress > 0) { if (!segment.Started) { segment.Started = true; RaiseSegmentStarted(segment); } } if (Status.CurrentUnitProgress >= previousSegmentsLengthWithThis) { if (!segment.Completed) { segment.Completed = true; RaiseSegmentCompleted(segment); } if (i < Status.CurrentUnitSegments.Count - 1) { if (!Status.CurrentUnitSegments[i + 1].Started) { Status.CurrentUnitSegments[i + 1].Started = true; RaiseSegmentStarted(Status.CurrentUnitSegments[i + 1]); } } } if (segment.Started && !segment.Completed) { segment.RemainingTime = segmentRemainingTime; } } //Set Segment Completion for All Segments List for (int i = 0; i < Status.Segments.Count; i++) { Segment segment = Status.Segments[i]; double previousSegmentsLengthWithThis = Status.Segments.Take(i + 1).Sum(x => x.LengthWithFactor); TimeSpan segmentsDuration = Job.TranslateProgressToTime(previousSegmentsLengthWithThis, ProcessParameters); TimeSpan segmentRemainingTime = segmentsDuration - Job.TranslateProgressToTime(Status.Progress, ProcessParameters); // segment.Progress = Math.Min(Math.Max((previousSegmentsLengthWithThis - segment.Length - Status.Progress) * -1, 0), segment.Length); segment.Progress = Math.Min(Math.Max((previousSegmentsLengthWithThis - segment.Length - Status.ProgressMinusSettingUp) * -1, 0), segment.Length); if (i == 0 && Status.Progress > 0) { if (!segment.Started) { segment.Started = true; Status.CurrentSegment = segment; } } //if (Status.Progress >= previousSegmentsLengthWithThis) if (Status.ProgressMinusSettingUp >= previousSegmentsLengthWithThis) { if (!segment.Completed) { segment.Completed = true; } if (i < Status.Segments.Count - 1) { if (!Status.Segments[i + 1].Started) { Status.Segments[i + 1].Started = true; Status.CurrentSegment = Status.Segments[i + 1]; } } } if (segment.Started && !segment.Completed) { segment.RemainingTime = segmentRemainingTime; } } } else { //Finalizing if (!_finalizing) { _finalizing = true; Status.IsFinalizing = true; var last_Segment = _effectiveSegments.Last().Clone(); last_Segment.Length = ProcessParameters.DryerBufferLengthMeters; Status.CurrentUnitSegments = new List { last_Segment }; Status.CurrentUnitTotalProgress = last_Segment.Length; Status.CurrentUnitProgress = 0; Status.ProgressWithoutFinalization = Status.TotalProgressWithoutFinalization; RaiseFinalizing(); } Status.CurrentUnitProgress = s.Progress - Job.LengthIncludingNumberOfUnits; Status.FinalizingProgress = s.Progress - Status.TotalProgressWithoutFinalization; } } #endregion #region Protected Methods protected void RaiseStatusChanged() { StatusChanged?.Invoke(this, Status); } protected void RaiseSegmentStarted(Segment segment) { if (segment.IsInterSegment) { LogManager.Log($"Inter Segment started."); } else { LogManager.Log($"Segment {segment.SegmentIndex} of unit {Status.CurrentUnit + 1} started..."); } SegmentStarted?.Invoke(this, segment); } protected void RaiseSegmentCompleted(Segment segment) { if (segment.IsInterSegment) { LogManager.Log($"Inter Segment completed."); } else { LogManager.Log($"Segment {segment.SegmentIndex} of unit {Status.CurrentUnit + 1} completed."); } SegmentCompleted?.Invoke(this, segment); } protected void RaiseUnitCompleted(int unit) { LogManager.Log($"Unit {unit + 1} completed..."); UnitCompleted?.Invoke(this, unit); } protected void RaiseFinalizing() { LogManager.Log($"Finalizing..."); Finalizing?.Invoke(this, new EventArgs()); } #endregion #region Public Methods /// /// Cancels the associated job. /// public void Cancel() { _cancelAction(); } #endregion } }