aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs
blob: c73cc6196c1d00810f9f43e6b82f106191c4bff1 (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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
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 System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.BL;
using Tango.BL.Entities;
using Tango.Core.Commands;
using Tango.MachineStudio.Common.EventLogging;
using Tango.MachineStudio.Common.Messages;
using Tango.MachineStudio.Common.Notifications;
using Tango.MachineStudio.Common.StudioApplication;
using Tango.MachineStudio.Logging.Navigation;
using Tango.MachineStudio.Logging.Views;
using Tango.SharedUI;
using System.Data.Entity;

namespace Tango.MachineStudio.Logging.ViewModels
{
    public class EventsViewVM : ViewModel
    {
        private INotificationProvider _notification;
        private IStudioApplicationManager _application;
        private IEventLogger _eventLogger;
        private ObservableCollection<MachinesEvent> _realTimeEvents;
        private LoggingNavigationManager _navigation;
        private bool _dialog_shown;
        private ObservablesContext _db;
        private List<MachinesEvent> _history_events;

        private Machine _selectedMachine;
        public Machine SelectedMachine
        {
            get { return _selectedMachine; }
            set
            {
                if (_selectedMachine != value)
                {
                    _selectedMachine = value; RaisePropertyChangedAuto(); OnSelectedMachineChanged();
                }
            }
        }

        private ObservableCollection<MachinesEvent> _events;
        public ObservableCollection<MachinesEvent> Events
        {
            get { return _events; }
            set { _events = value; RaisePropertyChangedAuto(); }
        }

        private MachinesEvent _selectedEvent;
        public MachinesEvent SelectedEvent
        {
            get { return _selectedEvent; }
            set { _selectedEvent = value; RaisePropertyChangedAuto(); OnSelectedEventChanged(); }
        }

        private ObservableCollection<DateTime> _dates;
        public ObservableCollection<DateTime> Dates
        {
            get { return _dates; }
            set { _dates = value; RaisePropertyChangedAuto(); }
        }

        private DateTime _selectedDate;
        public DateTime SelectedDate
        {
            get { return _selectedDate; }
            set { _selectedDate = value; RaisePropertyChangedAuto(); OnSelectedDateChanged(); }
        }

        private DateTime _minDate;
        public DateTime MinDate
        {
            get { return _minDate; }
            set { _minDate = value; RaisePropertyChangedAuto(); }
        }

        private DateTime _maxDate;
        public DateTime MaxDate
        {
            get { return _maxDate; }
            set { _maxDate = value; RaisePropertyChangedAuto(); }
        }

        private bool _isRealTime;
        public bool IsRealTime
        {
            get { return _isRealTime; }
            set { _isRealTime = value; RaisePropertyChangedAuto(); OnSelectedDateChanged(); }
        }

        private TimelineViewVM _timelineViewVM;

        public TimelineViewVM TimelineViewVM
        {
            get { return _timelineViewVM; }
            set { _timelineViewVM = value; RaisePropertyChangedAuto(); }
        }

        public RelayCommand<MachinesEvent> DisplayTimelineCommand { get; set; }

        public RelayCommand NavigateToEventsCommand { get; set; }

        public RelayCommand NavigateToHomeCommand { get; set; }

        public EventsViewVM(INotificationProvider notification, IEventLogger eventLogger, IStudioApplicationManager application, LoggingNavigationManager navigation)
        {
            TimelineViewVM = new TimelineViewVM(notification);

            _navigation = navigation;
            _application = application;
            _notification = notification;
            _eventLogger = eventLogger;
            _realTimeEvents = new ObservableCollection<MachinesEvent>();
            _eventLogger.NewLog += _eventLogger_NewLog;

            DisplayTimelineCommand = new RelayCommand<MachinesEvent>(DisplayTimeline);
            NavigateToEventsCommand = new RelayCommand(() => _navigation.NavigateTo(LoggingNavigationView.EventsView));
            NavigateToHomeCommand = new RelayCommand(() => _navigation.NavigateTo(LoggingNavigationView.HomeView));
        }

        private void _eventLogger_NewLog(object sender, MachinesEvent machineEvent)
        {
            InvokeUI(() =>
            {
                _realTimeEvents.Insert(0, machineEvent);
            });
        }

        private async void OnSelectedMachineChanged()
        {
            if (SelectedMachine != null)
            {
                using (_notification.PushTaskItem("Loading machine events..."))
                {
                    try
                    {
                        await Task.Factory.StartNew(() =>
                        {
                            _db = ObservablesContext.CreateDefault();

                            _db.EventTypes.Load();

                            DateTime now = DateTime.UtcNow.AddMonths(-1);

                            _history_events = _db.MachinesEvents.Where(x => x.MachineGuid == SelectedMachine.Guid && x.DateTime > now).Include(x => x.User).Include(x => x.User.Contact).Include(x => x.Machine).ToList();

                            Dates = new ObservableCollection<DateTime>();

                            foreach (var day in _history_events.GroupBy(x => x.DateTime.DayOfYear).Select(x => x.First().DateTime).OrderByDescending(x => x))
                            {
                                Dates.Add(day);
                            }

                            if (Dates.Count > 0)
                            {
                                MinDate = Dates.Min();
                                MaxDate = Dates.Max();
                            }

                            SelectedDate = Dates.FirstOrDefault();
                        });
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error loading machine events.");
                        _notification.ShowError($"An error occurred while trying to load the selected machine events.\n{ex.FlattenMessage()}");
                    }
                }
            }
        }

        private void OnSelectedDateChanged()
        {
            if (SelectedDate != null && SelectedMachine != null)
            {
                if (IsRealTime)
                {
                    Events = _realTimeEvents;
                }
                else if (_history_events != null)
                {
                    Events = _history_events.Where(x => x.DateTime.DayOfYear == SelectedDate.Date.DayOfYear).OrderByDescending(x => x.DateTime).ToObservableCollection();
                }
            }
        }

        private void OnSelectedEventChanged()
        {
            if (SelectedEvent != null && SelectedEvent.Type != BL.Enumerations.EventTypes.APPLICATION_STARTED && !_dialog_shown)
            {
                _dialog_shown = true;
                _notification.ShowModalDialog<EventDetailsViewVM, EventDetailsView>(new EventDetailsViewVM(SelectedEvent), (x) =>
                {

                }, () =>
                {
                    _dialog_shown = false;
                });
            }
        }

        private void DisplayTimeline(MachinesEvent ev)
        {
            var events = Events.OrderBy(x => x.DateTime).SkipWhile(x => x != ev).Skip(1).TakeWhile(x => x.DateTime > ev.DateTime && x.Type != BL.Enumerations.EventTypes.APPLICATION_STARTED).ToObservableCollection();
            events.Insert(0, ev);

            TimelineViewVM.Initialize(events.ToList());

            _navigation.NavigateTo(LoggingNavigationView.TimelineWrapperView);
        }
    }
}
50'>1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726
using FluentFTP;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tango.BL;
using Tango.BL.Entities;
using Tango.Core;
using Tango.Core.DB;
using Tango.Core.ExtensionMethods;
using Tango.Core.Helpers;
using Tango.Core.IO;
using Tango.Integration.Operation;
using Tango.Integration.Upgrade;
using Tango.Logging;
using Tango.PMR.Synchronization;
using Tango.PPC.Common.Application;
using Tango.PPC.Common.Connection;
using Tango.PPC.Common.Navigation;
using Tango.PPC.Common.Publish;
using Tango.PPC.Common.UpdatePackages;
using Tango.PPC.Common.Web;
using Tango.Settings;
using Tango.SharedUI.Helpers;
using Tango.SQLExaminer;
using Tango.Transport.Web;
using System.Data.Entity;
using Tango.PPC.Common.ExternalBridge;
using Tango.Integration.ExternalBridge;
using Tango.BL.DTO;
using Tango.PPC.Shared.Updates;
using Tango.PPC.Shared.RemoteUpgrade;
using Tango.Core.Threading;

namespace Tango.PPC.Common.MachineUpdate
{
    public class MachineUpdateManager : ExtendedObject, IMachineUpdateManager, IExternalBridgeRequestHandler
    {
        private IPPCApplicationManager _app_manager;
        private IMachineProvider _machineProvider;
        private IPackageRunner _packageRunner;
        private PPCWebClient _client;
        private List<LogItemBase> _logs;
        private System.Timers.Timer _checkForUpdateTimer;
        private bool _isUpdating;
        private PPCSettings _settings;
        private DateTime _updateStartDate;

        #region Events

        /// <summary>
        /// Occurs when an application update is available.
        /// </summary>
        public event EventHandler<CheckForUpdateResponse> UpdateAvailable;

        /// <summary>
        /// Occurs when there is a text log message available.
        /// </summary>
        public event EventHandler<string> ProgressLog;

        /// <summary>
        /// Occurs when the update has made some progress.
        /// </summary>
        public event EventHandler<MachineUpdateProgress> Progress;

        #endregion

        #region Properties

        private MachineUpdateProgress _status;
        /// <summary>
        /// Gets the current machine update progress status.
        /// </summary>
        public MachineUpdateProgress Status
        {
            get { return _status; }
            private set { _status = value; RaisePropertyChangedAuto(); }
        }

        private bool _autoCheckForUpdates;
        /// <summary>
        /// Gets or sets a value indicating whether to automatically check for new application updates.
        /// </summary>
        public bool EnableAutoCheckForUpdates
        {
            get { return _autoCheckForUpdates; }
            set { _autoCheckForUpdates = value; RaisePropertyChangedAuto(); }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MachineUpdateManager"/> class.
        /// </summary>
        /// <param name="applicationManager">The application manager.</param>
        public MachineUpdateManager(PPCWebClient ppcWebClient, IPPCApplicationManager applicationManager, IMachineProvider machineProvider, IPackageRunner packageRunner, IPPCExternalBridgeService externalBridge)
        {
            _client = ppcWebClient;
            _machineProvider = machineProvider;
            _app_manager = applicationManager;
            _app_manager.ApplicationReady += _app_manager_ApplicationReady;
            _packageRunner = packageRunner;
            _packageRunner.PackageProgress += _packageRunner_PackageProgress;

            _logs = new List<LogItemBase>();
            LogManager.NewLog += LogManager_NewLog;

            _settings = SettingsManager.Default.GetOrCreate<PPCSettings>();

            _checkForUpdateTimer = new System.Timers.Timer(_settings.AutoUpdateCheckInterval.TotalMilliseconds);
            _checkForUpdateTimer.Elapsed += _checkForUpdateTimer_Elapsed;
            _checkForUpdateTimer.Stop();

            externalBridge.RegisterRequestHandler(this);
        }

        #endregion

        #region Event Handlers

        private void _app_manager_ApplicationReady(object sender, EventArgs e)
        {
            _checkForUpdateTimer.Start();
        }

        private void _packageRunner_PackageProgress(object sender, PackageProgressEventArgs e)
        {
            UpdateProgress(e.PackageName, e.Message, e.IsIntermediate, e.Progress, e.Total);
        }

        private void LogManager_NewLog(object sender, LogItemBase e)
        {
            if (_isUpdating)
            {
                _logs.Add(e);
            }
        }

        #endregion

        #region Private Methods

        private Task Login(String serialNumber)
        {
            return _client.Login(new LoginRequest()
            {
                Mode = LoginMode.Machine,
                SerialNumber = serialNumber,
            });
        }

        private async void OnFailed(Exception ex, TaskCompletionSource<MachineUpdateResult> completionSource, DownloadUpdateResponse response, bool performDatabaseRollback, String dbBackupFile, String backupsFolder, String tempDbName, Tango.Core.DataSource localDataSource, String tempUpdatePackageFolder = null, PublishInfo tupPublishInfo = null)
        {
            LogManager.Log(ex, "An error occurred in machine update.");

            await Task.Factory.StartNew(() =>
            {

                if (performDatabaseRollback)
                {
                    LogManager.Log("Rolling back database changes...");

                    using (DbManager db = DbManager.FromDataSource(localDataSource))
                    {
                        try
                        {
                            UpdateProgress("Rollback", "Rolling back database changes...");
                            db.Restore(localDataSource.Catalog, dbBackupFile);
                            LogManager.Log("Database restored successfully.");
                        }
                        catch (Exception e)
                        {
                            LogManager.Log(e, "Could not rollback the database.");
                        }
                        finally
                        {
                            try
                            {
                                File.Delete(dbBackupFile);
                            }
                            catch { }
                        }
                    }
                }

                if (tempDbName != null)
                {
                    try
                    {
                        LogManager.Log($"Removing temporary database '{tempDbName}'...");
                        using (DbManager dbManager = DbManager.FromDataSource(localDataSource))
                        {
                            dbManager.SetOffline(tempDbName);
                            dbManager.SetOnline(tempDbName);
                            dbManager.Delete(tempDbName);
                        }
                    }
                    catch (Exception exx)
                    {
                        LogManager.Log(exx, "Error removing temporary database.");
                    }
                }

                try
                {
                    Directory.Delete(backupsFolder, true);
                }
                catch (Exception ee)
                {
                    LogManager.Log(ee, $"Error deleting backups folder '{backupsFolder}'.");
                }

                if (tempUpdatePackageFolder != null)
                {
                    try
                    {
                        Directory.Delete(tempUpdatePackageFolder, true);
                    }
                    catch (Exception eee)
                    {
                        LogManager.Log(eee, "Error removing temporary package folder.");
                    }
                }

            });

            completionSource.SetException(ex);

            String logs = GetLogsStringAndClear();

            if (response != null)
            {
                try
                {
                    var result = await _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest()
                    {
                        Token = response.NotifyCompletedToken,
                        Status = BL.Enumerations.TangoUpdateStatuses.UpdateFailed,
                        FailedReason = ex.FlattenMessage(),
                        FailedLog = logs,
                    });
                }
                catch (Exception xx)
                {
                    LogManager.Log(xx, "Error notifying update failed.");
                }

                try
                {
                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        TangoUpdate update = new TangoUpdate();
                        update.ApplicationVersion = response.Version;
                        update.FirmwareVersion = response.FirmwareVersion;
                        update.MachineGuid = _machineProvider.Machine.Guid;
                        update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.UpdateFailed;
                        update.StartDate = _updateStartDate;
                        update.EndDate = DateTime.UtcNow;
                        update.FailedReason = ex.FlattenMessage();
                        update.FailedLog = logs;
                        db.TangoUpdates.Add(update);
                        await db.SaveChangesAsync();
                    }
                }
                catch (Exception xxx)
                {
                    LogManager.Log(xxx, "Error saving tango update information to database.");
                }
            }


            if (tupPublishInfo != null)
            {
                try
                {
                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        TangoUpdate update = new TangoUpdate();
                        update.ApplicationVersion = tupPublishInfo.ApplicationVersion;
                        update.FirmwareVersion = tupPublishInfo.GetFirmwareVersion();
                        update.MachineGuid = _machineProvider.Machine.Guid;
                        update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.OfflineUpdateFailed;
                        update.StartDate = _updateStartDate;
                        update.EndDate = DateTime.UtcNow;
                        update.FailedReason = ex.FlattenMessage();
                        update.FailedLog = logs;
                        db.TangoUpdates.Add(update);
                        await db.SaveChangesAsync();
                    }
                }
                catch (Exception xxx)
                {
                    LogManager.Log(xxx, "Error saving tango offline update information to database.");
                }
            }

            _isUpdating = false;
        }

        private async void OnCompleted(MachineUpdateResult result, TaskCompletionSource<MachineUpdateResult> completionSource, DownloadUpdateResponse response, String tempDbName, String backupsFolder, Core.DataSource localDataSource, PublishInfo tupPublishInfo = null)
        {
            await Task.Factory.StartNew(() =>
            {
                if (tempDbName != null)
                {
                    try
                    {
                        LogManager.Log($"Removing temporary database '{tempDbName}'...");
                        using (DbManager dbManager = DbManager.FromDataSource(localDataSource))
                        {
                            dbManager.SetOffline(tempDbName);
                            dbManager.SetOnline(tempDbName);
                            dbManager.Delete(tempDbName);
                        }
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error removing temporary database.");
                    }
                }

                try
                {
                    Directory.Delete(backupsFolder, true);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, $"Error deleting backups folder '{backupsFolder}'.");
                }

                if (!result.RequiresBinariesUpdate)
                {
                    try
                    {
                        Directory.Delete(result.UpdatePackagePath, true);
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error removing temporary package folder.");
                    }
                }

            });

            completionSource.SetResult(result);

            if (response != null)
            {
                try
                {
                    var r = await _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest()
                    {
                        Token = response.NotifyCompletedToken,
                        Status = BL.Enumerations.TangoUpdateStatuses.UpdateCompleted,
                    });
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error notifying update completed.");
                }

                try
                {
                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        TangoUpdate update = new TangoUpdate();
                        update.ApplicationVersion = response.Version;
                        update.FirmwareVersion = response.FirmwareVersion;
                        update.MachineGuid = (await db.Machines.FirstAsync()).Guid;
                        update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.UpdateCompleted;
                        update.StartDate = _updateStartDate;
                        update.EndDate = DateTime.UtcNow;
                        db.TangoUpdates.Add(update);
                        await db.SaveChangesAsync();
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error saving tango update information to database.");
                }
            }


            if (tupPublishInfo != null)
            {
                try
                {
                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        TangoUpdate update = new TangoUpdate();
                        update.ApplicationVersion = tupPublishInfo.ApplicationVersion;
                        update.FirmwareVersion = tupPublishInfo.GetFirmwareVersion();
                        update.MachineGuid = _machineProvider.Machine.Guid;
                        update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.OfflineUpdateCompleted;
                        update.StartDate = _updateStartDate;
                        update.EndDate = DateTime.UtcNow;
                        db.TangoUpdates.Add(update);
                        await db.SaveChangesAsync();
                    }
                }
                catch (Exception xxx)
                {
                    LogManager.Log(xxx, "Error saving tango offline update information to database.");
                }
            }

            _isUpdating = false;
        }

        private void OnFailed(Exception ex, UpdateDBResponse response, bool performDatabaseRollback, String dbBackupFile, Tango.Core.DataSource localDataSource)
        {
            LogManager.Log(ex, "An error occurred in database update.");

            if (performDatabaseRollback)
            {
                LogManager.Log("Rolling back database changes...");

                using (DbManager db = DbManager.FromDataSource(localDataSource))
                {
                    try
                    {
                        UpdateProgress("Rollback", "Rolling back database changes...");
                        db.Restore(localDataSource.Catalog, dbBackupFile);
                        LogManager.Log("Database restored successfully.");
                    }
                    catch (Exception e)
                    {
                        LogManager.Log(e, "Could not rollback the database.");
                        throw ex;
                    }
                    finally
                    {
                        try
                        {
                            File.Delete(dbBackupFile);
                        }
                        catch { }
                    }
                }
            }

            String logs = GetLogsStringAndClear();

            if (response != null)
            {
                try
                {
                    var r = _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest()
                    {
                        Token = response.NotifyCompletedToken,
                        Status = BL.Enumerations.TangoUpdateStatuses.DatabaseFailed,
                        FailedReason = ex.FlattenMessage(),
                        FailedLog = logs,
                    }).Result;
                }
                catch (Exception xx)
                {
                    LogManager.Log(xx, "Error notifying database failed.");
                }

                try
                {
                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        TangoUpdate update = new TangoUpdate();
                        update.ApplicationVersion = _app_manager.Version.ToString();
                        update.FirmwareVersion = _app_manager.FirmwareVersion.ToString();
                        update.MachineGuid = _machineProvider.Machine.Guid;
                        update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.DatabaseFailed;
                        update.StartDate = _updateStartDate;
                        update.EndDate = DateTime.UtcNow;
                        update.FailedReason = ex.FlattenMessage();
                        update.FailedLog = logs;
                        db.TangoUpdates.Add(update);
                        db.SaveChanges();
                    }
                }
                catch (Exception exx)
                {
                    LogManager.Log(exx, "Error saving database update information to database.");
                }
            }

            _isUpdating = false;
        }

        private void OnCompleted(UpdateDBResponse response, bool completedWithNoDifferences = false)
        {
            if (response != null)
            {
                try
                {
                    var r = _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest()
                    {
                        Token = response.NotifyCompletedToken,
                        Status = BL.Enumerations.TangoUpdateStatuses.DatabaseCompleted,
                        ReportsAboutDbCheckNoDifferences = completedWithNoDifferences,
                    }).Result;
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error notifying database completed.");
                }

                if (!completedWithNoDifferences)
                {
                    try
                    {
                        using (ObservablesContext db = ObservablesContext.CreateDefault())
                        {
                            TangoUpdate update = new TangoUpdate();
                            update.ApplicationVersion = _app_manager.Version.ToString();
                            update.FirmwareVersion = _app_manager.FirmwareVersion.ToString();
                            update.MachineGuid = _machineProvider.Machine.Guid;
                            update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.DatabaseCompleted;
                            update.StartDate = _updateStartDate;
                            update.EndDate = DateTime.UtcNow;
                            db.TangoUpdates.Add(update);
                            db.SaveChanges();
                        }
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error saving database update information to database.");
                    }
                }
            }

            _isUpdating = false;
        }

        private void OnFailed(Exception ex, TaskCompletionSource<object> completionSource, String firmwareVersion)
        {
            LogManager.Log(ex, "An error occurred in firmware upgrade.");

            completionSource.SetException(ex);
            String logs = GetLogsStringAndClear();

            try
            {
                using (ObservablesContext db = ObservablesContext.CreateDefault())
                {
                    TangoUpdate update = new TangoUpdate();
                    update.ApplicationVersion = _app_manager.Version.ToString();
                    update.FirmwareVersion = firmwareVersion;
                    update.MachineGuid = _machineProvider.Machine.Guid;
                    update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.OfflineFirmwareUpgradeFailed;
                    update.StartDate = _updateStartDate;
                    update.EndDate = DateTime.UtcNow;
                    update.FailedReason = ex.FlattenMessage();
                    update.FailedLog = logs;
                    db.TangoUpdates.Add(update);
                    db.SaveChanges();
                }
            }
            catch (Exception exx)
            {
                LogManager.Log(exx, "Error saving firmware upgrade information to database.");
            }

            _isUpdating = false;
        }

        private void OnCompleted(TaskCompletionSource<object> completionSource, String firmwareVersion)
        {
            LogManager.Log("Firmware upgrade completed successfully.");
            completionSource.SetResult(true);

            try
            {
                using (ObservablesContext db = ObservablesContext.CreateDefault())
                {
                    TangoUpdate update = new TangoUpdate();
                    update.ApplicationVersion = _app_manager.Version.ToString();
                    update.FirmwareVersion = firmwareVersion;
                    update.MachineGuid = _machineProvider.Machine.Guid;
                    update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.OfflineFirmwareUpgradeCompleted;
                    update.StartDate = _updateStartDate;
                    update.EndDate = DateTime.UtcNow;
                    db.TangoUpdates.Add(update);
                    db.SaveChanges();
                }
            }
            catch (Exception exx)
            {
                LogManager.Log(exx, "Error saving firmware upgrade information to database.");
            }

            _isUpdating = false;
        }

        private String GetLogsStringAndClear()
        {
            String logsString = String.Join(Environment.NewLine, _logs.ToList().Select(x => x.ToString()));
            _logs.Clear();
            return logsString;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Performs a machine update using the specified serial number and machine service address.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="setupFirmware">if set to <c>true</c> updates the embedded device firmware.</param>
        /// <param name="setupFPGA">if set to <c>true</c> updates the embedded device FPGA version and other parameters.</param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException">
        /// Could not perform an update while the machine is not connected.
        /// or
        /// or
        /// </exception>
        /// <exception cref="System.InvalidProgramException">Database tango does not exists.</exception>
        public async Task<MachineUpdateResult> Update(bool setupFirmware, bool setupFPGA)
        {
            _updateStartDate = DateTime.UtcNow;
            _logs.Clear();

            TaskCompletionSource<MachineUpdateResult> result = new TaskCompletionSource<MachineUpdateResult>();

            var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource;
            bool performDatabaseRollback = false;
            String dbBackupFile = null;
            DownloadUpdateResponse update_response = null;
            String backupsFolder = "C:\\Backups";

            //Create temporary folders for packages.
            var _newPackageTempFolder = TemporaryManager.CreateFolder();
            _newPackageTempFolder.Persist = true;

            String serialNumber = _machineProvider.Machine.SerialNumber;

            try
            {
                _isUpdating = true;

                var machineServiceAddress = _settings.GetMachineServiceAddress();

                LogManager.Log($"Starting machine update for serial number {serialNumber}...");

                //Connecting to machine...
                LogManager.Log("Verifying machine connection and state...");

                UpdateProgress("Verifying machine state", "Initializing...");

                await Task.Delay(1000);

                IMachineOperator op = _machineProvider.MachineOperator;

                if (setupFirmware)
                {
                    LogManager.Log("Machine is configured to update firmware...");

                    if (op.State != Transport.TransportComponentState.Connected)
                    {
                        throw LogManager.Log(new InvalidOperationException("Could not perform an update while the machine is not connected."));
                    }
                }

                if (!op.CanPrint)
                {
                    throw LogManager.Log(new InvalidOperationException($"Could not perform an update while the machine is in {op.Status} status."));
                }

                //Connect to machine service and get matching packages for this machine.
                UpdateProgress("Downloading software package", "Connecting to machine service...");

                LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");

                await Login(serialNumber);

                DownloadUpdateRequest request = new DownloadUpdateRequest();
                request.SerialNumber = serialNumber;

                update_response = await _client.MachineUpdate(request);

                LogManager.Log($"Machine update response received: {Environment.NewLine}{update_response.ToJsonString()}");

                LogManager.Log($"Temporary package folder created: {_newPackageTempFolder}.");

                //Download software package.
                var tempFile = TemporaryManager.CreateFile(".zip");

                LogManager.Log($"Temporary package zip file created: {tempFile}.");

                LogManager.Log("Downloading software package...");

                UpdateProgress("Downloading software package", "Downloading...", false);

                using (AutoFileDownloader downloader = new AutoFileDownloader(update_response.BlobAddress, update_response.CdnAddress, tempFile))
                {
                    await downloader.ResolveMode();

                    if (downloader.Mode == AutoFileDownloader.DownloadMode.Standard)
                    {
                        LogManager.Log($"Connecting to storage CDN with address {downloader.Address}");
                    }
                    else
                    {
                        LogManager.Log($"Connecting to storage blob with address {downloader.Address}");
                    }

                    downloader.Progress += (x, e) =>
                    {
                        UpdateProgress("Downloading software package", "Downloading...", false, e.Current, e.Total);
                    };

                    var size = await downloader.GetFileSize();
                    LogManager.Log("Download size: " + size + " bytes.");
                    LogManager.Log("Starting file download...");
                    await downloader.Download();
                }

                UpdateProgress("Downloading software package", "Extracting package...");

                LogManager.Log("Extracting downloaded zip file...");

                await Task.Factory.StartNew(() =>
                {
                    //Extract software package.
                    ZipFile.ExtractToDirectory(tempFile, _newPackageTempFolder);
                });

                LogManager.Log("Copying latest updater utility to application path...");
                //Copy new updater utility to app path.
                File.Copy(Path.Combine(_newPackageTempFolder, "Tango.PPC.Updater.exe"), Path.Combine(PathHelper.GetStartupPath(), "Tango.PPC.Updater.exe"), true);

                //Run pre-update packages.
                try
                {
                    UpdateProgress("Preparing", "Running update packages...");
                    LogManager.Log("Running pre-update packages...");
                    var packagesFolder = Path.Combine(_newPackageTempFolder, "Packages");

                    Version updateVersion = new Version(1, 0, 0, 0);
                    try
                    {
                        updateVersion = Version.Parse(update_response.Version);
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error parsing new version string for package runner.");
                    }

                    await _packageRunner.Run(PackageType.Pre, updateVersion, packagesFolder);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error running pre-update packages...");
                }

                //Synchronize database
                UpdateProgress("Updating Database", "Initializing...");

                LogManager.Log($"Synchronizing database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");

                UpdateProgress("Updating Database", "Connecting to local database...");
                LogManager.Log("Initializing database manager...");
                DbManager db = DbManager.FromDataSource(localDataSource);

                LogManager.Log("Checking Tango database exists on the local machine...");
                if (!db.Exists(localDataSource.Catalog))
                {
                    throw new InvalidProgramException("Database tango does not exists.");
                }

                UpdateProgress("Updating Database", "Creating database backup...");

                //Create Database Backup
                try
                {
                    Directory.CreateDirectory(backupsFolder);
                    dbBackupFile = $"{backupsFolder}\\{Path.GetRandomFileName()}.bak";
                    LogManager.Log($"Creating database backup to '{dbBackupFile}'...");
                    await Task.Factory.StartNew(() => db.Backup(localDataSource.Catalog, dbBackupFile));
                    performDatabaseRollback = true;
                    LogManager.Log("Database backup created successfully.");
                }
                catch (Exception ex)
                {
                    throw LogManager.Log(ex, "Update manager error while trying to create a database backup.");
                }


                LogManager.Log("Disposing database manager.");
                db.Dispose();

                LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}...");

                UpdateProgress("Updating Database", "Initializing update sequence...");

                ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner(
                    Path.Combine(_newPackageTempFolder, "Update Scripts", "config.xml"),
                    Path.Combine(_newPackageTempFolder, "Update Scripts"),
                        update_response.DataSource,
                        localDataSource,
                        serialNumber);

                runner.Log += (x, msg) =>
                {
                    LogManager.Log(msg);
                    ProgressLog?.Invoke(this, msg);
                };

                runner.ScriptExecuting += (x, item) =>
                {
                    LogManager.Log($"Executing script {item.ToString()}...");
                    UpdateProgress("Updating Database", item.Name + "...");
                };

                LogManager.Log("Starting synchronization process...");

                try
                {
                    await runner.Run();
                    LogManager.Log("Synchronization completed successfully!");
                    UpdateProgress("Updating Database", "Database synchronization completed successfully.");
                }
                catch (Exception ex)
                {
                    throw LogManager.Log(ex, "Update manager error while trying to synchronize database.");
                }

                //Updating firmware
                if (setupFirmware)
                {
                    UpdateProgress("Updating Firmware", "Connecting to firmware device...");
                    LogManager.Log("");
                    LogManager.Log("-------------------------------------------------------------------------");
                    LogManager.Log("Updating Firmware...");

                    UpdateProgress("Updating Firmware", "Loading firmware package...");
                    var tfpPath = Path.Combine(_newPackageTempFolder, "firmware_package.tfp");
                    var stream = new FileStream(tfpPath, FileMode.Open);

                    if (!_machineProvider.Machine.IsDemo)
                    {
                        if (setupFPGA)
                        {
                            op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE;
                        }
                        else
                        {
                            op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU;
                        }
                    }
                    else
                    {
                        op.FirmwareUpgradeMode = FirmwareUpgradeModes.TFP_PACKAGE;
                    }

                    var handler = await op.UpgradeFirmware(stream);
                    handler.Failed += (_, ex) =>
                    {
                        stream.Dispose();
                        OnFailed(ex, result, update_response, performDatabaseRollback, dbBackupFile, backupsFolder, null, localDataSource, _newPackageTempFolder);
                    };
                    handler.Completed += (_, __) =>
                    {
                        UpdateProgress("Updating Firmware", "Firmware update completed successfully.");
                        stream.Dispose();
                        OnCompleted(new MachineUpdateResult()
                        {
                            UpdatePackagePath = _newPackageTempFolder,
                        }, result, update_response, null, backupsFolder, localDataSource);
                    };
                    handler.Canceled += (_, __) =>
                    {
                        stream.Dispose();
                        OnFailed(new Exception("The operation has been canceled."), result, update_response, performDatabaseRollback, dbBackupFile, backupsFolder, null, localDataSource, _newPackageTempFolder);
                    };
                    handler.Progress += (_, e) =>
                    {
                        UpdateProgress("Updating Firmware", e.Message, false, e.Current, e.Total);
                    };
                }
                else
                {
                    OnCompleted(new MachineUpdateResult()
                    {
                        UpdatePackagePath = _newPackageTempFolder,
                    }, result, update_response, null, backupsFolder, localDataSource);
                }
            }
            catch (Exception ex)
            {
                OnFailed(ex, result, update_response, performDatabaseRollback, dbBackupFile, backupsFolder, null, localDataSource, _newPackageTempFolder);
            }

            return await result.Task;
        }

        /// <summary>
        /// Checks if any update are available for the specified machine serial number.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="machineServiceAddress">The machine service address.</param>
        /// <returns></returns>
        public Task<CheckForUpdateResponse> CheckForUpdate(string serialNumber)
        {
            return Task.Factory.StartNew<CheckForUpdateResponse>(() =>
            {
                _isUpdating = true;

                var machineServiceAddress = _settings.GetMachineServiceAddress();

                LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");

                Login(serialNumber).GetAwaiter().GetResult();

                LogManager.Log($"Checking if updates available...");

                CheckForUpdateRequest request = new CheckForUpdateRequest();
                request.SerialNumber = serialNumber;
                request.Version = _app_manager.Version.ToString();
                request.FirmwareVersion = _app_manager.FirmwareVersion?.ToString();

                try
                {
                    request.MachineLastUpdated = _machineProvider.Machine.LastUpdated;

                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        request.Rmls = db.Rmls.ToList().Select(x => new UpdatedEntity(x)).ToList();
                        request.HardwareVersions = db.HardwareVersions.ToList().Select(x => new UpdatedEntity(x)).ToList();
                        request.Catalogs = db.ColorCatalogs.ToList().Select(x => new UpdatedEntity(x)).ToList();
                        request.UsedRmlsGuids = db.Jobs.Select(x => x.RmlGuid).Distinct().ToList();
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "An error occurred while trying to fill the existing database entities before checking for updates.");
                }

                CheckForUpdateResponse update_response = null;

                update_response = _client.CheckForUpdates(request).Result;

                LogManager.Log($"Check for update response received: {Environment.NewLine}{update_response.ToJsonString()}");

                _isUpdating = false;

                return update_response;
            });
        }

        /// <summary>
        /// Updates all the "overwrite-able" database tables.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="machineServiceAddress">The machine service address.</param>
        /// <returns></returns>
        public Task UpdateDB(DbCompareResult dbCompareResult, String serialNumber)
        {
            _updateStartDate = DateTime.UtcNow;
            _logs.Clear();

            return Task.Factory.StartNew(() =>
            {
                _isUpdating = true;
                UpdateDBResponse update_response = null;
                var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource;
                bool performDatabaseRollback = false;
                String dbBackupFile = null;

                try
                {
                    LogManager.Log("Starting database update...");

                    if (!_machineProvider.MachineOperator.CanPrint)
                    {
                        throw LogManager.Log(new InvalidOperationException($"Could not perform an update while the machine is in {_machineProvider.MachineOperator.Status} status."));
                    }

                    UpdateProgress("Updating Database", "Initializing...");

                    LogManager.Log("Looking for update scripts configuration on application path...");

                    String config_file = Path.Combine(PathHelper.GetStartupPath(), "Update Scripts", "config.xml");

                    if (!File.Exists(config_file))
                    {
                        throw LogManager.Log(new FileNotFoundException($"Could not locate '{config_file}' file on application folder."));
                    }

                    update_response = dbCompareResult.UpdateDBResponse;

                    LogManager.Log($"Updating database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");

                    UpdateProgress("Updating Database", "Initializing update sequence...");

                    ExaminerSequenceConfiguration config_sequence = ExaminerSequenceConfiguration.FromFile(config_file);

                    UpdateProgress("Updating Database", "Connecting to local database...");
                    LogManager.Log("Initializing database manager...");
                    DbManager db = DbManager.FromDataSource(localDataSource);

                    LogManager.Log("Checking Tango database exists on the local machine...");
                    if (!db.Exists(localDataSource.Catalog))
                    {
                        throw new InvalidProgramException("Database tango does not exists.");
                    }

                    UpdateProgress("Updating Database", "Creating database backup...");

                    //Create Database Backup
                    try
                    {
                        Directory.CreateDirectory("C:\\Backups");
                        dbBackupFile = $"C:\\Backups\\{Path.GetRandomFileName()}.bak";
                        LogManager.Log($"Creating database backup to '{dbBackupFile}'...");
                        db.Backup(localDataSource.Catalog, dbBackupFile);
                        performDatabaseRollback = true;
                        LogManager.Log("Database backup created successfully.");
                    }
                    catch (Exception ex)
                    {
                        throw LogManager.Log(ex, "Update manager error while trying to create a database backup.");
                    }

                    LogManager.Log("Disposing database manager.");
                    db.Dispose();

                    foreach (var item in config_sequence.Items.Where(x => x.Type == ExaminerSequenceItemType.Data || update_response.PerformSchemaUpdate).OrderBy(x => x.Index))
                    {
                        LogManager.Log($"Executing update script '{item.FileName}...'");

                        ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(Path.Combine(Path.GetDirectoryName(config_file), item.FileName));
                        builder.SetSource(update_response.DataSource);
                        builder.SetTarget(localDataSource);

                        if (item.RequiresSerialNumber)
                        {
                            builder.SetMachineSerialNumber(serialNumber);
                        }

                        builder.Synchronize();

                        var config = builder.Build();

                        ExaminerProcess process = new ExaminerProcess(config, item.Type == ExaminerSequenceItemType.Data ? ExaminerProcessType.Data : ExaminerProcessType.Schema);
                        process.Progress += (x, msg) =>
                        {
                            LogManager.Log(msg);
                        };

                        try
                        {
                            UpdateProgress("Updating Database", item.Name + "...");

                            var result = process.Execute().Result;

                            if (result.ExitCode != ExaminerProcessExitCode.Success)
                            {
                                throw LogManager.Log(new InvalidDataException($"{item.FileName} script has terminated with exit code '{result.ExitCode}'."));
                            }

                            LogManager.Log("Script executed successfully.");
                        }
                        catch (Exception ex)
                        {
                            throw LogManager.Log(ex, "Upudate manager error while trying to update the database.");
                        }
                    }

                    UpdateProgress("Updating Database", "Database synchronization completed successfully.");
                    LogManager.Log("Update completed successfully.");
                    OnCompleted(update_response);
                }
                catch (Exception ex)
                {
                    OnFailed(ex, update_response, performDatabaseRollback, dbBackupFile, localDataSource);
                    throw ex;
                }
            });
        }

        /// <summary>
        /// Checks whether it is necessary to updates all the "overwrite-able" database tables.
        /// </summary>
        /// <param name="serialNumber">The serial number.</param>
        /// <param name="machineServiceAddress">The machine service address.</param>
        /// <returns></returns>
        public Task<DbCompareResult> UpdateDBCheck(string serialNumber)
        {
            return Task.Factory.StartNew<DbCompareResult>(() =>
            {
                var machineServiceAddress = _settings.GetMachineServiceAddress();

                LogManager.Log($"Checking if database update is required for serial number {serialNumber}...");

                LogManager.Log("Looking for update scripts configuration on application path...");

                String config_file = Path.Combine(PathHelper.GetStartupPath(), "Update Scripts", "config.xml");

                if (!File.Exists(config_file))
                {
                    throw LogManager.Log(new FileNotFoundException($"Could not locate '{config_file}' file on application folder."));
                }

                LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");

                Login(serialNumber).Wait();

                UpdateDBRequest request = new UpdateDBRequest();
                request.SerialNumber = serialNumber;
                request.ApplicationVersion = _app_manager.Version.ToString();
                request.FirmwareVersion = _app_manager.FirmwareVersion.ToString();

                UpdateDBResponse update_response = null;

                update_response = _client.UpdateDB(request).Result;

                LogManager.Log($"Update DB response received: {Environment.NewLine}{update_response.ToJsonString()}");

                var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource;

                LogManager.Log($"Comparing database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");

                var report_file = TemporaryManager.CreateFile(".xml");

                ExaminerSequenceConfiguration config_sequence = ExaminerSequenceConfiguration.FromFile(config_file);

                bool has_differences = false;

                foreach (var item in config_sequence.Items.Where(x => x.Type == ExaminerSequenceItemType.Data).OrderBy(x => x.Index))
                {
                    LogManager.Log($"Executing update script '{item.FileName}...'");

                    ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(Path.Combine(Path.GetDirectoryName(config_file), item.FileName));
                    builder.SetSource(update_response.DataSource);
                    builder.SetTarget(localDataSource);
                    builder.SetReportFile(report_file);

                    if (item.RequiresSerialNumber)
                    {
                        builder.SetMachineSerialNumber(serialNumber);
                    }

                    var config = builder.Build();

                    ExaminerProcess process = new ExaminerProcess(config, ExaminerProcessType.Data);
                    process.Progress += (x, msg) =>
                    {
                        LogManager.Log(msg);
                    };

                    LogManager.Log("Starting comparison process...");
                    LogManager.Log("Generating report on " + report_file);

                    try
                    {
                        var result = process.Execute().Result;

                        if (result.ExitCode != ExaminerProcessExitCode.Success)
                        {
                            throw LogManager.Log(new InvalidDataException(String.Format("Update script has terminated with exit code '{0}'.", result.ExitCode)));
                        }

                        LogManager.Log("Comparison completed successfully!");
                        LogManager.Log("Loading report file...");

                        ExaminerDataReport report = ExaminerDataReport.FromFile(report_file);
                        report_file.Delete();

                        LogManager.Log("Comparison summary: \n" + report.Totals.ToJsonString());

                        if (report.HasDifferences)
                        {
                            has_differences = true;
                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        OnFailed(ex, update_response, false, null, null);
                        throw LogManager.Log(ex, "Update manager error while trying to compare the database.");
                    }
                }

                LogManager.Log("Comparison completed successfully.");

                if (!has_differences)
                {
                    OnCompleted(update_response, true);
                }

                return new DbCompareResult()
                {
                    RequiresUpdate = has_differences,
                    UpdateDBResponse = update_response,
                };
            });
        }

        /// <summary>
        /// Performs a machine update using the specified software update package path.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public async Task<MachineUpdateResult> UpdateFromTUP(string fileName, bool setupFirmware, bool setupFPGA)
        {
            _updateStartDate = DateTime.UtcNow;
            _logs.Clear();

            TaskCompletionSource<MachineUpdateResult> result = new TaskCompletionSource<MachineUpdateResult>();

            var localDataSource = SettingsManager.Default.GetOrCreate<CoreSettings>().DataSource;
            bool performDatabaseRollback = false;
            String dbBackupFile = null;
            String tempDbName = "Tango_TUP";
            String tempDbFileName = tempDbName + ".bak";
            String backupsFolder = "C:\\Backups";
            bool replaceBinaries = false;
            PublishInfo publishInfo = null;

            String serialNumber = _machineProvider.Machine.SerialNumber;

            //Create temporary folders for packages.
            var _newPackageTempFolder = TemporaryManager.CreateFolder();
            _newPackageTempFolder.Persist = true;

            try
            {
                _isUpdating = true;

                LogManager.Log($"Starting machine update for serial number {serialNumber}...");

                //Connecting to machine...
                LogManager.Log("Verifying machine connection and state...");

                UpdateProgress("Verifying machine state", "Initializing...");

                await Task.Delay(1000);

                IMachineOperator op = _machineProvider.MachineOperator;

                if (setupFirmware)
                {
                    LogManager.Log("Machine is configured to update firmware...");

                    if (op.State != Transport.TransportComponentState.Connected)
                    {
                        throw LogManager.Log(new InvalidOperationException("Could not perform an update while the machine is not connected."));
                    }
                }

                if (!op.CanPrint)
                {
                    throw LogManager.Log(new InvalidOperationException($"Could not perform an update while the machine is in {op.Status} status."));
                }

                UpdateProgress("Exploring package", "Extracting...");
                LogManager.Log("Extracting package...");

                LogManager.Log($"Temporary package folder created: {_newPackageTempFolder}.");

                await Task.Factory.StartNew(() =>
                {
                    //Extract software package.
                    ZipFile.ExtractToDirectory(fileName, _newPackageTempFolder);
                });

                //Extracting publish info
                UpdateProgress("Exploring package", "Verifying...");
                publishInfo = PublishInfo.FromJson(File.ReadAllText(Path.Combine(_newPackageTempFolder, "version.json")));

                if (!publishInfo.IsMachineTupPackage)
                {
                    throw new InvalidOperationException("The specified tup file is invalid. Updating a machine from a tup file requires a custom generated package.");
                }

                if (publishInfo.MachineSerialNumber != serialNumber)
                {
                    throw new InvalidOperationException("The specified tup file is invalid. The package was generated for a different machine.");
                }

                if (publishInfo.MachineDeploymentSlot != _settings.DeploymentSlot)
                {
                    throw new InvalidOperationException("The specified tup file is invalid. The package was generated on a different environment.");
                }

                replaceBinaries = _app_manager.Version.ToString() != publishInfo.ApplicationVersion;

                LogManager.Log("Copying latest updater utility to application path...");

                //Copy new updater utility to app path.
                File.Copy(Path.Combine(_newPackageTempFolder, "Tango.PPC.Updater.exe"), Path.Combine(PathHelper.GetStartupPath(), "Tango.PPC.Updater.exe"), true);

                //Run pre-update packages.
                try
                {
                    UpdateProgress("Preparing", "Running update packages...");
                    LogManager.Log("Running pre-update packages...");
                    var packagesFolder = Path.Combine(_newPackageTempFolder, "Packages");

                    Version updateVersion = new Version(1, 0, 0, 0);
                    try
                    {
                        updateVersion = Version.Parse(publishInfo.ApplicationVersion);
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error parsing new version string for package runner.");
                    }

                    await _packageRunner.Run(PackageType.Pre, updateVersion, packagesFolder);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error running pre-update packages...");
                }

                //Synchronize database
                UpdateProgress("Updating Database", "Initializing...");

                UpdateProgress("Updating Database", "Connecting to local database...");
                LogManager.Log("Initializing database manager...");
                DbManager db = DbManager.FromDataSource(localDataSource);

                LogManager.Log("Checking Tango database exists on the local machine...");
                if (!db.Exists(localDataSource.Catalog))
                {
                    throw new InvalidProgramException("Database tango does not exists.");
                }

                UpdateProgress("Updating Database", "Creating database backup...");

                //Create Database Backup
                try
                {
                    Directory.CreateDirectory(backupsFolder);
                    dbBackupFile = $"{backupsFolder}\\{Path.GetRandomFileName()}.bak";
                    LogManager.Log($"Creating database backup to '{dbBackupFile}'...");
                    await Task.Factory.StartNew(() => db.Backup(localDataSource.Catalog, dbBackupFile));
                    performDatabaseRollback = true;
                    LogManager.Log("Database backup created successfully.");
                }
                catch (Exception ex)
                {
                    throw LogManager.Log(ex, "Update manager error while trying to create a database backup.");
                }

                LogManager.Log("Extracting database file from package...");
                File.Copy(Path.Combine(_newPackageTempFolder, tempDbFileName), Path.Combine(backupsFolder, tempDbFileName));

                LogManager.Log("Restoring package database as a new database...");
                db.RestoreAsNew(tempDbName, Path.Combine(backupsFolder, tempDbFileName), backupsFolder);

                Core.DataSource tempDbDataSource = new Core.DataSource();
                tempDbDataSource.Address = localDataSource.Address;
                tempDbDataSource.IntegratedSecurity = localDataSource.IntegratedSecurity;
                tempDbDataSource.Type = localDataSource.Type;
                tempDbDataSource.Catalog = tempDbName;

                LogManager.Log("Disposing database manager.");
                db.Dispose();

                LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}...");

                UpdateProgress("Updating Database", "Initializing update sequence...");

                ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner(
                    Path.Combine(_newPackageTempFolder, "Update Scripts", "config.xml"),
                    Path.Combine(_newPackageTempFolder, "Update Scripts"),
                        tempDbDataSource,
                        localDataSource,
                        serialNumber);

                runner.Log += (x, msg) =>
                {
                    LogManager.Log(msg);
                    ProgressLog?.Invoke(this, msg);
                };

                runner.ScriptExecuting += (x, item) =>
                {
                    LogManager.Log($"Executing script {item.ToString()}...");
                    UpdateProgress("Updating Database", item.Name + "...");
                };

                LogManager.Log("Starting synchronization process...");

                try
                {
                    await runner.Run();
                    LogManager.Log("Synchronization completed successfully!");
                    UpdateProgress("Updating Database", "Database synchronization completed successfully.");
                }
                catch (Exception ex)
                {
                    throw LogManager.Log(ex, "Update manager error while trying to synchronize database.");
                }

                LogManager.Log("Getting setup firmware/fpga directly from db..");

                using (var dbManager = DbManager.FromDataSource(localDataSource))
                {
                    try
                    {
                        String firmware = dbManager.GetValue($"SELECT TOP 1 * FROM MACHINES WHERE SERIAL_NUMBER = '{serialNumber}'", "SETUP_FIRMWARE");
                        String fpga = dbManager.GetValue($"SELECT TOP 1 * FROM MACHINES WHERE SERIAL_NUMBER = '{serialNumber}'", "SETUP_FPGA");

                        setupFirmware = bool.Parse(firmware);
                        setupFPGA = bool.Parse(fpga);
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error getting new values of SETUP_FIRMWARE and SETUP_FPGA.");
                    }
                }

                //Updating firmware
                if (setupFirmware)
                {
                    UpdateProgress("Updating Firmware", "Connecting to firmware device...");
                    LogManager.Log("");
                    LogManager.Log("-------------------------------------------------------------------------");
                    LogManager.Log("Updating Firmware...");

                    UpdateProgress("Updating Firmware", "Loading firmware package...");
                    var tfpPath = Path.Combine(_newPackageTempFolder, "firmware_package.tfp");
                    var stream = new FileStream(tfpPath, FileMode.Open);

                    if (!_machineProvider.Machine.IsDemo)
                    {
                        if (setupFPGA)
                        {
                            op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE;
                        }
                        else
                        {
                            op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU;
                        }
                    }
                    else
                    {
                        op.FirmwareUpgradeMode = FirmwareUpgradeModes.TFP_PACKAGE;
                    }

                    var handler = await op.UpgradeFirmware(stream);
                    handler.Failed += (_, ex) =>
                    {
                        stream.Dispose();
                        OnFailed(ex, result, null, performDatabaseRollback, dbBackupFile, backupsFolder, tempDbName, localDataSource, _newPackageTempFolder, publishInfo);
                    };
                    handler.Completed += (_, __) =>
                    {
                        UpdateProgress("Updating Firmware", "Firmware update completed successfully.");
                        stream.Dispose();
                        OnCompleted(new MachineUpdateResult()
                        {
                            UpdatePackagePath = _newPackageTempFolder,
                            RequiresBinariesUpdate = replaceBinaries,
                        }, result, null, tempDbName, backupsFolder, localDataSource, publishInfo);
                    };
                    handler.Canceled += (_, __) =>
                    {
                        stream.Dispose();
                        OnFailed(new Exception("The operation has been canceled."), result, null, performDatabaseRollback, dbBackupFile, backupsFolder, tempDbName, localDataSource, _newPackageTempFolder, publishInfo);
                    };
                    handler.Progress += (_, e) =>
                    {
                        UpdateProgress("Updating Firmware", e.Message, false, e.Current, e.Total);
                    };
                }
                else
                {
                    OnCompleted(new MachineUpdateResult()
                    {
                        UpdatePackagePath = _newPackageTempFolder,
                        RequiresBinariesUpdate = replaceBinaries,
                    }, result, null, tempDbName, backupsFolder, localDataSource, publishInfo);
                }
            }
            catch (Exception ex)
            {
                OnFailed(ex, result, null, performDatabaseRollback, dbBackupFile, backupsFolder, tempDbName, localDataSource, _newPackageTempFolder, publishInfo);
            }

            return await result.Task;
        }

        /// <summary>
        /// Performs a firmware upgrade from the specified TFP file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">
        /// Could not perform a firmware upgrade while the machine is not connected.
        /// or
        /// </exception>
        public async Task UpdateFromTFP(String fileName)
        {
            _updateStartDate = DateTime.UtcNow;
            _logs.Clear();

            TaskCompletionSource<object> result = new TaskCompletionSource<object>();

            String version = String.Empty;
            Stream stream = null;

            try
            {
                _isUpdating = true;

                IMachineOperator op = _machineProvider.MachineOperator;

                UpdateProgress("Updating Firmware", "Loading firmware package...");
                stream = new FileStream(fileName, FileMode.Open);

                var packageInfo = await op.GetFirmwarePackageInfo(stream);
                stream.Position = 0;
                version = packageInfo.FileDescriptors.FirstOrDefault(x => x.Destination == PMR.FirmwareUpgrade.VersionFileDestination.Mcu)?.Version;

                LogManager.Log("Verifying machine connection and state...");

                UpdateProgress("Verifying machine state", "Initializing...");

                await Task.Delay(1000);

                if (op.State != Transport.TransportComponentState.Connected)
                {
                    throw LogManager.Log(new InvalidOperationException("Could not perform a firmware upgrade while the machine is not connected."));
                }
                if (!op.CanPrint)
                {
                    throw LogManager.Log(new InvalidOperationException($"Could not perform a firmware upgrade while the machine is in {op.Status} status."));
                }

                UpdateProgress("Updating Firmware", "Connecting to firmware device...");
                LogManager.Log("");
                LogManager.Log("-------------------------------------------------------------------------");
                LogManager.Log("Updating Firmware...");

                if (!_machineProvider.Machine.IsDemo)
                {
                    op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE;
                }
                else
                {
                    op.FirmwareUpgradeMode = FirmwareUpgradeModes.TFP_PACKAGE;
                }

                var handler = await op.UpgradeFirmware(stream);
                handler.Failed += (_, ex) =>
                {
                    stream.Dispose();
                    OnFailed(ex, result, version);
                };
                handler.Completed += (_, __) =>
                {
                    UpdateProgress("Updating Firmware", "Firmware update completed successfully.");
                    stream.Dispose();
                    OnCompleted(result, version);
                };
                handler.Canceled += (_, __) =>
                {
                    stream.Dispose();
                    OnFailed(new Exception("The operation has been canceled."), result, version);
                };
                handler.Progress += (_, e) =>
                {
                    UpdateProgress("Updating Firmware", e.Message, false, e.Current, e.Total);
                };
            }
            catch (Exception ex)
            {
                try
                {
                    if (stream != null)
                    {
                        stream.Dispose();
                    }
                }
                catch { }

                OnFailed(ex, result, version);
            }

            await result.Task;
        }

        /// <summary>
        /// Gets the update package file information.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <returns></returns>
        public Task<PublishInfo> GetUpdatePackageFileInfo(string filePath)
        {
            return Task.Factory.StartNew<PublishInfo>(() =>
            {
                using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile(filePath))
                {
                    var appEntry = zip.Entries.SingleOrDefault(x => x.FileName == "version.json");
                    var reader = appEntry.OpenReader();

                    using (StreamReader stReader = new StreamReader(reader))
                    {
                        String json = stReader.ReadToEnd();
                        reader.Dispose();

                        return PublishInfo.FromJson(json);
                    }
                }
            });
        }

        /// <summary>
        /// Checks whether any post update packages needs to be installed.
        /// </summary>
        /// <returns></returns>
        public Task<bool> PostUpdatePackagesRequired()
        {
            String packagesFolder = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "packages");
            return _packageRunner.IsPackageInstallationRequired(PackageType.Post, packagesFolder);
        }

        /// <summary>
        /// Runs all post update packages.
        /// </summary>
        /// <returns></returns>
        public Task<PackageRunnerResult> RunPostUpdatePackages()
        {
            String packagesFolder = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "packages");

            Version previousVersion = null;
            String str = _settings.PreviousApplicationVersion;

            if (Version.TryParse(str, out previousVersion))
            {
                return _packageRunner.Run(PackageType.Post, previousVersion, packagesFolder);
            }
            else
            {
                throw new InvalidCastException($"Error parsing the previous version string '{str}'.");
            }
        }

        #endregion

        #region Protected Methods

        protected virtual void UpdateProgress(String name, String message = "", bool isIntermediate = true, double progress = 0, double total = 0)
        {
            InvokeUINow(() =>
            {
                Status = new MachineUpdateProgress()
                {
                    Name = name,
                    Message = message,
                    IsIntermediate = isIntermediate,
                    Progress = progress,
                    Total = total,
                };

                Progress?.Invoke(this, Status);
            });

            UIHelper.DoEvents();
        }

        #endregion

        #region Auto Check For Update

        private async void _checkForUpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (EnableAutoCheckForUpdates && _settings.AutoCheckForUpdates && !_isUpdating)
            {
                _checkForUpdateTimer.Stop();

                try
                {
                    var response = await CheckForUpdate(_machineProvider.Machine.SerialNumber);
                    if (response.IsUpdateAvailable || response.IsDatabaseUpdateAvailable)
                    {
                        LogManager.Log($"New {(response.IsDatabaseUpdateAvailable ? "database updates" : "application version")} detected ({response.Version}). Raising event...");
                        UpdateAvailable?.Invoke(this, response);
                    }
                }
                catch { }

                _checkForUpdateTimer.Start();
            }
        }

        #endregion

        #region External Bridge

        public void OnReceiverDisconnected(ExternalBridgeReceiver receiver)
        {
            //Do nothing.
        }

        [ExternalBridgeRequestHandlerMethod(typeof(GetUpdatesAndPackagesRequest), RequestHandlerLoggingMode.LogRequestName)]
        public async Task OnGetUpdatesAndPackagesRequest(GetUpdatesAndPackagesRequest request, String token, ExternalBridgeReceiver receiver)
        {
            using (ObservablesContext db = ObservablesContext.CreateDefault())
            {
                var updates = await db.TangoUpdates.OrderByDescending(x => x.StartDate).ToListAsync();
                var updatesDTO = updates.Select(x => TangoUpdateDTO.FromObservable(x)).ToList();
                var packages = (await _packageRunner.GetPackagesFile()).PackageInstallations;

                var response = new GetUpdatesAndPackagesResponse();
                response.Updates.AddRange(updatesDTO);
                response.Packages.AddRange(packages);

                await receiver.SendGenericResponse(response, token);
            }
        }

        #endregion
    }
}