aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinition.cs
blob: 0b42cd426cd5316841b00ddaddd8eb62da235320 (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
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)

using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace Tango.Scripting.Editors.Highlighting
{
	/// <summary>
	/// A highlighting definition.
	/// </summary>
	[TypeConverter(typeof(HighlightingDefinitionTypeConverter))]
	public interface IHighlightingDefinition
	{
		/// <summary>
		/// Gets the name of the highlighting definition.
		/// </summary>
		string Name { get; }
		
		/// <summary>
		/// Gets the main rule set.
		/// </summary>
		HighlightingRuleSet MainRuleSet { get; }
		
		/// <summary>
		/// Gets a rule set by name.
		/// </summary>
		/// <returns>The rule set, or null if it is not found.</returns>
		HighlightingRuleSet GetNamedRuleSet(string name);
		
		/// <summary>
		/// Gets a named highlighting color.
		/// </summary>
		/// <returns>The highlighting color, or null if it is not found.</returns>
		HighlightingColor GetNamedColor(string name);
		
		/// <summary>
		/// Gets the list of named highlighting colors.
		/// </summary>
		IEnumerable<HighlightingColor> NamedHighlightingColors { get; }
	}
	
	/// <summary>
	/// Extension of IHighlightingDefinition to avoid breaking changes in the API.
	/// </summary>
	public interface IHighlightingDefinition2 : IHighlightingDefinition
	{
		/// <summary>
		/// Gets the list of properties.
		/// </summary>
		IDictionary<string, string> Properties { get; }
	}
}
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
    }
}