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; using Tango.PPC.Common.Build; namespace Tango.PPC.Common.MachineUpdate { public class MachineUpdateManager : ExtendedObject, IMachineUpdateManager, IExternalBridgeRequestHandler { private IPPCApplicationManager _app_manager; private IMachineProvider _machineProvider; private IPackageRunner _packageRunner; private IBuildProvider _buildProvider; 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. /// public event EventHandler ProgressLog; /// /// Occurs when the update has made some progress. /// public event EventHandler Progress; #endregion #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 /// /// Initializes a new instance of the class. /// /// The application manager. public MachineUpdateManager(PPCWebClient ppcWebClient, IPPCApplicationManager applicationManager, IMachineProvider machineProvider, IPackageRunner packageRunner, IPPCExternalBridgeService externalBridge, IBuildProvider buildProvider) { _client = ppcWebClient; _machineProvider = machineProvider; _app_manager = applicationManager; _app_manager.ApplicationReady += _app_manager_ApplicationReady; _packageRunner = packageRunner; _buildProvider = buildProvider; _packageRunner.PackageProgress += _packageRunner_PackageProgress; _logs = new List(); LogManager.NewLog += LogManager_NewLog; _settings = SettingsManager.Default.GetOrCreate(); _checkForUpdateTimer = new System.Timers.Timer(TimeSpan.FromSeconds(30).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(); if (!_app_manager.IsUpdateFailed) { ClearLastDatabaseBackup(); } } 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 machineGuid) { return _client.Login(new LoginRequest() { Mode = LoginMode.Machine, MachineGuid = machineGuid, }); } 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."); 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 = null, }); } 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.ApplicationVersionTag = response.Tag; 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.ApplicationVersionTag = tupPublishInfo.Tag; 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 completionSource, DownloadUpdateResponse response, String tempDbName, String backupsFolder, Core.DataSource localDataSource, PublishInfo tupPublishInfo = null, bool deleteBackupsFolder = false) { 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."); } } if (deleteBackupsFolder) { 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.ApplicationVersionTag = response.Tag; 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.ApplicationVersionTag = tupPublishInfo.Tag; 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 = null, }).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.ApplicationVersionTag = _app_manager.VersionTag; 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.ApplicationVersionTag = _app_manager.VersionTag; 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(); try { using (ObservablesContext db = ObservablesContext.CreateDefault()) { TangoUpdate update = new TangoUpdate(); update.ApplicationVersion = _app_manager.Version.ToString(); update.FirmwareVersion = firmwareVersion; update.ApplicationVersionTag = _app_manager.VersionTag; 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 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.ApplicationVersionTag = _app_manager.VersionTag; 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; } 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 /// /// Performs a machine update. /// /// 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(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; 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 { _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.Status == MachineStatuses.Printing || op.Status == MachineStatuses.GettingReady) { 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(machineGuid); DownloadUpdateRequest request = new DownloadUpdateRequest(); 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); 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($"Synchronizing database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'..."); UpdateProgress("Updating Database", "Connecting to local database..."); LogManager.Log("Checking Tango database exists on the local machine..."); if (!db.Exists(localDataSource.Catalog)) { throw new InvalidProgramException("Database tango does not exists."); } 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, 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) { _isUpdating = false; 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(() => { try { _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(); request.Tag = _app_manager.VersionTag; 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; } catch (Exception ex) { _isUpdating = false; throw ex; } }); } /// /// 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(() => { UpdateDBResponse update_response = null; var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; bool performDatabaseRollback = false; String dbBackupFile = null; try { _isUpdating = true; 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) { _isUpdating = false; 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("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 (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, 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, true); }; 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, e.IsIndeterminate, e.Current, e.Total); }; } else { OnCompleted(new MachineUpdateResult() { UpdatePackagePath = _newPackageTempFolder, RequiresBinariesUpdate = replaceBinaries, }, result, null, tempDbName, backupsFolder, localDataSource, publishInfo, true); } } catch (Exception ex) { _isUpdating = false; OnFailed(ex, result, null, performDatabaseRollback, dbBackupFile, backupsFolder, tempDbName, localDataSource, _newPackageTempFolder, publishInfo); } return await result.Task; } /// /// Performs a firmware upgrade from the specified TFP file. /// /// Name of the file. /// /// /// Could not perform a firmware upgrade while the machine is not connected. /// or /// public async Task UpdateFromTFP(String fileName) { _updateStartDate = DateTime.UtcNow; _logs.Clear(); TaskCompletionSource result = new TaskCompletionSource(); 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..."); op.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; var handler = await op.UpgradeFirmware(stream, _machineProvider.Machine.IsDemo); 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, e.IsIndeterminate, e.Current, e.Total); }; } catch (Exception ex) { _isUpdating = false; try { if (stream != null) { stream.Dispose(); } } catch { } OnFailed(ex, result, version); } await result.Task; } /// /// 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(); using (StreamReader stReader = new StreamReader(reader)) { String json = stReader.ReadToEnd(); reader.Dispose(); return PublishInfo.FromJson(json); } } }); } /// /// Checks whether any post update packages needs to be installed. /// /// public Task PostUpdatePackagesRequired() { String packagesFolder = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "packages"); return _packageRunner.IsPackageInstallationRequired(PackageType.Post, packagesFolder); } /// /// Runs all post update packages. /// /// public Task 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}'."); } } public Task RestoreLastDatabaseBackup() { return Task.Factory.StartNew(() => { LogManager.Log("Rolling back database changes..."); UpdateProgress("Rollback", "Rolling back database changes..."); var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; var lastBackupFile = SettingsManager.Default.GetOrCreate().LastDatabaseBackupFile; 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 { } } } }); } #endregion #region Protected Methods protected virtual void UpdateProgress(String name, String message = "", bool isIntermediate = true, double progress = 0, double total = 0) { InvokeUI(() => { Status = new MachineUpdateProgress() { Name = name, Message = message, IsIntermediate = isIntermediate, Progress = progress, Total = total, }; Progress?.Invoke(this, Status); }); } #endregion #region Auto Check For Update private async void _checkForUpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { _checkForUpdateTimer.Interval = _settings.AutoUpdateCheckInterval.TotalMilliseconds; 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 (Exception ex) { LogManager.Log(ex, "Error occurred while checking for software update."); } _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 } }