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 { private Action _cancelAction; private List _effectiveSegments; private String _lastStatusMessage; private int _last_unit; private bool _finalizing; private JobHandlerModes _mode; private double _last_progress; #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; #endregion #region Properties /// /// Gets a value indicating whether this handler job has been canceled. /// public bool IsCanceled { get; internal set; } /// /// 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() { } /// /// Initializes a new instance of the class. /// /// The cancel action. internal 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 = () => { IsCanceled = true; 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 < Job.NumberOfUnits; 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. internal void RaiseStatusReceived(JobStatus status) { InvalidateJobProgress(status); } /// /// Raises the failed event. /// /// The ex. internal void RaiseFailed(Exception ex) { Status.IsFailed = true; StatusChanged?.Invoke(this, Status); RaisePropertyChanged(nameof(Status)); Failed?.Invoke(this, ex); Stopped?.Invoke(this, new EventArgs()); } /// /// Raises the completed event. /// internal void RaiseCompleted() { Status.Segments.Last().Completed = true; Status.RemainingUnits = 0; Status.IsFinalizing = false; Status.IsCompleted = true; StatusChanged?.Invoke(this, Status); RaisePropertyChanged(nameof(Status)); Completed?.Invoke(this, new EventArgs()); Stopped?.Invoke(this, new EventArgs()); } /// /// Raises the canceled event. /// internal void RaiseCanceled() { Status.IsCanceled = true; StatusChanged?.Invoke(this, Status); 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 private void InvalidateJobProgress(JobStatus s) { bool invalidProgress = false; LogManager.Log($"Updating job progress {s.Progress}/{Status.TotalProgress}..."); 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; StatusChanged?.Invoke(this, Status); 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; StatusChanged?.Invoke(this, Status); //Segments Completion if (Status.CurrentUnit > _last_unit) { foreach (var segment in Status.CurrentUnitSegments) { segment.Started = false; segment.Completed = false; } if (Job.NumberOfUnits > 1) { UnitCompleted?.Invoke(this, _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; SegmentStarted?.Invoke(this, segment); } } if (Status.CurrentUnitProgress >= previousSegmentsLengthWithThis) { if (!segment.Completed) { segment.Completed = true; SegmentCompleted?.Invoke(this, segment); } if (i < Status.CurrentUnitSegments.Count - 1) { if (!Status.CurrentUnitSegments[i + 1].Started) { Status.CurrentUnitSegments[i + 1].Started = true; SegmentStarted?.Invoke(this, 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; Finalizing?.Invoke(this, new EventArgs()); } Status.CurrentUnitProgress += delta; Status.FinalizingProgress += delta; } } #endregion #region Public Methods /// /// Cancels the associated job. /// public void Cancel() { _cancelAction(); } #endregion } }