aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.Integration/Operation/JobHandler.cs
blob: fce815bc378cdc2fba42c8aed9079854754252ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
using LibGit2Sharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.Core;

namespace Tango.Git
{
    public class GitRepositoryManager : IDisposable
    {
        private Repository _repo;
        private String _pat;
        private String _userEmail;
        private String _localFolder;

        public event EventHandler<TangoProgressChangedEventArgs<double>> Progress;

        public GitRepositoryManager(String localFolder, String userEmail, String personalAccessToken)
        {
            _pat = personalAccessToken;
            _userEmail = userEmail;
            _localFolder = localFolder;
            _repo = new Repository(_localFolder);
        }

        public void CreatePushTag(String name, String description, String userName)
        {
            var tag = _repo.ApplyTag(name, new Signature(userName, _userEmail, DateTime.Now), description);

            _repo.Network.Push(_repo.Network.Remotes.First(), tag.CanonicalName, new PushOptions()
            {
                CredentialsProvider = new LibGit2Sharp.Handlers.CredentialsHandler(CredentialsHandlerMethod),
                OnPushTransferProgress = new LibGit2Sharp.Handlers.PushTransferProgressHandler(PushTagProgressHandlerMethod)
            });
        }

        public void Commit(String message)
        {
            ExecuteGitProcess($"commit -a -m \"{message}\"");
        }

        public void Pull()
        {
            ExecuteGitProcess($"pull");
        }

        public void Push()
        {
            ExecuteGitProcess($"push");
        }

        public void Sync()
        {
            Pull();
            Push();
        }

        private bool PushTagProgressHandlerMethod(int current, int total, long bytes)
        {
            //TODO: Implement via TangoProgress & event...
            RaiseProgress("Pushing Tag...", false, 0, total);
            return true;
        }

        public List<GitCommit> GetOutgoingCommits()
        {
            List<GitCommit> commits = new List<GitCommit>();

            var branch = _repo.Head.TrackedBranch;

            if (branch.TrackingDetails.AheadBy != null)
            {
                foreach (var commit in _repo.Commits.Take(branch.TrackingDetails.AheadBy.Value))
                {
                    commits.Add(new GitCommit()
                    {
                        Message = commit.Message
                    });
                }
            }

            return commits;
        }

        public List<GitCommit> GetIncomingCommits()
        {
            List<GitCommit> commits = new List<GitCommit>();

            var trackingBranch = _repo.Head.TrackedBranch;
            var log = _repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = trackingBranch.Tip.Id, ExcludeReachableFrom = _repo.Head.Tip.Id });

            var count = log.Count();

            foreach (var commit in log)
            {
                commits.Add(new GitCommit()
                {
                    Message = commit.Message
                });
            }

            return commits;
        }

        public List<GitFile> GetChanges()
        {
            List<GitFile> files = new List<GitFile>();

            var status = _repo.RetrieveStatus();

       
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
{
    /// <summary>
    /// Represents a <see cref="MachineOperator"/> job handler.
    /// </summary>
    /// <seealso cref="Tango.Core.ExtendedObject" />
    public class JobHandler : ExtendedObject
    {
        protected Action _cancelAction;
        protected List<Segment> _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

        /// <summary>
        /// Occurs when a job status has been received.
        /// </summary>
        public event EventHandler<RunningJobStatus> StatusChanged;

        /// <summary>
        /// Occurs when the job has failed.
        /// </summary>
        public event EventHandler<Exception> Failed;

        /// <summary>
        /// Occurs when the job has completed successfully.
        /// </summary>
        public event EventHandler Completed;

        /// <summary>
        /// Occurs when the job has been canceled.
        /// </summary>
        public event EventHandler Canceled;

        /// <summary>
        /// Occurs when the job has been canceled, failed or completed.
        /// </summary>
        public event EventHandler Stopped;

        /// <summary>
        /// Occurs when rolling the dryer buffer.
        /// </summary>
        public event EventHandler Finalizing;

        /// <summary>
        /// Occurs when a segment has started.
        /// </summary>
        public event EventHandler<Segment> SegmentStarted;

        /// <summary>
        /// Occurs when a segment has completed.
        /// </summary>
        public event EventHandler<Segment> SegmentCompleted;

        /// <summary>
        /// Occurs when a unit completes.
        /// </summary>
        public event EventHandler<int> UnitCompleted;

        /// <summary>
        /// Occurs when the job is set to 1 segment per spool and it's time to replace the spool.
        /// </summary>
        public event EventHandler<SpoolChangeRequiredEventArgs> SpoolChangeRequired;

        /// <summary>
        /// Occurs when <see cref="CanCancel"/> has changed.
        /// </summary>
        public event EventHandler CanCancelChanged;

        #endregion

        #region Properties

        private JobStatus _jobStatus;
        /// <summary>
        /// Gets or sets the current job status that was used to invalidate this handler.
        /// </summary>
        public JobStatus JobStatus
        {
            get { return _jobStatus; }
            set { _jobStatus = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets a value indicating whether this handler job has been canceled.
        /// </summary>
        public bool IsCanceled { get; internal set; }

        private bool _canCancel;
        /// <summary>
        /// Gets a value indicating whether the job can be canceled.
        /// </summary>
        public bool CanCancel
        {
            get { return _canCancel; }
            internal set { _canCancel = value; RaisePropertyChangedAuto(); CanCancelChanged?.Invoke(this, new EventArgs()); }
        }

        /// <summary>
        /// Gets the process parameters.
        /// </summary>
        public ProcessParametersTable ProcessParameters { get; private set; }

        private RunningJobStatus _Status;
        /// <summary>
        /// Gets the current status.
        /// </summary>
        public RunningJobStatus Status
        {
            get { return _Status; }
            internal set { _Status = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets the job.
        /// </summary>
        public Job Job { get; private set; }

        /// <summary>
        /// Gets the PMR job ticket.
        /// </summary>
        public JobTicket JobTicket { get; private set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="JobHandler"/> class.
        /// </summary>
        public JobHandler()
        {
            CanCancel = true;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="JobHandler"/> class.
        /// </summary>
        /// <param name="cancelAction">The cancel action.</param>
        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

        /// <summary>
        /// Raises the status received event.
        /// </summary>
        /// <param name="status">The status.</param>
        public void RaiseStatusReceived(JobStatus status)
        {
            InvalidateJobProgress(status);
        }

        /// <summary>
        /// Raises the failed event.
        /// </summary>
        /// <param name="ex">The ex.</param>
        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());
        }

        /// <summary>
        /// Raises the completed event.
        /// </summary>
        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());
        }

        /// <summary>
        /// Raises the canceled event.
        /// </summary>
        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());
        }

        /// <summary>
        /// Raises the spool change required event.
        /// </summary>
        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<Segment> unit_segments = new List<Segment>();
        //    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<Segment> { 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<Segment> unit_segments = new List<Segment>();

            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<Segment> { 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

        /// <summary>
        /// Cancels the associated job.
        /// </summary>
        public void Cancel()
        {
            _cancelAction();
        }

        #endregion
    }
}