From d33c19b3ac6803de4b5c8d475832efef131c1a45 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Wed, 30 Dec 2020 15:11:34 +0000 Subject: Revert "Hope it is fine" --- .../MachineUpdate/MachineUpdateManager.cs | 1877 +++++++++++++++----- 1 file changed, 1478 insertions(+), 399 deletions(-) (limited to 'Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs') diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs index b7573ec60..c115f4f5b 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs @@ -9,6 +9,8 @@ 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; @@ -16,25 +18,47 @@ 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 + public class MachineUpdateManager : ExtendedObject, IMachineUpdateManager, IExternalBridgeRequestHandler { private IPPCApplicationManager _app_manager; private IMachineProvider _machineProvider; + private IPackageRunner _packageRunner; private PPCWebClient _client; + private List _logs; + private System.Timers.Timer _checkForUpdateTimer; + private bool _isUpdating; + private PPCSettings _settings; + private DateTime _updateStartDate; #region Events + /// + /// Occurs when an application update is available. + /// + public event EventHandler UpdateAvailable; + /// /// Occurs when there is a text log message available. /// @@ -50,12 +74,25 @@ namespace Tango.PPC.Common.MachineUpdate #region Properties private MachineUpdateProgress _status; + /// + /// Gets the current machine update progress status. + /// public MachineUpdateProgress Status { get { return _status; } private set { _status = value; RaisePropertyChangedAuto(); } } + private bool _autoCheckForUpdates; + /// + /// Gets or sets a value indicating whether to automatically check for new application updates. + /// + public bool EnableAutoCheckForUpdates + { + get { return _autoCheckForUpdates; } + set { _autoCheckForUpdates = value; RaisePropertyChangedAuto(); } + } + #endregion #region Constructors @@ -64,592 +101,1583 @@ namespace Tango.PPC.Common.MachineUpdate /// Initializes a new instance of the class. /// /// The application manager. - public MachineUpdateManager(PPCWebClient ppcWebClient, IPPCApplicationManager applicationManager, IMachineProvider machineProvider) + 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; - #endregion + _logs = new List(); + LogManager.NewLog += LogManager_NewLog; - #region Private Methods + _settings = SettingsManager.Default.GetOrCreate(); - private Task Login(String serialNumber) - { - return _client.Login(new LoginRequest() - { - Mode = LoginMode.Machine, - SerialNumber = serialNumber, - }); + _checkForUpdateTimer = new System.Timers.Timer(_settings.AutoUpdateCheckInterval.TotalMilliseconds); + _checkForUpdateTimer.Elapsed += _checkForUpdateTimer_Elapsed; + _checkForUpdateTimer.Stop(); + + externalBridge.RegisterRequestHandler(this); } #endregion - #region Public Methods + #region Event Handlers - /// - /// Performs a machine update using the specified serial number and machine service address. - /// - /// The serial number. - /// if set to true updates the embedded device firmware. - /// if set to true updates the embedded device FPGA version and other parameters. - /// - /// - /// Could not perform an update while the machine is not connected. - /// or - /// or - /// - /// Database tango does not exists. - public async Task Update(String serialNumber, bool setupFirmware, bool setupFPGA) + private void _app_manager_ApplicationReady(object sender, EventArgs e) { - TaskCompletionSource result = new TaskCompletionSource(); + _checkForUpdateTimer.Start(); - var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; - bool performDatabaseRollback = false; - String dbBackupFile = null; + if (!_app_manager.IsUpdateFailed) + { + ClearLastDatabaseBackup(); + } + } - try + 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) { - var machineServiceAddress = SettingsManager.Default.GetOrCreate().GetMachineServiceAddress(); + _logs.Add(e); + } + } - LogManager.Log($"Starting machine update for serial number {serialNumber}..."); + #endregion - //Connecting to machine... - LogManager.Log("Verifying machine connection and state..."); + #region Private Methods - UpdateProgress("Verifying machine state", "Initializing..."); + private Task Login(String machineGuid) + { + return _client.Login(new LoginRequest() + { + Mode = LoginMode.Machine, + MachineGuid = machineGuid, + }); + } - await Task.Delay(1000); + private async void OnFailed(Exception ex, TaskCompletionSource 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."); - IMachineOperator op = _machineProvider.MachineOperator; + await Task.Factory.StartNew(() => + { - if (setupFirmware) + if (performDatabaseRollback) { - LogManager.Log("Machine is configured to update firmware..."); + LogManager.Log("Rolling back database changes..."); - if (op.State != Transport.TransportComponentState.Connected) + using (DbManager db = DbManager.FromDataSource(localDataSource)) { - throw LogManager.Log(new InvalidOperationException("Could not perform an update while the machine is not connected.")); + 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); + } } - if (op.IsPrinting) + catch (Exception exx) { - throw LogManager.Log(new InvalidOperationException($"Could not perform an update while the machine is in {op.Status} status.")); + LogManager.Log(exx, "Error removing temporary database."); } } - //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; - - DownloadUpdateResponse update_response = null; - - update_response = await _client.MachineUpdate(request); - - LogManager.Log($"Machine update response received: {Environment.NewLine}{update_response.ToJsonString()}"); - - //Create temporary folders for packages. - var _newPackageTempFolder = TemporaryManager.CreateFolder(); - _newPackageTempFolder.Persist = true; - - 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..."); - - long fileSize = 0; - UpdateProgress("Downloading software package", "Downloading...", false); - - using (FileStreamWrapper fs = new FileStreamWrapper(tempFile.Path, FileMode.Create, (current) => + try { - UpdateProgress("Downloading software package", "Downloading...", false, current, fileSize); - })) + Directory.Delete(backupsFolder, true); + } + catch (Exception ee) { - LogManager.Log($"Connecting to storage blob with address {update_response.BlobAddress}"); - CloudBlockBlob blob = new CloudBlockBlob(new Uri(update_response.BlobAddress)); - LogManager.Log("Fetching blob attributes..."); - blob.FetchAttributes(); - fileSize = blob.Properties.Length; - LogManager.Log("Download size: " + fileSize + " bytes."); - LogManager.Log("Starting blob download..."); - blob.DownloadToStream(fs); + LogManager.Log(ee, $"Error deleting backups folder '{backupsFolder}'."); } - UpdateProgress("Downloading software package", "Extracting package..."); - - LogManager.Log("Extracting downloaded zip file..."); - //Extract software package. - ZipFile.ExtractToDirectory(tempFile, _newPackageTempFolder); + if (tempUpdatePackageFolder != null) + { + try + { + Directory.Delete(tempUpdatePackageFolder, true); + } + catch (Exception eee) + { + LogManager.Log(eee, "Error removing temporary package folder."); + } + } + }); - 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); + completionSource.SetException(ex); + String logs = GetLogsStringAndClear(); - //Synchronize database - UpdateProgress("Updating Database", "Initializing..."); + 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."); + } - LogManager.Log($"Synchronizing database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'..."); + 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."); + } + } - 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)) + if (tupPublishInfo != null) + { + try { - throw new InvalidProgramException("Database tango does not exists."); + 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(); + } } - - if (setupFirmware) + catch (Exception xxx) { - LogManager.Log("Setup firmware is active so a database rollback procedure should be configured."); - UpdateProgress("Updating Database", "Creating database backup..."); + LogManager.Log(xxx, "Error saving tango offline update information to database."); + } + } + + _isUpdating = false; + } + private async void OnCompleted(MachineUpdateResult result, TaskCompletionSource completionSource, DownloadUpdateResponse response, String tempDbName, String backupsFolder, Core.DataSource localDataSource, PublishInfo tupPublishInfo = null) + { + await Task.Factory.StartNew(() => + { + if (tempDbName != null) + { try { - Directory.CreateDirectory("C:\\Backups"); - dbBackupFile = $"C:\\Backups\\{Path.GetRandomFileName()}.bak"; - LogManager.Log($"Creating database backup to '{dbBackupFile}'..."); - await Task.Factory.StartNew(() => db.Backup(localDataSource.Catalog, dbBackupFile)); - LogManager.Log("Database backup created successfully."); + LogManager.Log($"Removing temporary database '{tempDbName}'..."); + using (DbManager dbManager = DbManager.FromDataSource(localDataSource)) + { + dbManager.SetOffline(tempDbName); + dbManager.SetOnline(tempDbName); + dbManager.Delete(tempDbName); + } } catch (Exception ex) { - throw LogManager.Log(ex, "Setup manager error while trying to create a database backup."); + LogManager.Log(ex, "Error removing temporary database."); } } - LogManager.Log("Disposing database manager."); - db.Dispose(); + //try + //{ + // Directory.Delete(backupsFolder, true); + //} + //catch (Exception ex) + //{ + // LogManager.Log(ex, $"Error deleting backups folder '{backupsFolder}'."); + //} - LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}..."); + if (!result.RequiresBinariesUpdate) + { + try + { + Directory.Delete(result.UpdatePackagePath, true); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error removing temporary package folder."); + } + } - 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); + completionSource.SetResult(result); - runner.Log += (x, msg) => + if (response != null) + { + try { - LogManager.Log(msg); - ProgressLog?.Invoke(this, msg); - }; - - runner.ScriptExecuting += (x, item) => + var r = await _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() + { + Token = response.NotifyCompletedToken, + Status = BL.Enumerations.TangoUpdateStatuses.UpdateCompleted, + }); + } + catch (Exception ex) { - LogManager.Log($"Executing script {item.ToString()}..."); - UpdateProgress("Updating Database", item.Name + "..."); - }; - - LogManager.Log("Starting synchronization process..."); + LogManager.Log(ex, "Error notifying update completed."); + } try { - await runner.Run(); - LogManager.Log("Synchronization completed successfully!"); - UpdateProgress("Updating Database", "Database synchronization completed successfully."); + 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) { - throw LogManager.Log(ex, "Setup manager error while trying to synchronize database."); + LogManager.Log(ex, "Error saving tango update information to database."); } + } - //Updating firmware - if (setupFirmware) + + if (tupPublishInfo != null) + { + try { - performDatabaseRollback = true; + 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."); + } + } - UpdateProgress("Updating Firmware", "Connecting to firmware device..."); - LogManager.Log(""); - LogManager.Log("-------------------------------------------------------------------------"); - LogManager.Log("Updating Firmware..."); + _isUpdating = false; + } - UpdateProgress("Updating Firmware", "Loading firmware package..."); - var tfpPath = Path.Combine(_newPackageTempFolder, "firmware_package.tfp"); - var stream = new FileStream(tfpPath, FileMode.Open); + 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 (setupFPGA) + if (performDatabaseRollback) + { + LogManager.Log("Rolling back database changes..."); + + using (DbManager db = DbManager.FromDataSource(localDataSource)) + { + try { - op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; + UpdateProgress("Rollback", "Rolling back database changes..."); + db.Restore(localDataSource.Catalog, dbBackupFile); + LogManager.Log("Database restored successfully."); } - else + catch (Exception e) { - op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU; + LogManager.Log(e, "Could not rollback the database."); + throw ex; } - - var handler = await op.UpgradeFirmware(stream); - handler.Failed += (_, ex) => + finally { - stream.Dispose(); - throw ex; - }; - handler.Completed += (_, __) => - { - UpdateProgress("Updating Firmware", "Firmware update completed successfully."); - stream.Dispose(); - result.SetResult(new MachineUpdateResult() + try { - UpdatePackagePath = _newPackageTempFolder, - }); - }; - handler.Canceled += (_, __) => - { - stream.Dispose(); - throw new Exception("The operation has been canceled."); - }; - handler.Progress += (_, e) => + File.Delete(dbBackupFile); + } + catch { } + } + } + } + + String logs = GetLogsStringAndClear(); + + if (response != null) + { + try + { + var r = _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() { - UpdateProgress("Updating Firmware", e.Message, false, e.Current, e.Total); - }; + Token = response.NotifyCompletedToken, + Status = BL.Enumerations.TangoUpdateStatuses.DatabaseFailed, + FailedReason = ex.FlattenMessage(), + FailedLog = logs, + }).Result; } - else + catch (Exception xx) + { + LogManager.Log(xx, "Error notifying database failed."); + } + + try { - result.SetResult(new MachineUpdateResult() + using (ObservablesContext db = ObservablesContext.CreateDefault()) { - UpdatePackagePath = _newPackageTempFolder, - }); + 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."); } } - catch (Exception ex) - { - LogManager.Log(ex, "An error occurred in machine update."); - if (performDatabaseRollback) + _isUpdating = false; + } + + private void OnCompleted(UpdateDBResponse response, bool completedWithNoDifferences = false) + { + if (response != null) + { + try { - LogManager.Log("Rolling back database changes..."); + 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."); + } - using (DbManager db = DbManager.FromDataSource(localDataSource)) + if (!completedWithNoDifferences) + { + try { - try - { - UpdateProgress("Rollback", "Rolling back database changes..."); - await Task.Factory.StartNew(() => db.Restore(localDataSource.Catalog, dbBackupFile)); - LogManager.Log("Database restored successfully."); - } - catch (Exception e) - { - LogManager.Log(e, "Could not rollback the database."); - throw ex; - } - finally + using (ObservablesContext db = ObservablesContext.CreateDefault()) { - try - { - File.Delete(dbBackupFile); - } - catch { } + 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 completionSource, String firmwareVersion) + { + LogManager.Log(ex, "An error occurred in firmware upgrade."); + + completionSource.SetException(ex); + String logs = GetLogsStringAndClear(); - result.SetException(ex); + 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(); + } } - finally + catch (Exception exx) { - try + LogManager.Log(exx, "Error saving firmware upgrade information to database."); + } + + _isUpdating = false; + } + + private void OnCompleted(TaskCompletionSource completionSource, String firmwareVersion) + { + LogManager.Log("Firmware upgrade completed successfully."); + completionSource.SetResult(true); + + try + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) { - File.Delete(dbBackupFile); + 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 { } + } + catch (Exception exx) + { + LogManager.Log(exx, "Error saving firmware upgrade information to database."); } - return await result.Task; + _isUpdating = false; + } + + private String GetLogsStringAndClear() + { + String logsString = String.Join(Environment.NewLine, _logs.ToList().Select(x => x.ToString())); + _logs.Clear(); + return logsString; + } + + private void ClearLastDatabaseBackup() + { + Task.Factory.StartNew(() => + { + try + { + var lastBackupFile = SettingsManager.Default.GetOrCreate().LastDatabaseBackupFile; + + if (File.Exists(lastBackupFile)) + { + File.Delete(lastBackupFile); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error removing last database backup file."); + } + }); } + #endregion + + #region Public Methods + /// - /// Checks if any update are available for the specified machine serial number. + /// Performs a machine update. /// - /// The serial number. - /// The machine service address. + /// if set to true updates the embedded device firmware. + /// if set to true updates the embedded device FPGA version and other parameters. /// - public Task CheckForUpdate(string serialNumber) + /// + /// Could not perform an update while the machine is not connected. + /// or + /// or + /// + /// Database tango does not exists. + public async Task Update(bool setupFirmware, bool setupFPGA) { - return Task.Factory.StartNew(() => + _updateStartDate = DateTime.UtcNow; + _logs.Clear(); + + TaskCompletionSource result = new TaskCompletionSource(); + + var localDataSource = SettingsManager.Default.GetOrCreate().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 machineGuid = _machineProvider.Machine.Guid; + + try { - var machineServiceAddress = SettingsManager.Default.GetOrCreate().GetMachineServiceAddress(); + _isUpdating = true; + + var machineServiceAddress = _settings.GetMachineServiceAddress(); + + LogManager.Log($"Starting machine update..."); + + //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}..."); - Login(serialNumber).GetAwaiter().GetResult(); + await Login(machineGuid); - LogManager.Log($"Checking if updates available..."); + DownloadUpdateRequest request = new DownloadUpdateRequest(); - CheckForUpdateRequest request = new CheckForUpdateRequest(); - request.SerialNumber = serialNumber; - request.Version = _app_manager.Version.ToString(); + update_response = await _client.MachineUpdate(request); - CheckForUpdateResponse update_response = null; + LogManager.Log($"Machine update response received: {Environment.NewLine}{update_response.ToJsonString()}"); - update_response = _client.CheckForUpdates(request).Result; + LogManager.Log($"Temporary package folder created: {_newPackageTempFolder}."); - LogManager.Log($"Check for update response received: {Environment.NewLine}{update_response.ToJsonString()}"); + //Download software package. + var tempFile = TemporaryManager.CreateFile(".zip"); - return update_response; - }); - } + LogManager.Log($"Temporary package zip file created: {tempFile}."); - /// - /// Updates all the "overwrite-able" database tables. - /// - /// The serial number. - /// The machine service address. - /// - public Task UpdateDB(DbCompareResult dbCompareResult, String serialNumber) - { - return Task.Factory.StartNew(() => - { - LogManager.Log("Starting database update..."); + 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); + + LogManager.Log("Initializing database manager..."); + DbManager db = DbManager.FromDataSource(localDataSource); + + //Create Database Backup + UpdateProgress("Updating Database", "Creating database backup..."); + try + { + Directory.CreateDirectory(backupsFolder); + dbBackupFile = $"{backupsFolder}\\{Path.GetRandomFileName()}.bak"; + _settings.LastDatabaseBackupFile = dbBackupFile; + _settings.Save(); + 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."); + } + + //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("Looking for update scripts configuration on application path..."); + LogManager.Log($"Synchronizing database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'..."); - String config_file = Path.Combine(PathHelper.GetStartupPath(), "Update Scripts", "config.xml"); + UpdateProgress("Updating Database", "Connecting to local database..."); - if (!File.Exists(config_file)) + LogManager.Log("Checking Tango database exists on the local machine..."); + if (!db.Exists(localDataSource.Catalog)) { - throw LogManager.Log(new FileNotFoundException($"Could not locate '{config_file}' file on application folder.")); + throw new InvalidProgramException("Database tango does not exists."); } - UpdateDBResponse update_response = dbCompareResult.UpdateDBResponse; + LogManager.Log("Disposing database manager."); + db.Dispose(); + + LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}..."); - var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; + 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, + machineGuid); + + 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 (setupFPGA) + { + op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; + } + else + { + op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU; + } + + var handler = await op.UpgradeFirmware(stream, _machineProvider.Machine.IsDemo); + 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, e.IsIndeterminate, 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; + } + + /// + /// Checks if any update are available for the specified machine. + /// + /// The machine service address. + /// + public Task CheckForUpdate() + { + return Task.Factory.StartNew(() => + { + _isUpdating = true; + + var machineServiceAddress = _settings.GetMachineServiceAddress(); + + LogManager.Log($"Connecting to machine service on {machineServiceAddress}..."); + + String machineGuid = _machineProvider.Machine.Guid; + + Login(machineGuid).GetAwaiter().GetResult(); + + LogManager.Log($"Checking if updates available..."); + + CheckForUpdateRequest request = new CheckForUpdateRequest(); + 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; + }); + } + + /// + /// Updates all the "overwrite-able" database tables. + /// + /// The machine service address. + /// + public Task UpdateDB(DbCompareResult dbCompareResult) + { + _updateStartDate = DateTime.UtcNow; + _logs.Clear(); + + return Task.Factory.StartNew(() => + { + _isUpdating = true; + UpdateDBResponse update_response = null; + var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; + bool performDatabaseRollback = false; + String dbBackupFile = null; + + try + { + LogManager.Log("Starting database update..."); + + if (_machineProvider.MachineOperator.IsPrinting) + { + throw LogManager.Log(new InvalidOperationException($"Could not perform a database update while the machine is dyeing.")); + } + + 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(_machineProvider.Machine.Guid); + } + + 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; + } + }); + } + + /// + /// Checks whether it is necessary to updates all the "overwrite-able" database tables. + /// + /// The machine service address. + /// + public Task UpdateDBCheck() + { + return Task.Factory.StartNew(() => + { + var machineServiceAddress = _settings.GetMachineServiceAddress(); + + LogManager.Log($"Checking if database update is required..."); + + 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(_machineProvider.Machine.Guid).Wait(); + + UpdateDBRequest request = new UpdateDBRequest(); + 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().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(_machineProvider.Machine.Guid); + } + + 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, + }; + }); + } + + /// + /// Performs a machine update using the specified software update package path. + /// + /// Name of the file. + /// + public async Task UpdateFromTUP(string fileName, bool setupFirmware, bool setupFPGA) + { + _updateStartDate = DateTime.UtcNow; + _logs.Clear(); + + TaskCompletionSource result = new TaskCompletionSource(); + + var localDataSource = SettingsManager.Default.GetOrCreate().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 (TUP) 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, + _machineProvider.Machine.Guid); + + 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($"Updating database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'..."); + LogManager.Log("Getting setup firmware/fpga directly from db.."); - UpdateProgress("Updating Database", "Initializing update sequence..."); + 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"); - ExaminerSequenceConfiguration config_sequence = ExaminerSequenceConfiguration.FromFile(config_file); + setupFirmware = bool.Parse(firmware); + setupFPGA = bool.Parse(fpga); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error getting new values of SETUP_FIRMWARE and SETUP_FPGA."); + } + } - foreach (var item in config_sequence.Items.Where(x => x.Type == ExaminerSequenceItemType.Data || update_response.PerformSchemaUpdate).OrderBy(x => x.Index)) + //Updating firmware + if (setupFirmware) { - LogManager.Log($"Executing update script '{item.FileName}...'"); + UpdateProgress("Updating Firmware", "Connecting to firmware device..."); + LogManager.Log(""); + LogManager.Log("-------------------------------------------------------------------------"); + LogManager.Log("Updating Firmware..."); - ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(Path.Combine(Path.GetDirectoryName(config_file), item.FileName)); - builder.SetSource(update_response.DataSource); - builder.SetTarget(localDataSource); + UpdateProgress("Updating Firmware", "Loading firmware package..."); + var tfpPath = Path.Combine(_newPackageTempFolder, "firmware_package.tfp"); + var stream = new FileStream(tfpPath, FileMode.Open); - if (item.RequiresSerialNumber) + if (setupFPGA) { - builder.SetMachineSerialNumber(serialNumber); + op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; + } + else + { + op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU; } - builder.Synchronize(); - - var config = builder.Build(); - - ExaminerProcess process = new ExaminerProcess(config, item.Type == ExaminerSequenceItemType.Data ? ExaminerProcessType.Data : ExaminerProcessType.Schema); - process.Progress += (x, msg) => + var handler = await op.UpgradeFirmware(stream, _machineProvider.Machine.IsDemo); + handler.Failed += (_, ex) => { - LogManager.Log(msg); + stream.Dispose(); + OnFailed(ex, result, null, performDatabaseRollback, dbBackupFile, backupsFolder, tempDbName, localDataSource, _newPackageTempFolder, publishInfo); }; - - try + handler.Completed += (_, __) => { - UpdateProgress("Updating Database", item.Name + "..."); - - var result = process.Execute().Result; - - if (result.ExitCode != ExaminerProcessExitCode.Success) + UpdateProgress("Updating Firmware", "Firmware update completed successfully."); + stream.Dispose(); + OnCompleted(new MachineUpdateResult() { - throw LogManager.Log(new InvalidDataException($"{item.FileName} script has terminated with exit code '{result.ExitCode}'.")); - } - - LogManager.Log("Script executed successfully."); - } - catch (Exception ex) + UpdatePackagePath = _newPackageTempFolder, + RequiresBinariesUpdate = replaceBinaries, + }, result, null, tempDbName, backupsFolder, localDataSource, publishInfo); + }; + handler.Canceled += (_, __) => { - throw LogManager.Log(ex, "Setup manager error while trying to update the database."); - } + 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, e.IsIndeterminate, 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); + } - UpdateProgress("Updating Database", "Database synchronization completed successfully."); - LogManager.Log("Update completed successfully."); - }); + return await result.Task; } /// - /// Checks whether it is necessary to updates all the "overwrite-able" database tables. + /// Performs a firmware upgrade from the specified TFP file. /// - /// The serial number. - /// The machine service address. + /// Name of the file. /// - public Task UpdateDBCheck(string serialNumber) + /// + /// Could not perform a firmware upgrade while the machine is not connected. + /// or + /// + public async Task UpdateFromTFP(String fileName) { - return Task.Factory.StartNew(() => - { - var machineServiceAddress = SettingsManager.Default.GetOrCreate().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"); + _updateStartDate = DateTime.UtcNow; + _logs.Clear(); - if (!File.Exists(config_file)) - { - throw LogManager.Log(new FileNotFoundException($"Could not locate '{config_file}' file on application folder.")); - } + TaskCompletionSource result = new TaskCompletionSource(); - LogManager.Log($"Connecting to machine service on {machineServiceAddress}..."); + String version = String.Empty; + Stream stream = null; - Login(serialNumber).Wait(); + try + { + _isUpdating = true; - UpdateDBRequest request = new UpdateDBRequest(); - request.SerialNumber = serialNumber; + IMachineOperator op = _machineProvider.MachineOperator; - UpdateDBResponse update_response = null; + UpdateProgress("Updating Firmware", "Loading firmware package..."); + stream = new FileStream(fileName, FileMode.Open); - update_response = _client.UpdateDB(request).Result; + var packageInfo = await op.GetFirmwarePackageInfo(stream); + stream.Position = 0; + version = packageInfo.FileDescriptors.FirstOrDefault(x => x.Destination == PMR.FirmwareUpgrade.VersionFileDestination.Mcu)?.Version; - LogManager.Log($"Update DB response received: {Environment.NewLine}{update_response.ToJsonString()}"); + LogManager.Log("Verifying machine connection and state..."); - var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; + UpdateProgress("Verifying machine state", "Initializing..."); - LogManager.Log($"Comparing database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'..."); + await Task.Delay(1000); - var report_file = TemporaryManager.CreateFile(".xml"); + 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.")); + } - ExaminerSequenceConfiguration config_sequence = ExaminerSequenceConfiguration.FromFile(config_file); + UpdateProgress("Updating Firmware", "Connecting to firmware device..."); + LogManager.Log(""); + LogManager.Log("-------------------------------------------------------------------------"); + LogManager.Log("Updating Firmware..."); - bool has_differences = false; + op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; - foreach (var item in config_sequence.Items.Where(x => x.Type == ExaminerSequenceItemType.Data).OrderBy(x => x.Index)) + var handler = await op.UpgradeFirmware(stream, _machineProvider.Machine.IsDemo); + handler.Failed += (_, ex) => { - 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) + 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, e.IsIndeterminate, e.Current, e.Total); + }; + } + catch (Exception ex) + { + try + { + if (stream != null) { - builder.SetMachineSerialNumber(serialNumber); + stream.Dispose(); } + } + catch { } - var config = builder.Build(); + OnFailed(ex, result, version); + } - ExaminerProcess process = new ExaminerProcess(config, ExaminerProcessType.Data); - process.Progress += (x, msg) => - { - LogManager.Log(msg); - }; + await result.Task; + } - LogManager.Log("Starting comparison process..."); - LogManager.Log("Generating report on " + report_file); + /// + /// Gets the update package file information. + /// + /// The file path. + /// + public Task GetUpdatePackageFileInfo(string filePath) + { + return Task.Factory.StartNew(() => + { + using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile(filePath)) + { + var appEntry = zip.Entries.SingleOrDefault(x => x.FileName == "version.json"); + var reader = appEntry.OpenReader(); - try + using (StreamReader stReader = new StreamReader(reader)) { - 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..."); + String json = stReader.ReadToEnd(); + reader.Dispose(); - 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) - { - throw LogManager.Log(ex, "Update manager error while trying to compare the database."); + return PublishInfo.FromJson(json); } } - - LogManager.Log("Comparison completed successfully."); - - return new DbCompareResult() - { - RequiresUpdate = has_differences, - UpdateDBResponse = update_response, - }; }); } /// - /// Performs a machine update using the specified software update package path. + /// Checks whether any post update packages needs to be installed. /// - /// Name of the file. /// - public Task UpdateFromTUP(string fileName) + public Task PostUpdatePackagesRequired() { - return Task.Factory.StartNew(() => - { - LogManager.Log($"Starting machine update from update package '{fileName}'..."); - - //Create temporary folders for packages. - var _newPackageTempFolder = TemporaryManager.CreateFolder(); - _newPackageTempFolder.Persist = true; - - LogManager.Log("Extracting downloaded zip file..."); - //Extract software package. - ZipFile.ExtractToDirectory(fileName, _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); - - LogManager.Log("Update operation completed!"); - - return new MachineUpdateResult() - { - UpdatePackagePath = _newPackageTempFolder, - }; - }); + String packagesFolder = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "packages"); + return _packageRunner.IsPackageInstallationRequired(PackageType.Post, packagesFolder); } /// - /// Gets the update package file information. + /// Runs all post update packages. /// - /// The file path. /// - public Task GetUpdatePackageFileInfo(string filePath) + public Task RunPostUpdatePackages() { - return Task.Factory.StartNew(() => - { - UpdatePackageFile file = new UpdatePackageFile(); - var tempFolder = TemporaryManager.CreateFolder(); + String packagesFolder = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "packages"); - using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile(filePath)) - { - var appEntry = zip.Entries.SingleOrDefault(x => x.FileName == "Tango.PPC.UI.exe"); - appEntry.Extract(tempFolder); - } + 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}'."); + } + } - FileVersionInfo info = FileVersionInfo.GetVersionInfo(Path.Combine(tempFolder, "Tango.PPC.UI.exe")); - file.Version = Version.Parse(info.ProductVersion); + public Task RestoreLastDatabaseBackup() + { + return Task.Factory.StartNew(() => + { + LogManager.Log("Rolling back database changes..."); + UpdateProgress("Rollback", "Rolling back database changes..."); - tempFolder.Delete(); + var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; + var lastBackupFile = SettingsManager.Default.GetOrCreate().LastDatabaseBackupFile; - return file; + using (DbManager db = DbManager.FromDataSource(localDataSource)) + { + try + { + db.Restore(localDataSource.Catalog, lastBackupFile); + LogManager.Log("Database restored successfully."); + } + catch (Exception ex) + { + LogManager.Log(ex, "Could not rollback the database after a failed updater."); + throw ex; + } + finally + { + try + { + File.Delete(lastBackupFile); + } + catch { } + } + } }); } @@ -677,5 +1705,56 @@ namespace Tango.PPC.Common.MachineUpdate } #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(); + 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 } } -- cgit v1.3.1