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.Core; using Tango.Logging; using Tango.PMR.Printing; 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; #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; } #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) : this() { _mode = mode; ProcessParameters = processParameters; Job = job; JobTicket = jobTicket; 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; 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; // bool invalidProgress = false; // if (_last_progress != s.Progress) // { // 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); // invalidProgress = true; // } // if (s.Progress > Status.TotalProgress) // { // LogManager.Log($"Invalid job progress received '{s.Progress}' while total progress is '{Status.TotalProgress}'.", LogCategory.Error); // invalidProgress = true; // } // if (s.Progress < _last_progress) // { // LogManager.Log($"Invalid job progress received '{s.Progress}' while last progress was '{_last_progress}'."); // invalidProgress = true; // } // if (invalidProgress) // { // return; // } // _last_progress = s.Progress; // //Job Status // if (IsCanceled) // { // Status.IsCanceled = IsCanceled; // RaiseStatusChanged(); // return; // } // List unit_segments = new List(); // double delta = s.Progress - Status.Progress; // Status.Progress = s.Progress; // Status.RemainingTime = Status.TotalTime - Job.TranslateProgressToTime(Status.Progress, ProcessParameters); // Status.RemainingProgress = Status.TotalProgress - Status.Progress; // if (Status.SettingUpProgress < Status.SettingUpTotalProgress) // { // Status.SettingUpProgress += delta; // } // else // { // if (Status.IsSettingUp && Status.Progress > 0) // { // Status.IsSettingUp = false; // } // Status.ProgressMinusSettingUp += delta; // } // if (s.Progress < Job.LengthIncludingNumberOfUnits || _mode == JobHandlerModes.SettingUp) // { // Status.ProgressWithoutFinalization += delta; // unit_segments = _effectiveSegments.ToList(); // 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 (_mode == JobHandlerModes.Finalization) // { // Status.CurrentUnitProgress += delta; // } // else // { // if (!Status.IsSettingUp) // { // Status.CurrentUnitProgress += delta; // } // } // if (Status.CurrentUnitProgress >= Status.CurrentUnitTotalProgress) // { // Status.CurrentUnitProgress = 0; // Status.CurrentUnit++; // } // Status.RemainingUnits = Job.NumberOfUnits - Status.CurrentUnit; // if (s.Message != _lastStatusMessage && s.Message != String.Empty) // { // Status.Message = s.Message; // } // else // { // Status.Message = null; // } // _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); // if (i == 0 && Status.Progress > 0) // { // if (!segment.Started) // { // segment.Started = true; // Status.CurrentSegment = segment; // } // } // if (Status.Progress >= 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 += delta; // Status.FinalizingProgress += delta; // } //} protected virtual void InvalidateJobProgress(JobStatus s) { JobStatus = s; if (_last_progress != s.Progress) { 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; } if (s.Progress >= Status.SettingUpTotalProgress) { if (Status.IsSettingUp && Status.Progress > 0) { Status.IsSettingUp = false; } 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(); Status.CurrentUnitProgress = 0.0; double previousUnitsLengthWithoutThis = 0.0; for (int index = 0; index < units; ++index) { Status.CurrentUnit = index; double unitLength = !Job.EnableInterSegment || index >= units - 1 ? Job.Length : Job.Length + Job.InterSegmentLength; if (_mode == JobHandlerModes.Finalization) { if (s.Progress < unitLength + previousUnitsLengthWithoutThis) { Status.CurrentUnitProgress = s.Progress - previousUnitsLengthWithoutThis; break; } } else if (s.Progress <= previousUnitsLengthWithoutThis + unitLength + Status.SettingUpProgress) { if (!Status.IsSettingUp) { Status.CurrentUnitProgress = s.Progress - previousUnitsLengthWithoutThis - this.Status.SettingUpProgress; break; } break; } previousUnitsLengthWithoutThis += unitLength; } 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 && s.Message != String.Empty) { Status.Message = s.Message; } else { Status.Message = null; } _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); if (i == 0 && Status.Progress > 0) { if (!segment.Started) { segment.Started = true; Status.CurrentSegment = segment; } } if (Status.Progress >= 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 } }