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" --- .../Application/IPPCApplicationManager.cs | 31 + .../Authentication/IAuthenticationProvider.cs | 11 + .../Tango.PPC.Common/BackupRestore/BackupFile.cs | 81 + .../Tango.PPC.Common/BackupRestore/BackupMode.cs | 23 + .../BackupRestoreProgressEventArgs.cs | 36 + .../BackupRestore/BackupRestoreStage.cs | 63 + .../BackupRestore/BackupSettings.cs | 19 + .../BackupRestore/DefaultBackupManager.cs | 604 +++++++ .../BackupRestore/IBackupManager.cs | 43 + .../BackupRestore/RestoreResult.cs | 14 + .../BackupRestore/RestoreSettings.cs | 31 + .../Connection/DefaultMachineProvider.cs | 136 +- .../Connection/IMachineProvider.cs | 15 + .../Connectivity/IConnectivityProvider.cs | 7 +- .../Console/DefaultConsoleEngineService.cs | 92 + .../Console/IConsoleEngineService.cs | 16 + .../Controls/ImageGalleryControl.cs | 114 ++ .../Controls/ImageGalleryControl.xaml | 101 ++ .../DataStore/DefaultDataStoreService.cs | 492 +++++ .../DataStore/IDataStoreService.cs | 14 + .../EventLogging/DefaultEventLogger.cs | 61 +- .../Tango.PPC.Common/EventLogging/IEventLogger.cs | 3 +- .../ExternalBridge/PPCExternalBridgeService.cs | 36 +- .../FileSystem/DefaultFileSystemService.cs | 433 +++++ .../FileSystem/FileSystemOperation.cs | 51 + .../FileSystem/FileSystemOperationMode.cs | 14 + .../FileSystem/IFileSystemService.cs | 20 + .../PPC/Tango.PPC.Common/Helpers/KeyboardHelper.cs | 24 + .../PPC/Tango.PPC.Common/Helpers/LogsHelper.cs | 24 + .../PPC/Tango.PPC.Common/IPPCService.cs | 36 + .../PPC/Tango.PPC.Common/Images/cl-full.png | Bin 0 -> 3454 bytes .../PPC/Tango.PPC.Common/Images/lubricant2.png | Bin 0 -> 194234 bytes .../Insights/DefaultInsightsService.cs | 154 ++ .../Tango.PPC.Common/Insights/IInsightsService.cs | 13 + .../MachineSetup/MachineSetupManager.cs | 200 ++- .../MachineUpdate/IMachineUpdateManager.cs | 59 +- .../MachineUpdate/MachineUpdateManager.cs | 1877 +++++++++++++++----- .../MachineUpdate/MachineUpdateResult.cs | 13 + .../MachineUpdate/UpdatePackageFile.cs | 13 - .../PPC/Tango.PPC.Common/Models/FineTuneItem.cs | 2 + .../Navigation/INavigationManager.cs | 17 +- .../Tango.PPC.Common/Navigation/NavigationView.cs | 3 +- .../Tango.PPC.Common/Notifications/AppBarItem.cs | 10 + .../Notifications/AppBarPriority.cs | 15 + .../Notifications/INotificationProvider.cs | 16 +- .../Notifications/NotificationItem.cs | 25 +- .../NotificationItems/MessageNotificationItem.cs | 3 +- .../MessageNotificationItemView.xaml | 14 +- .../OS/DefaultOperationSystemManager.cs | 22 + .../Tango.PPC.Common/OS/IOperationSystemManager.cs | 11 + .../PPC/Tango.PPC.Common/PPCSettings.cs | 190 +- .../PPC/Tango.PPC.Common/PPCViewModel.cs | 50 +- .../Performance/DefaultPerformanceService.cs | 219 +++ .../Performance/IPerformanceService.cs | 14 + .../Properties/Resources.Designer.cs | 22 +- .../PPC/Tango.PPC.Common/Properties/Resources.resx | 20 +- .../PPC/Tango.PPC.Common/Publish/PPCPublisher.cs | 97 +- .../PPC/Tango.PPC.Common/Publish/PublishInfo.cs | 22 +- .../PPC/Tango.PPC.Common/Publish/PublishOptions.cs | 25 +- .../RemoteActions/IRemoteActionsService.cs | 13 + .../DefaultRemoteAssistanceProvider.cs | 31 +- .../RemoteAssistance/IRemoteAssistanceProvider.cs | 4 +- .../RemoteDesktop/DefaultRemoteDesktopService.cs | 584 ++++++ .../RemoteDesktop/IRemoteDesktopService.cs | 24 + .../RemoteDesktop/RemoteDesktopClient.cs | 20 + .../RemoteJob/DefaultRemoteJobService.cs | 177 ++ .../RemoteJob/IRemoteJobService.cs | 14 + .../PPC/Tango.PPC.Common/Resources/Colors.xaml | 11 + .../PPC/Tango.PPC.Common/Resources/Merged.xaml | 10 + .../PPC/Tango.PPC.Common/Resources/Styles.xaml | 65 + .../PPC/Tango.PPC.Common/Resources/finger3.png | Bin 0 -> 1005 bytes .../PPC/Tango.PPC.Common/Resources/tap.png | Bin 0 -> 1316 bytes .../SQL/DefaultRemoteSqlService.cs | 59 + .../PPC/Tango.PPC.Common/SQL/IRemoteSqlService.cs | 13 + .../PPC/Tango.PPC.Common/SafetyLevelOperations.csv | 3 + .../Storage/DefaultStorageProvider.cs | 23 +- .../Tango.PPC.Common/Storage/IStorageProvider.cs | 6 +- .../DefaultMachineDataSynchronizer.cs | 662 +++++++ .../Synchronization/IMachineDataSynchronizer.cs | 26 + .../SynchronizationEndedEventArgs.cs | 15 + .../Synchronization/SynchronizationState.cs | 23 + .../Synchronization/SynchronizationStatus.cs | 47 + .../SynchronizationStatusChangedEventArgs.cs | 13 + .../SystemInfo/DefaultSystemInfoService.cs | 138 ++ .../SystemInfo/ISystemInfoService.cs | 17 + .../PPC/Tango.PPC.Common/Tango.PPC.Common.csproj | 141 +- .../ThreadLoading/IThreadLoadingService.cs | 14 + .../UWF/AlternativeUnifiedWriteFilterManager.cs | 48 +- .../UWF/DefaultUnifiedWriteFilterManager.cs | 35 +- .../UWF/IUnifiedWriteFilterManager.cs | 3 +- .../UpdatePackages/DefaultPackageRunner.cs | 362 ++++ .../Tango.PPC.Common/UpdatePackages/IPPCPackage.cs | 13 + .../UpdatePackages/IPackageRunner.cs | 18 + .../UpdatePackages/PPCPackageAttribute.cs | 26 + .../UpdatePackages/PackageContext.cs | 28 + .../UpdatePackages/PackageProgressEventArgs.cs | 17 + .../UpdatePackages/PackageRunnerResult.cs | 13 + .../UpdatePackages/PackageStateChangedEventArgs.cs | 16 + .../UpdatePackages/PackagesFile.cs | 19 + .../Tango.PPC.Common/Web/CheckForUpdateRequest.cs | 16 +- .../Tango.PPC.Common/Web/CheckForUpdateResponse.cs | 10 + .../Web/DownloadMachineDataRequest.cs | 24 + .../Web/DownloadMachineDataResponse.cs | 27 + .../Tango.PPC.Common/Web/DownloadUpdateRequest.cs | 2 +- .../Tango.PPC.Common/Web/DownloadUpdateResponse.cs | 6 + .../Tango.PPC.Common/Web/LatestVersionResponse.cs | 1 + .../PPC/Tango.PPC.Common/Web/LoginRequest.cs | 1 + .../Tango.PPC.Common/Web/MachineSetupRequest.cs | 1 - .../Tango.PPC.Common/Web/MachineSetupResponse.cs | 7 + .../Web/MachineUpdateCompletedRequest.cs | 19 + .../Web/MachineUpdateCompletedResponse.cs | 15 + .../NotifyMachineDataDownloadCompletedRequest.cs | 27 + .../NotifyMachineDataDownloadCompletedResponse.cs | 16 + .../PPC/Tango.PPC.Common/Web/PPCWebClient.cs | 5 + .../PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs | 45 + .../Web/SynchronizationFailedEntity.cs | 14 + .../PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs | 3 +- .../PPC/Tango.PPC.Common/Web/UpdateDBResponse.cs | 1 + .../PPC/Tango.PPC.Common/Web/UpdatedEntity.cs | 31 + .../Web/UploadMachineDataRequest.cs | 31 + .../Web/UploadMachineDataResponse.cs | 31 + .../PPC/Tango.PPC.Common/packages.config | 1 + 122 files changed, 8257 insertions(+), 599 deletions(-) create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupMode.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreProgressEventArgs.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupSettings.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Console/DefaultConsoleEngineService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Console/IConsoleEngineService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.xaml create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/DefaultDataStoreService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/IDataStoreService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperation.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperationMode.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/KeyboardHelper.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/LogsHelper.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/IPPCService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Images/cl-full.png create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Images/lubricant2.png create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/DefaultInsightsService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/IInsightsService.cs delete mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/UpdatePackageFile.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarPriority.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/DefaultPerformanceService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/IPerformanceService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteActions/IRemoteActionsService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/RemoteDesktopClient.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/DefaultRemoteJobService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/IRemoteJobService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/finger3.png create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/tap.png create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/DefaultRemoteSqlService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/IRemoteSqlService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/SafetyLevelOperations.csv create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/DefaultMachineDataSynchronizer.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/IMachineDataSynchronizer.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationEndedEventArgs.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationState.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatus.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatusChangedEventArgs.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/DefaultSystemInfoService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/ISystemInfoService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/ThreadLoading/IThreadLoadingService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/DefaultPackageRunner.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPPCPackage.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPackageRunner.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PPCPackageAttribute.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageContext.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageProgressEventArgs.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageRunnerResult.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageStateChangedEventArgs.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackagesFile.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataRequest.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataResponse.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedRequest.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedResponse.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedRequest.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedResponse.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/SynchronizationFailedEntity.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdatedEntity.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataRequest.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataResponse.cs (limited to 'Software/Visual_Studio/PPC/Tango.PPC.Common') diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs index 7c67ac1a3..a9d4e7c3a 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs @@ -45,6 +45,11 @@ namespace Tango.PPC.Common.Application /// event EventHandler SystemRestartRequired; + /// + /// Occurs when the updater utility has failed to perform the last update. + /// + event EventHandler UpdaterFailed; + /// /// Occurs when the application has encountered an error when initializing. /// @@ -60,6 +65,16 @@ namespace Tango.PPC.Common.Application /// bool IsInTechnicianMode { get; } + /// + /// Gets a value indicating whether an update has occurred before the application started. + /// + bool IsAfterUpdate { get; } + + /// + /// Gets a value indicating whether the updater utility has failed to perform the last update. + /// + bool IsUpdateFailed { get; } + /// /// Shutdown the application. /// @@ -90,6 +105,11 @@ namespace Tango.PPC.Common.Application /// Version Version { get; } + /// + /// Gets the firmware version. + /// + Version FirmwareVersion { get; } + /// /// Gets the application build date. /// @@ -100,6 +120,11 @@ namespace Tango.PPC.Common.Application /// DateTime StartUpDate { get; } + /// + /// Gets or sets the application folder. + /// + String StartPath { get; } + /// /// Gets or sets a value indicating whether the screen is currently locked. /// @@ -114,5 +139,11 @@ namespace Tango.PPC.Common.Application /// Invokes a dialog for entering a password and releasing the screen lock. /// void ReleaseScreenLock(); + + /// + /// Sets the state of the main window. + /// + /// The state. + void SetWindowState(WindowState state); } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Authentication/IAuthenticationProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Authentication/IAuthenticationProvider.cs index 33761c8d6..ca927e6df 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Authentication/IAuthenticationProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Authentication/IAuthenticationProvider.cs @@ -22,6 +22,11 @@ namespace Tango.PPC.Common.Authentication /// User CurrentUser { get; } + /// + /// Gets a value indicating whether the authentication provider is using a null user. + /// + bool AuthenticationRequired { get; } + /// /// Performs a user login by the specified email and password. /// @@ -31,6 +36,12 @@ namespace Tango.PPC.Common.Authentication /// Task Login(String email, String password, bool encrypt = true); + /// + /// Performs a fake login when no authentication is used. + /// + /// + Task Login(); + /// /// Logs-out the current logged-in user. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs new file mode 100644 index 000000000..c687377a6 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs @@ -0,0 +1,81 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PMR.Exports; +using Tango.Web; + +namespace Tango.PPC.Common.BackupRestore +{ + /// + /// Represents a backup file record. + /// + public class BackupFile + { + /// + /// Gets or sets the backup file version. + /// + public int Version { get; set; } + + /// + /// Gets or sets the backup name. + /// + public String Name { get; set; } + + /// + /// Gets or sets the creation date. + /// + public DateTime Date { get; set; } + + /// + /// Gets or sets the machine serial number. + /// + public String MachineSerialNumber { get; set; } + + /// + /// Gets or sets the application version. + /// + public String ApplicationVersion { get; set; } + + /// + /// Gets or sets the firmware version. + /// + public String FirmwareVersion { get; set; } + + /// + /// Gets or sets the settings file. + /// + public String SettingsFile { get; set; } + + /// + /// Gets or sets the job files. + /// + public List JobFiles { get; set; } + + /// + /// Gets or sets the backup settings. + /// + public BackupSettings Settings { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public BackupFile() + { + Settings = new BackupSettings(); + JobFiles = new List(); + } + + public String ToJson() + { + return JsonConvert.SerializeObject(this); + } + + public static BackupFile FromJson(String json) + { + return JsonConvert.DeserializeObject(json); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupMode.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupMode.cs new file mode 100644 index 000000000..8533ce22a --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupMode.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.BackupRestore +{ + /// + /// Represents a backup mode. + /// + public enum BackupMode + { + /// + /// Jobs only backup. + /// + Jobs, + /// + /// Complete backup of data, app binaries, firmware and settings file. + /// + Full, + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreProgressEventArgs.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreProgressEventArgs.cs new file mode 100644 index 000000000..d12db7b56 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreProgressEventArgs.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.BackupRestore +{ + /// + /// Represents a backup restore procedure progress. + /// + /// + public class BackupRestoreProgressEventArgs : EventArgs + { + /// + /// Gets or sets a value indicating whether the progress is intermediate. + /// + public bool IsIntermediate { get; set; } + + /// + /// Gets or sets the progress value. + /// + public double Progress { get; set; } + + + /// + /// Gets or sets the maximum progress. + /// + public double MaxProgress { get; set; } + + /// + /// Gets or sets the progress stage. + /// + public BackupRestoreStage Stage { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs new file mode 100644 index 000000000..4e0398237 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.BackupRestore +{ + /// + /// Represents a backup restore procedure stage. + /// + public enum BackupRestoreStage + { + [Description("Initializing")] + Initializing, + + //Backup + [Description("Backing up jobs...")] + BackingupJobs, + [Description("Backing up data...")] + BackingupDatabase, + [Description("Backing up application...")] + BackingupApplication, + [Description("Backing up user settings...")] + BackingupSettings, + [Description("Writing configuration...")] + WritingConfiguration, + [Description("Compressing files...")] + CompressingFiles, + [Description("Encrypting...")] + Encrypting, + + //Restore + [Description("Decrypting...")] + Decrypting, + [Description("Extracting backup configuration...")] + ExtractingBackupConfiguration, + [Description("Validating machine state...")] + ValidatingMachineState, + [Description("Extracting content...")] + ExtractingContent, + [Description("Restoring user settings...")] + RestoringSettings, + [Description("Restoring jobs...")] + RestoringJobs, + [Description("Restoring data...")] + RestoringDatabase, + [Description("Removing temporary files...")] + RemovingTemporaryFiles, + [Description("Restoring firmware version...")] + RestoringFirmware, + [Description("Rolling back changes...")] + RollingBackChanges, + + + [Description("Done")] + Done, + + [Description("Error")] + Error, + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupSettings.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupSettings.cs new file mode 100644 index 000000000..b2021ba39 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupSettings.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.BackupRestore +{ + /// + /// Represents a backup settings. + /// + public class BackupSettings + { + /// + /// Gets or sets the backup mode. + /// + public BackupMode Mode { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs new file mode 100644 index 000000000..d32df734d --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs @@ -0,0 +1,604 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; +using Tango.PPC.Common.Application; +using Tango.PPC.Common.BackupRestore; +using Tango.Core.ExtensionMethods; +using Tango.Core.DI; +using Tango.PPC.Common.Connection; +using System.IO; +using Tango.Core.Helpers; +using Tango.BL; +using Tango.Settings; +using Tango.Core.DB; +using System.Data.SqlClient; +using Ionic.Zip; +using Tango.BL.Entities; +using Tango.PPC.Common.Authentication; +using Tango.Integration.Upgrade; +using Tango.Core.IO; + +namespace Tango.PPC.Common.BackupRestore +{ + public class DefaultBackupManager : ExtendedObject, IBackupManager + { + private const string BACKUP_FILE_NAME = "Backup.json"; + private const string DATABASE_FILE_NAME = "Tango.bak"; + private const int VERSION = 1; + private const string PASSWORD = "1Creativity"; + + [TangoInject(TangoInjectMode.WhenAvailable)] + private IPPCApplicationManager _applicationManager; + + [TangoInject(TangoInjectMode.WhenAvailable)] + private IMachineProvider _machineProvider; + + [TangoInject(TangoInjectMode.WhenAvailable)] + private IAuthenticationProvider _authenticationProvider; + + public DefaultBackupManager() + { + TangoIOC.Default.Inject(this); + } + + public event EventHandler Progress; + + public Task CreateBackup(string filePath, String name, BackupSettings settings) + { + return Task.Factory.StartNew(() => + { + var tempFolder = TemporaryManager.CreateFolder(); + + try + { + //Basic + LogManager.Log($"Starting backup operation to file '{filePath}'..."); + LogManager.Log($"Backup settings:\n{settings.ToJsonString()}"); + OnProgress(BackupRestoreStage.Initializing); + + LogManager.Log($"Temporary folder created on {tempFolder.Path}."); + + BackupFile backupFile = new BackupFile(); + backupFile.Version = VERSION; + backupFile.Date = DateTime.Now; + backupFile.Settings = settings; + backupFile.Name = name; + backupFile.MachineSerialNumber = _machineProvider.Machine.SerialNumber; + backupFile.ApplicationVersion = _applicationManager.Version.ToString(); + + //Firmware + try + { + LogManager.Log("Extracting firmware version from local tfp package..."); + using (var st = File.OpenRead(Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "firmware_package.tfp"))) + { + backupFile.FirmwareVersion = _machineProvider.MachineOperator.GetFirmwarePackageInfo(st).Result.FileDescriptors.SingleOrDefault(x => x.Destination == PMR.FirmwareUpgrade.VersionFileDestination.Mcu).Version; + } + } + catch (Exception ex) + { + throw new FileLoadException("Could extract the firmware version from the TFP package.", ex); + } + + LogManager.Log($"Backup file generated:\n{backupFile.ToJsonString()}"); + + if (settings.Mode == BackupMode.Jobs) + { + //Jobs + LogManager.Log("Starting jobs backup..."); + OnProgress(BackupRestoreStage.BackingupJobs); + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + var jobs = db.Jobs.ToList(); + + foreach (var job in jobs) + { + try + { + LogManager.Log($"Backing up job '{job.Name}'..."); + var jobFile = job.ToJobFile().Result; + backupFile.JobFiles.Add(jobFile); + + OnProgress(BackupRestoreStage.BackingupJobs, jobs.IndexOf(job) + 1, jobs.Count, false); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error extracting job {job.Name}.", ex); + } + } + } + LogManager.Log("Jobs backup completed."); + } + else + { + //User Settings + LogManager.Log("Backing up application settings..."); + OnProgress(BackupRestoreStage.BackingupSettings); + backupFile.SettingsFile = File.ReadAllText(SettingsManager.Default.FilePath); + + //Application Version + LogManager.Log("Backing up application files..."); + OnProgress(BackupRestoreStage.BackingupApplication); + + try + { + PathHelper.CopyDirectory(AssemblyHelper.GetCurrentAssemblyFolder(), tempFolder, true, (current, total) => + { + OnProgress(BackupRestoreStage.BackingupApplication, current, total, false); + }); + } + catch (Exception ex) + { + throw new IOException($"Error occurred while copying application files.", ex); + } + + //Database + LogManager.Log("Backing up database..."); + OnProgress(BackupRestoreStage.BackingupDatabase); + try + { + var dataSource = ObservablesContext.GetActualDataSource(); + using (var dbManager = DbManager.FromDataSource(dataSource)) + { + Directory.CreateDirectory("C:\\Backups"); + var dbBackupFile = $"C:\\Backups\\{DATABASE_FILE_NAME}"; + if (File.Exists(dbBackupFile)) + { + File.Delete(dbBackupFile); + } + dbManager.Backup(dataSource.Catalog, dbBackupFile); + File.Move(dbBackupFile, Path.Combine(tempFolder, DATABASE_FILE_NAME)); + } + } + catch (Exception ex) + { + throw new IOException("Error creating database backup", ex); + } + + LogManager.Log("Database backup completed."); + } + + //Backup.json + try + { + OnProgress(BackupRestoreStage.WritingConfiguration); + var backupFilePath = Path.Combine(tempFolder, BACKUP_FILE_NAME); + LogManager.Log($"Writing backup configuration file '{backupFilePath}'..."); + File.WriteAllText(backupFilePath, backupFile.ToJsonString()); + } + catch (Exception ex) + { + throw new IOException("Error writing backup configuration file.", ex); + } + + //Compression + LogManager.Log($"Generating {filePath}..."); + using (ZipFile zip = new ZipFile()) + { + zip.Password = PASSWORD; + zip.AddDirectory(tempFolder); + + zip.SaveProgress += (x, e) => + { + if (e.EventType == ZipProgressEventType.Saving_AfterWriteEntry) + { + LogManager.Log($"Compressing '{e.CurrentEntry.FileName}'..."); + OnProgress(BackupRestoreStage.CompressingFiles, e.EntriesSaved + 1, e.EntriesTotal, false); + } + }; + + zip.ParallelDeflateThreshold = -1; + zip.Save(filePath); + } + + //Done + LogManager.Log("Backup operation completed!!!"); + OnProgress(BackupRestoreStage.Done, 100, 100, false); + tempFolder.Delete(); + } + catch (Exception ex) + { + tempFolder.Delete(); + + OnProgress(BackupRestoreStage.Error, 100, 100, false); + LogManager.Log(ex, "Could not complete the backup operation."); + throw ex; + } + }); + } + + /// + /// Extracts the backup configuration from the specified backup file. + /// + /// The file path. + /// + public Task ExtractBackupConfiguration(string filePath) + { + return Task.Factory.StartNew(() => + { + using (ZipFile zip = ZipFile.Read(filePath)) + { + zip.Password = PASSWORD; + var reader = zip.Entries.SingleOrDefault(x => x.FileName == BACKUP_FILE_NAME).OpenReader(); + String json = String.Empty; + + using (StreamReader stReader = new StreamReader(reader)) + { + json = stReader.ReadToEnd(); + } + + var backupFile = BackupFile.FromJson(json); + reader.Close(); + reader.Dispose(); + return backupFile; + } + }); + } + + public Task Restore(string filePath, RestoreSettings settings) + { + TaskCompletionSource completionSource = new TaskCompletionSource(); + + String dbRollbackFile = null; + bool shouldRollback = false; + + Task.Factory.StartNew(() => + { + LogManager.Log($"Starting restore operation from file '{filePath}'..."); + OnProgress(BackupRestoreStage.Initializing); + + var tempFolder = TemporaryManager.CreateFolder(); + tempFolder.Persist = true; + + var restoreResult = new RestoreResult() { FolderPath = tempFolder }; + + try + { + LogManager.Log("Extracting backup file configuration..."); + + BackupFile backupFile = null; + + //Extract Configuration + try + { + OnProgress(BackupRestoreStage.ExtractingBackupConfiguration); + backupFile = ExtractBackupConfiguration(filePath).Result; + restoreResult.BackupFile = backupFile; + LogManager.Log($"Backup settings:\n{backupFile.Settings.ToJsonString()}"); + } + catch (Exception ex) + { + throw new IOException("Error extracting backup configuration.", ex); + } + + //Validate Version + if (backupFile.Version > VERSION) + { + throw new NotSupportedException($"Backup file version {backupFile} is not supported."); + } + + //Validate Machine Serial Number + if (backupFile.Settings.Mode == BackupMode.Full && backupFile.MachineSerialNumber != _machineProvider.Machine.SerialNumber) + { + throw new InvalidOperationException($"The specified backup file targets machine '{backupFile.MachineSerialNumber}'. Cannot perform the restore operation."); + } + + //Validate Machine State + LogManager.Log("Validating machine state..."); + OnProgress(BackupRestoreStage.ValidatingMachineState); + if (_machineProvider.MachineOperator.IsPrinting) + { + LogManager.Log("The machine is currently printing. Aborting!"); + throw new InvalidOperationException("Cannot perform restore operation while machine is dyeing."); + } + + if (backupFile.Settings.Mode == BackupMode.Full && _machineProvider.MachineOperator.State != Transport.TransportComponentState.Connected) + { + LogManager.Log("Backup is configured to restore the firmware but machine is not connected!"); + throw new InvalidOperationException("The restore operation is configured to restore the firmware version but the machine is currently disconnected."); + } + + //Create Restore Point + try + { + LogManager.Log("Creating database rollback file..."); + var dataSource = ObservablesContext.GetActualDataSource(); + using (var dbManager = DbManager.FromDataSource(dataSource)) + { + Directory.CreateDirectory("C:\\Backups"); + dbRollbackFile = $"C:\\Backups\\{Path.GetRandomFileName()}.bak"; + LogManager.Log($"Creating database rollback to '{dbRollbackFile}'..."); + dbManager.Backup(dataSource.Catalog, dbRollbackFile); + LogManager.Log("Database rollback created successfully."); + shouldRollback = true; + } + } + catch (Exception ex) + { + throw new InvalidDataException("Error creating database rollback file.", ex); + } + + if (backupFile.Settings.Mode == BackupMode.Jobs) + { + //Restore Jobs + OnProgress(BackupRestoreStage.RestoringJobs); + LogManager.Log("Starting jobs restore..."); + + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + var jobs = db.Jobs.ToList(); + var jobFiles = backupFile.JobFiles; + + if (settings.AllowDeleteJobs) + { + try + { + LogManager.Log("Removing existing jobs..."); + foreach (var job in jobs.ToList()) + { + LogManager.Log($"Removing job '{job.Name}'..."); + job.Delete(db); + jobs.Remove(job); + } + + db.SaveChanges(); + } + catch (Exception ex) + { + throw new Exception("Error removing existing jobs from database.", ex); + } + } + + foreach (var jobFile in jobFiles) + { + LogManager.Log($"Importing job '{jobFile.Name}'..."); + + try + { + var existingJob = jobs.FirstOrDefault(x => x.Name == jobFile.Name); + + if (existingJob != null) + { + if (settings.OverwriteExistingJobs) + { + try + { + LogManager.Log("Job already exist, overwriting..."); + + var newJob = Job.FromJobFile(jobFile, _machineProvider.Machine.Guid, null).Result; + newJob.Guid = existingJob.Guid; + + existingJob.Delete(db); + jobs.Remove(existingJob); + + db.SaveChanges(); + db.Jobs.Add(newJob); + db.SaveChanges(); + } + catch (Exception ex) + { + throw new InvalidOperationException("Error overwriting job.", ex); + } + } + } + else + { + var newJob = Job.FromJobFile(jobFile, _machineProvider.Machine.Guid, null).Result; + db.Jobs.Add(newJob); + } + } + catch (Exception ex) + { + throw new InvalidOperationException("Error importing job.", ex); + } + + OnProgress(BackupRestoreStage.RestoringJobs, jobFiles.IndexOf(jobFile) + 1, jobFiles.Count, false); + } + + OnProgress(BackupRestoreStage.RestoringJobs); + db.SaveChanges(); + } + + LogManager.Log("Jobs restored successfully."); + OnProgress(BackupRestoreStage.Done, 100, 100, false); + completionSource.SetResult(restoreResult); + } + else + { + //Extract zip file + LogManager.Log("Starting backup file extraction..."); + OnProgress(BackupRestoreStage.ExtractingContent); + try + { + using (ZipFile zip = new ZipFile(filePath)) + { + zip.Password = PASSWORD; + + zip.ExtractProgress += (x, e) => + { + if (e.EventType == ZipProgressEventType.Extracting_AfterExtractEntry) + { + LogManager.Log($"Extracting '{e.CurrentEntry.FileName}'..."); + OnProgress(BackupRestoreStage.ExtractingContent, e.EntriesExtracted + 1, e.EntriesTotal, false); + } + }; + + zip.ParallelDeflateThreshold = -1; + zip.ExtractAll(tempFolder); + } + } + catch (Exception ex) + { + throw new IOException("Error extracting backup content.", ex); + } + + //Overwrite settings + LogManager.Log("Validating user settings..."); + if (backupFile.SettingsFile != null) + { + try + { + LogManager.Log("Overwriting settings file..."); + OnProgress(BackupRestoreStage.RestoringSettings); + File.WriteAllText(SettingsManager.Default.FilePath, backupFile.SettingsFile); + } + catch (Exception ex) + { + throw new IOException("Error overwriting user settings.", ex); + } + } + else + { + LogManager.Log("No user settings, skipping..."); + } + + //Restore database + var backupFilePath = Path.Combine(tempFolder, DATABASE_FILE_NAME); + LogManager.Log($"Looking for file database backup on '{backupFilePath}'..."); + if (File.Exists(backupFilePath)) + { + LogManager.Log("Restoring database..."); + OnProgress(BackupRestoreStage.RestoringDatabase); + try + { + var dataSource = ObservablesContext.GetActualDataSource(); + using (var dbManager = DbManager.FromDataSource(dataSource)) + { + Directory.CreateDirectory("C:\\Backups"); + var dbBackupFile = $"C:\\Backups\\{DATABASE_FILE_NAME}"; + File.Copy(backupFilePath, dbBackupFile, true); + dbManager.Restore(dataSource.Catalog, dbBackupFile); + File.Delete(dbBackupFile); + } + } + catch (Exception ex) + { + throw new IOException("Error restoring database backup", ex); + } + + LogManager.Log("Database backup completed."); + } + else + { + LogManager.Log("Database backup file not found, skipping..."); + } + + //Remove extra files from application temp folder + OnProgress(BackupRestoreStage.RemovingTemporaryFiles); + LogManager.Log("Removing redundant files from temp folder..."); + try + { + File.Delete(backupFilePath); + } + catch { } + try + { + File.Delete(Path.Combine(tempFolder, BACKUP_FILE_NAME)); + } + catch { } + + //Update firmware + var tfpFile = Path.Combine(tempFolder, "firmware_package.tfp"); + LogManager.Log($"Looking for tfp file on '{tfpFile}'..."); + if (File.Exists(tfpFile)) + { + OnProgress(BackupRestoreStage.RestoringFirmware); + LogManager.Log("Restoring firmware version..."); + + var stream = new FileStream(tfpFile, FileMode.Open); + + _machineProvider.MachineOperator.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; + + + var handler = _machineProvider.MachineOperator.UpgradeFirmware(stream, _machineProvider.Machine.IsDemo).Result; + handler.Failed += (_, ex) => + { + stream.Dispose(); + OnRestoreException(ex, completionSource, shouldRollback, dbRollbackFile, tempFolder); + }; + handler.Completed += (_, __) => + { + OnProgress(BackupRestoreStage.RestoringFirmware, 100, 100, false); + stream.Dispose(); + LogManager.Log("Full backup restored successfully."); + OnProgress(BackupRestoreStage.Done, 100, 100, false); + completionSource.SetResult(restoreResult); + }; + handler.Canceled += (_, __) => + { + stream.Dispose(); + OnRestoreException(new Exception("The operation has been canceled."), completionSource, shouldRollback, dbRollbackFile, tempFolder); + }; + handler.Progress += (_, e) => + { + OnProgress(BackupRestoreStage.RestoringFirmware, e.Current, e.Total, false); + }; + } + else + { + LogManager.Log("Firmware package file not found, skipping..."); + OnProgress(BackupRestoreStage.Done, 100, 100, false); + completionSource.SetResult(restoreResult); + } + } + } + catch (Exception ex) + { + OnRestoreException(ex, completionSource, shouldRollback, dbRollbackFile, tempFolder); + } + }); + + return completionSource.Task; + } + + private void OnRestoreException(Exception ex, TaskCompletionSource completionSource, bool shouldRollback, String dbRollbackFile, TemporaryFolder tempFolder) + { + if (shouldRollback) + { + LogManager.Log("Rolling back database changes..."); + + var dataSource = ObservablesContext.GetActualDataSource(); + using (var dbManager = DbManager.FromDataSource(dataSource)) + { + try + { + OnProgress(BackupRestoreStage.RollingBackChanges); + dbManager.Restore(dataSource.Catalog, dbRollbackFile); + LogManager.Log("Database restored successfully."); + } + catch (Exception e) + { + LogManager.Log(e, "Error rolling back database."); + } + finally + { + try + { + File.Delete(dbRollbackFile); + } + catch { } + } + } + } + + tempFolder.Delete(); + OnProgress(BackupRestoreStage.Error, 100, 100, false); + LogManager.Log(ex, "Could not complete the restore operation."); + completionSource.SetException(ex); + } + + protected virtual void OnProgress(BackupRestoreStage stage, double progress = 0, double maxProgress = 100, bool isIntermediate = true) + { + Progress?.Invoke(this, new BackupRestoreProgressEventArgs() + { + Stage = stage, + Progress = progress, + MaxProgress = maxProgress, + IsIntermediate = isIntermediate, + }); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs new file mode 100644 index 000000000..ae1884677 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.BackupRestore +{ + /// + /// Represents a backup/restore manager. + /// + public interface IBackupManager + { + /// + /// Occurs when the backup/restore procedure makes progress. + /// + event EventHandler Progress; + + /// + /// Creates a backup file containing database, application and firmware versions. + /// + /// The file path. + /// The backup name. + /// Backup configuration. + /// + Task CreateBackup(String filePath, String name, BackupSettings settings); + + /// + /// Restores a backup located in the specified file path. + /// + /// The file path. + /// The restore settings + /// + Task Restore(String filePath, RestoreSettings settings); + + /// + /// Extracts the backup configuration from the specified backup file. + /// + /// The file path. + /// + Task ExtractBackupConfiguration(String filePath); + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs new file mode 100644 index 000000000..5f10aebcf --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.BackupRestore +{ + public class RestoreResult + { + public String FolderPath { get; set; } + public BackupFile BackupFile { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs new file mode 100644 index 000000000..a5b343302 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.PPC.Common.BackupRestore +{ + public class RestoreSettings : ExtendedObject + { + private bool _allowDeleteJobs; + public bool AllowDeleteJobs + { + get { return _allowDeleteJobs; } + set { _allowDeleteJobs = value; RaisePropertyChangedAuto(); } + } + + private bool _overwriteExistingJobs; + public bool OverwriteExistingJobs + { + get { return _overwriteExistingJobs; } + set { _overwriteExistingJobs = value; RaisePropertyChangedAuto(); } + } + + public RestoreSettings() + { + OverwriteExistingJobs = true; + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs index a16e2f649..b2c752ca8 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs @@ -21,6 +21,7 @@ using Tango.Integration; using Tango.Transport; using System.Threading; using Tango.Core.ExtensionMethods; +using System.IO.Ports; namespace Tango.PPC.Common.Connection { @@ -35,6 +36,26 @@ namespace Tango.PPC.Common.Connection private Thread _connection_thread; private ObservablesContext _context; + /// + /// Occurs when the machine has connected. + /// + public event EventHandler MachineConnected; + + /// + /// Occurs when the machine has disconnected. + /// + public event EventHandler MachineDisconnected; + + private bool _isConnected; + /// + /// Gets a value indicating whether the machine is currently connected. + /// + public bool IsConnected + { + get { return _isConnected; } + private set { _isConnected = value; RaisePropertyChangedAuto(); } + } + private Machine _machine; /// /// Gets the database machine entity associated with the current machine. @@ -74,18 +95,24 @@ namespace Tango.PPC.Common.Connection /// public DefaultMachineProvider() { + var settings = SettingsManager.Default.GetOrCreate(); + MachineOperator = new MachineOperator(); + MachineOperator.StatusChanged += MachineOperator_StatusChanged; MachineOperator.EnableEventsNotification = true; MachineOperator.EnableJobResume = true; MachineOperator.UseKeepAlive = true; MachineOperator.EnableMachineStatusUpdates = true; - MachineOperator.EnableDiagnostics = false; - MachineOperator.EnableEmbeddedDebugging = false; - MachineOperator.FirmwareUpgradeMode = Integration.Upgrade.FirmwareUpgradeModes.DFU | Integration.Upgrade.FirmwareUpgradeModes.TFP_PACKAGE; + MachineOperator.EnableDiagnostics = true; + MachineOperator.EnablePowerUpSequence = true; + MachineOperator.EnableEmbeddedDebugging = settings.EnableEmbeddedDebugLogs; + MachineOperator.EnableAutomaticThreadLoading = settings.EnableAutomaticThreadLoading; + MachineOperator.JobRunsLogger.JobSource = BL.Enumerations.JobSource.Local; - var settings = SettingsManager.Default.GetOrCreate(); + MachineOperator.FirmwareUpgradeMode = Integration.Upgrade.FirmwareUpgradeModes.DFU | Integration.Upgrade.FirmwareUpgradeModes.TFP_PACKAGE; MachineOperator.JobUploadStrategy = settings.JobUploadStrategy; + MachineOperator.JobUnitsMethod = settings.JobUnitsMethod; MachineOperator.GradientGenerationConfiguration.IsEnabled = settings.EnableGradientGeneration; MachineOperator.GradientGenerationConfiguration.ResolutionCM = settings.GradientGenerationResolution; @@ -96,8 +123,28 @@ namespace Tango.PPC.Common.Connection MachineOperator.EnableJobLiquidQuantityValidation = settings.EnableJobLiquidQuantityValidation; } + private void MachineOperator_StatusChanged(object sender, MachineStatuses status) + { + if (status != MachineStatuses.Disconnected) + { + if (!IsConnected) + { + OnMachineConnected(); + } + } + else + { + if (IsConnected) + { + OnMachineDisconnected(); + } + } + } + private async void ConnectionThreadMethod() { + bool fileLoggingDisabled = false; + while (true) { if (MachineOperator.State != TransportComponentState.Connected) @@ -106,7 +153,10 @@ namespace Tango.PPC.Common.Connection { Thread.Sleep(2000); - LogManager.Log("Starting machine connection procedure...", LogCategory.Debug); + if (!fileLoggingDisabled) + { + LogManager.Log("Starting machine connection procedure...", LogCategory.Info); + } var settings = SettingsManager.Default.GetOrCreate(); @@ -116,22 +166,28 @@ namespace Tango.PPC.Common.Connection { TimeSpan timeout = TimeSpan.FromSeconds(SettingsManager.Default.GetOrCreate().MachineScanningTimeoutSeconds); - LogManager.Log("Scanning for machine on available serial ports...", LogCategory.Debug); + LogManager.Log("Scanning for machine on available serial ports...", LogCategory.Info); Transport.Discovery.UsbCommunicationScanner scanner = new Transport.Discovery.UsbCommunicationScanner(UsbSerialBaudRates.BR_115200); var response = await scanner.Scan(new ConnectRequest() { Password = "1234" }, settings.EmbeddedDeviceHint, timeout); - LogManager.Log("Machine discovered on port: " + response.Adapter.Address, LogCategory.Debug); + LogManager.Log("Machine discovered on port: " + response.Adapter.Address, LogCategory.Info); LogManager.Log("Device Information:", LogCategory.Debug); - LogManager.Log(response.Response.DeviceInformation.ToJsonString(), LogCategory.Debug); + LogManager.Log(response.Response.DeviceInformation.ToJsonString(), LogCategory.Info); - LogManager.Log("Disconnecting machine operator...", LogCategory.Debug); + LogManager.Log("Disconnecting machine operator...", LogCategory.Info); await MachineOperator.Disconnect(); MachineOperator.Adapter = response.Adapter; MachineOperator.JobHandlingMode = JobHandlerModes.SettingUp; - LogManager.Log("Connecting machine operator...", LogCategory.Debug); + LogManager.Log("Connecting machine operator...", LogCategory.Info); try { await MachineOperator.Connect(); + + if (MachineOperator.DeviceInformation != null) + { + settings.FirmwareVersion = MachineOperator.DeviceInformation.Version; + settings.Save(); + } } catch (Exception) { @@ -146,7 +202,17 @@ namespace Tango.PPC.Common.Connection } else { - LogManager.Log($"Connecting to machine on {settings.EmbeddedComPort}...", LogCategory.Debug); + //Perform a pre-test to not overload the log file when machine is off for a long time. + using (SerialPort preCheckSerialPort = new SerialPort(settings.EmbeddedComPort)) + { + preCheckSerialPort.BaudRate = UsbSerialBaudRates.BR_115200.ToInt32(); + preCheckSerialPort.Open(); + preCheckSerialPort.Close(); + fileLoggingDisabled = false; + Thread.Sleep(500); //Wait a little while to not scare the other side?.. + } + + LogManager.Log($"Connecting to machine on {settings.EmbeddedComPort}...", LogCategory.Info); UsbTransportAdapter adapter = new UsbTransportAdapter(settings.EmbeddedComPort, UsbSerialBaudRates.BR_115200); MachineOperator.Adapter = adapter; @@ -154,6 +220,12 @@ namespace Tango.PPC.Common.Connection try { await MachineOperator.Connect(); + + if (MachineOperator.DeviceInformation != null) + { + settings.FirmwareVersion = MachineOperator.DeviceInformation.Version; + settings.Save(); + } } catch (Exception) { @@ -184,13 +256,24 @@ namespace Tango.PPC.Common.Connection LogManager.Log("Connecting machine operator..."); await MachineOperator.Connect(); + if (MachineOperator.DeviceInformation != null) + { + settings.FirmwareVersion = MachineOperator.DeviceInformation.Version; + settings.Save(); + } + await Task.Delay(1000); await MachineOperator.UploadHardwareConfiguration(Machine.Configuration.HardwareVersion, Machine.Configuration); } } catch (Exception ex) { - LogManager.Log(ex, LogCategory.Debug, "Error while trying to scan and connect to the machine."); + if (!fileLoggingDisabled || LogManager.Categories.Contains(LogCategory.Debug)) + { + LogManager.Log(ex, "Error while trying to scan and connect to the machine."); + LogManager.Log("Application logging of further connection attempts is now disabled and will resume when connection is successful."); + fileLoggingDisabled = true; + } } } @@ -217,6 +300,15 @@ namespace Tango.PPC.Common.Connection if (Machine != null) { LogManager.Log("First machine entry found. Machine serial number is: " + Machine.SerialNumber + "."); + + if (Machine.IsDemo) + { + LogManager.Log("Machine is in demo mode. Changing firmware upgrade mode to TFP package only."); + MachineOperator.FirmwareUpgradeMode = Integration.Upgrade.FirmwareUpgradeModes.TFP_PACKAGE; + } + + MachineOperator.JobRunsLogger.SetDefaultMachine(Machine); + ConnectToMachine(); } else @@ -247,7 +339,7 @@ namespace Tango.PPC.Common.Connection public async Task SaveMachine() { await _context.SaveChangesAsync(); - Machine = await new MachineBuilder(_context).SetFirst().WithSettings().BuildAsync(); + Machine = await new MachineBuilder(_context).SetFirst().BuildAsync(); TangoMessenger.Default.Send(new MachineSettingsSavedMessage() { Machine = Machine }); } @@ -304,5 +396,23 @@ namespace Tango.PPC.Common.Connection return machineOperator; } + + /// + /// Called when the machine has connected. + /// + protected virtual void OnMachineConnected() + { + IsConnected = true; + MachineConnected?.Invoke(this, new EventArgs()); + } + + /// + /// Called when the machine has disconnected. + /// + protected virtual void OnMachineDisconnected() + { + IsConnected = false; + MachineDisconnected?.Invoke(this, new EventArgs()); + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/IMachineProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/IMachineProvider.cs index 10180b9cc..774fa7c9e 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/IMachineProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/IMachineProvider.cs @@ -15,6 +15,21 @@ namespace Tango.PPC.Common.Connection /// public interface IMachineProvider { + /// + /// Occurs when the machine has connected. + /// + event EventHandler MachineConnected; + + /// + /// Occurs when the machine has disconnected. + /// + event EventHandler MachineDisconnected; + + /// + /// Gets a value indicating whether the machine is currently connected. + /// + bool IsConnected { get; } + /// /// Gets the database machine entity associated with the current machine. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connectivity/IConnectivityProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connectivity/IConnectivityProvider.cs index 67b73d4f6..39404934d 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connectivity/IConnectivityProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connectivity/IConnectivityProvider.cs @@ -20,10 +20,15 @@ namespace Tango.PPC.Common.Connectivity event EventHandler ConnectionStateChanged; /// - /// Gets a value indicating whether there is any Internet connection available. + /// Gets a value indicating whether there is a WiFi connection. /// bool IsConnected { get; } + /// + /// Gets a value indicating whether there is LAN connection. + /// + bool IsLanConnected { get; } + /// /// Gets the available WiFi networks. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Console/DefaultConsoleEngineService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Console/DefaultConsoleEngineService.cs new file mode 100644 index 000000000..94b677b18 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Console/DefaultConsoleEngineService.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Console; +using Tango.Console.Network; +using Tango.Core; +using Tango.Core.DI; +using Tango.Integration.ExternalBridge; +using Tango.PPC.Common.ExternalBridge; +using Tango.Transport; + +namespace Tango.PPC.Common.Console +{ + /// + /// Represents the default implementation + /// which listens to incoming console request by registering as a external bridge request handler. + /// + /// + /// + [TangoCreateWhenRegistered] + public class DefaultConsoleEngineService : ExtendedObject, IConsoleEngineService, IExternalBridgeRequestHandler + { + /// + /// Gets or sets a value indicating whether this is enabled. + /// + public bool Enabled { get; set; } = true; + + /// + /// Initializes a new instance of the class. + /// + /// The external bridge service instance. + public DefaultConsoleEngineService(IPPCExternalBridgeService externalBridge) + { + externalBridge.RegisterRequestHandler(this); + } + + /// + /// Handles requests. + /// + /// The request. + /// The token. + /// The transporter. + [ExternalBridgeRequestHandlerMethod(typeof(GetCurrentDirectoryRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnGetCurrentDirectoryRequest(GetCurrentDirectoryRequest request, String token, ITransporter transporter) + { + this.ThrowIfDisabled(); + + await transporter.SendGenericResponse(new GetCurrentDirectoryResponse() + { + CurrentDirectory = Environment.CurrentDirectory, + Suggestions = ConsoleExecutionEngine.GetSuggestions(Environment.CurrentDirectory) + }, token); + } + + /// + /// Handles requests. + /// + /// The request. + /// The token. + /// The transporter. + [ExternalBridgeRequestHandlerMethod(typeof(ConsoleCommandRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnConsoleCommandRequest(ConsoleCommandRequest request, String token, ITransporter transporter) + { + this.ThrowIfDisabled(); + + LogManager.Log($"{nameof(ConsoleCommandRequest)} received with command '{request.Command}'. Executing..."); + + ConsoleExecutionEngine engine = new ConsoleExecutionEngine(); + var result = await engine.Execute(request); + + LogManager.Log("Console command executed successfully."); + + await transporter.SendGenericResponse(new ConsoleCommandResponse() + { + Output = result.Output, + Suggestions = result.Suggestions, + WorkingFolder = result.WorkingFolder + }, token); + } + + /// + /// Called when any of the external bridge clients (receivers) has disconnected. + /// + /// The receiver. + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + //Do nothing. + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Console/IConsoleEngineService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Console/IConsoleEngineService.cs new file mode 100644 index 000000000..18edb3629 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Console/IConsoleEngineService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Console +{ + /// + /// Represents a command prompt console service which listens for incoming console requests. + /// + public interface IConsoleEngineService : IPPCService + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.cs new file mode 100644 index 000000000..ba2550e25 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Threading; +using Tango.SharedUI.Controls; + +namespace Tango.PPC.Common.Controls +{ + [ContentProperty(nameof(Elements))] + public class ImageGalleryControl : Control + { + private NavigationControl _navigationControl; + private DispatcherTimer _timer; + + public int SelectedIndex + { + get { return (int)GetValue(SelectedIndexProperty); } + set { SetValue(SelectedIndexProperty, value); } + } + public static readonly DependencyProperty SelectedIndexProperty = + DependencyProperty.Register("SelectedIndex", typeof(int), typeof(ImageGalleryControl), new PropertyMetadata(0)); + + public ObservableCollection Elements + { + get { return (ObservableCollection)GetValue(ElementsProperty); } + set { SetValue(ElementsProperty, value); } + } + public static readonly DependencyProperty ElementsProperty = + DependencyProperty.Register("Elements", typeof(ObservableCollection), typeof(ImageGalleryControl), new PropertyMetadata(null)); + + public Duration Duration + { + get { return (Duration)GetValue(DurationProperty); } + set { SetValue(DurationProperty, value); } + } + public static readonly DependencyProperty DurationProperty = + DependencyProperty.Register("Duration", typeof(Duration), typeof(ImageGalleryControl), new PropertyMetadata(new Duration(TimeSpan.FromSeconds(2)))); + + + static ImageGalleryControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageGalleryControl), new FrameworkPropertyMetadata(typeof(ImageGalleryControl))); + } + + public ImageGalleryControl() + { + Elements = new ObservableCollection(); + Loaded += ImageGalleryControl_Loaded; + + _timer = new DispatcherTimer(); + _timer.Tick += _timer_Tick; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _navigationControl = GetTemplateChild("navigationControl") as NavigationControl; + } + + private void ImageGalleryControl_Loaded(object sender, RoutedEventArgs e) + { + if (_navigationControl != null) + { + _navigationControl.Elements = Elements; + + _timer.Interval = Duration.TimeSpan; + + if (!DesignerProperties.GetIsInDesignMode(new DependencyObject())) + { + _timer.Start(); + } + } + } + + private void _timer_Tick(object sender, EventArgs e) + { + if (SelectedIndex < Elements.Count - 1) + { + SelectedIndex++; + } + else + { + SelectedIndex = 0; + } + } + + protected override void OnPreviewMouseDown(MouseButtonEventArgs e) + { + base.OnPreviewMouseDown(e); + _timer.Stop(); + } + + protected override void OnPreviewMouseUp(MouseButtonEventArgs e) + { + base.OnPreviewMouseUp(e); + _timer.Start(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.xaml b/Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.xaml new file mode 100644 index 000000000..495335ff1 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Controls/ImageGalleryControl.xaml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/DefaultDataStoreService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/DefaultDataStoreService.cs new file mode 100644 index 000000000..02539aed9 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/DefaultDataStoreService.cs @@ -0,0 +1,492 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core.DI; +using Tango.DataStore; +using Tango.DataStore.EF; +using Tango.DataStore.Lite; +using Tango.DataStore.Remote; +using Tango.Integration.ExternalBridge; +using Tango.PMR.DataStore; +using Tango.PPC.Common.Connection; +using Tango.PPC.Common.ExternalBridge; +using Tango.Transport; +using Tango.Core.ExtensionMethods; +using Newtonsoft.Json.Linq; +using Tango.BL; +using Tango.DataStore.Editing; +using Newtonsoft.Json; +using Tango.Core; + +namespace Tango.PPC.Common.DataStore +{ + [TangoCreateWhenRegistered] + public class DefaultDataStoreService : ExtendedObject, IDataStoreService, IExternalBridgeRequestHandler + { + private IDataStoreManager _manager; + private IMachineProvider _machineProvider; + private List _listenerReceivers; + + private class ListerReceiver + { + public String Token { get; set; } + public ExternalBridgeReceiver Receiver { get; set; } + } + + public DefaultDataStoreService(IPPCExternalBridgeService externalBridge, IMachineProvider machineProvider) + { + externalBridge.RegisterRequestHandler(this); + + + _listenerReceivers = new List(); + _machineProvider = machineProvider; + machineProvider.MachineOperator.RegisterRequestHandler(OnPutDataStoreItemRequest); + machineProvider.MachineOperator.RegisterRequestHandler(OnGetDataStoreItemRequest); + } + + public IDataStoreManager GetManager() + { + if (_manager == null) + { + _manager = new EFDataStoreManager(); + } + + return _manager; + } + + public void Dispose() + { + _manager?.Dispose(); + } + + #region Generic Handlers + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStorePutRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStorePutRequest(RemoteDataStorePutRequest request, String token, ExternalBridgeReceiver receiver) + { + ValidateCollectionAndKey(request.Collection, request.Key); + GetManager().GetCollection(request.Collection).Put(request.Key, request.Value); + await receiver.SendGenericResponse(new RemoteDataStorePutResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreGetRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreGetRequest(RemoteDataStoreGetRequest request, String token, ExternalBridgeReceiver receiver) + { + ValidateCollectionAndKey(request.Collection, request.Key); + + if (request.DefaultValue is JObject obj) + { + request.DefaultValue = DataStoreProtoObject.FromJObject(obj); + } + + var item = GetManager().GetCollection(request.Collection).GetItem(request.Key, request.DefaultValue); + await receiver.SendGenericResponse(new RemoteDataStoreGetResponse() + { + DataType = item.Type, + Value = item.Value, + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreGetItemRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreGetItemRequest(RemoteDataStoreGetItemRequest request, String token, ExternalBridgeReceiver receiver) + { + ValidateCollectionAndKey(request.Collection, request.Key); + + if (request.DefaultValue is JObject obj) + { + request.DefaultValue = DataStoreProtoObject.FromJObject(obj); + } + + var item = GetManager().GetCollection(request.Collection).GetItem(request.Key, request.DefaultValue); + await receiver.SendGenericResponse(new RemoteDataStoreGetItemResponse() + { + Item = CreateRemoteItem(item) + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreCountRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreCountRequest(RemoteDataStoreCountRequest request, String token, ExternalBridgeReceiver receiver) + { + var count = GetManager().GetCollection(request.Collection).Count(); + await receiver.SendGenericResponse(new RemoteDataStoreCountResponse() + { + Count = count + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreDeleteRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreDeleteRequest(RemoteDataStoreDeleteRequest request, String token, ExternalBridgeReceiver receiver) + { + throw new InvalidOperationException("Deleting from the data store is not allowed."); + GetManager().GetCollection(request.Collection).Delete(request.Key); + await receiver.SendGenericResponse(new RemoteDataStoreDeleteResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreDeleteAllRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreDeleteAllRequest(RemoteDataStoreDeleteAllRequest request, String token, ExternalBridgeReceiver receiver) + { + throw new InvalidOperationException("Deleting from the data store is not allowed."); + GetManager().GetCollection(request.Collection).DeleteAll(); + await receiver.SendGenericResponse(new RemoteDataStoreDeleteAllResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreGetAllRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreGetAllRequest(RemoteDataStoreGetAllRequest request, String token, ExternalBridgeReceiver receiver) + { + var all = GetManager().GetCollection(request.Collection).GetAll(); + await receiver.SendGenericResponse(new RemoteDataStoreGetAllResponse() + { + Items = all.Select(x => CreateRemoteItem(x)).ToList() + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreGetCollectionNamesRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreGetCollectionNamesRequest(RemoteDataStoreGetCollectionNamesRequest request, String token, ExternalBridgeReceiver receiver) + { + var names = GetManager().GetCollectionNames(); + await receiver.SendGenericResponse(new RemoteDataStoreGetCollectionNamesResponse() + { + Names = names + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreGetAllItemsRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreGetAllItemsRequest(RemoteDataStoreGetAllItemsRequest request, String token, ExternalBridgeReceiver receiver) + { + List collections = new List(); + + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + var items = db.DataStoreItems.Where(x => !x.IsDeleted).ToList(); + + foreach (var itemsGroup in items.GroupBy(x => x.CollectionName)) + { + RemoteDataStoreCollection collection = new RemoteDataStoreCollection(); + collection.Name = itemsGroup.First().CollectionName; + collections.Add(collection); + + foreach (var item in itemsGroup) + { + collection.Items.Add(CreateRemoteItem(item.ToDataStoreItem())); + } + } + } + + await receiver.SendGenericResponse(new RemoteDataStoreGetAllItemsResponse() + { + Collections = collections + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(UpdateDataStoreRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnUpdateDataStoreRequest(UpdateDataStoreRequest request, String token, ExternalBridgeReceiver receiver) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + var allItems = db.DataStoreItems.ToList(); + + List deleted = new List(); + + foreach (var guid in request.ToDelete) + { + var item = allItems.FirstOrDefault(x => x.Guid == guid); + if (item != null) + { + item.IsDeleted = true; + item.IsSynchronized = true; + item.LastUpdated = DateTime.UtcNow; + deleted.Add(item); + } + } + + foreach (var item in request.ToUpsert) + { + ValidateCollectionAndKey(item.CollectionName, item.Key); + } + + foreach (var item in request.ToUpsert) + { + var itemDb = allItems.FirstOrDefault(x => x.CollectionName == item.CollectionName && x.Key == item.Key); + + if (itemDb == null) + { + itemDb = new BL.Entities.DataStoreItem(); + itemDb.Guid = item.Guid; + db.DataStoreItems.Add(itemDb); + } + + itemDb.CollectionName = item.CollectionName; + itemDb.DataType = item.DataType; + itemDb.IsDeleted = item.IsDeleted; + itemDb.IsSynchronized = true; + itemDb.Key = item.Key; + itemDb.LastUpdated = item.LastUpdated; + itemDb.Value = item.Value; + } + + db.SaveChanges(); + + if (_machineProvider.IsConnected) + { + Core.Threading.ThreadFactory.StartNew(() => + { + foreach (var item in request.ToUpsert) + { + try + { + var response = _machineProvider.MachineOperator.SendRequest(new DataStoreItemModifiedRequest() + { + Collection = item.CollectionName, + Key = item.Key + }).Result; + } + catch (Exception ex) + { + Logging.LogManager.Default.Log(ex, $"Error notifying firmware about data store item change '{item.CollectionName}.{item.Key}'."); + } + } + + foreach (var item in deleted) + { + try + { + var response = _machineProvider.MachineOperator.SendRequest(new DataStoreItemModifiedRequest() + { + Collection = item.CollectionName, + Key = item.Key + }).Result; + } + catch (Exception ex) + { + Logging.LogManager.Default.Log(ex, $"Error notifying firmware about data store item change '{item.CollectionName}.{item.Key}'."); + } + } + }); + } + } + + await receiver.SendGenericResponse(new UpdateDataStoreResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDataStoreStartListenRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDataStoreStartListenRequest(RemoteDataStoreStartListenRequest request, String token, ExternalBridgeReceiver receiver) + { + _listenerReceivers.Add(new ListerReceiver() { Receiver = receiver, Token = token }); + + await receiver.SendGenericResponse(new RemoteDataStoreStartListenResponse() + { + ChangeType = RemoteDataStoreChangeType.None, + Item = null + }, token); + } + + private RemoteDataStoreItem CreateRemoteItem(IDataStoreItem item) + { + RemoteDataStoreItem remote = new RemoteDataStoreItem(); + + item.MapPropertiesTo(remote, MappingFlags.All); + + return remote; + } + + #endregion + + #region Proto Handlers + + private async void OnPutDataStoreItemRequest(ITransporter transporter, PutDataStoreItemRequest request, string token) + { + try + { + ValidateCollectionAndKey(request.Collection, request.Key); + + GetManager().GetCollection(request.Collection).Put(request.Key, GetPMRValue(request.Item)); + await transporter.SendResponse(new PutDataStoreItemResponse(), token); + + try + { + if (_listenerReceivers.Count > 0) + { + var item = GetManager().GetCollection(request.Collection).GetItem(request.Key); + var remoteItem = CreateRemoteItem(item); + + foreach (var listener in _listenerReceivers.ToList()) + { + try + { + await listener.Receiver.SendGenericResponse(new RemoteDataStoreStartListenResponse() + { + ChangeType = RemoteDataStoreChangeType.Modified, + CollectionName = request.Collection, + Item = remoteItem + }, listener.Token); + } + catch (Exception ex) + { + LogManager.Log(ex, $"Error sending data store item notification to receiver '{listener.Receiver.Adapter.ToString()}'"); + } + } + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error generating data store item notifications."); + } + } + catch (Exception ex) + { + try + { + await transporter.SendResponse(new PutDataStoreItemResponse(), token, new TransportResponseConfig() + { + ErrorCode = PMR.Common.ErrorCode.GeneralDatastoreError, + ErrorMessage = ex.Message + }); + } + catch (Exception exx) + { + Debug.WriteLine(exx); + } + } + } + + private async void OnGetDataStoreItemRequest(ITransporter transporter, GetDataStoreItemRequest request, string token) + { + try + { + ValidateCollectionAndKey(request.Collection, request.Key); + + var item = GetManager().GetCollection(request.Collection).GetItem(request.Key, GetPMRValue(request.DefaultItem)); + await transporter.SendResponse(new GetDataStoreItemResponse() + { + Key = item.Key, + Item = CreatePMRDataStoreItem(item), + }, token); + } + catch (KeyNotFoundException ex) + { + try + { + await transporter.SendResponse(new GetDataStoreItemResponse(), token, new TransportResponseConfig() + { + ErrorCode = PMR.Common.ErrorCode.KeyNotFound, + ErrorMessage = ex.Message + }); + } + catch (Exception exx) + { + Debug.WriteLine(exx); + } + } + catch (Exception ex) + { + try + { + await transporter.SendResponse(new GetDataStoreItemResponse(), token, new TransportResponseConfig() + { + ErrorCode = PMR.Common.ErrorCode.GeneralDatastoreError, + ErrorMessage = ex.Message + }); + } + catch (Exception exx) + { + Debug.WriteLine(exx); + } + } + } + + #region Helpers + + private DataStoreItem CreatePMRDataStoreItem(IDataStoreItem item) + { + DataStoreItem pmr = new DataStoreItem(); + pmr.DataType = (PMR.DataStore.DataType)item.Type; + + switch (item.Type) + { + case Tango.DataStore.DataType.Int32: + pmr.Int32Value = (int)Convert.ChangeType(item.Value, typeof(int)); + break; + case Tango.DataStore.DataType.Float: + pmr.FloatValue = (float)Convert.ChangeType(item.Value, typeof(float)); + break; + case Tango.DataStore.DataType.Double: + pmr.DoubleValue = (double)Convert.ChangeType(item.Value, typeof(double)); + break; + case Tango.DataStore.DataType.Boolean: + pmr.BooleanValue = (bool)Convert.ChangeType(item.Value, typeof(bool)); + break; + case Tango.DataStore.DataType.String: + pmr.StringValue = (String)Convert.ChangeType(item.Value, typeof(String)); + break; + case Tango.DataStore.DataType.Bytes: + pmr.BytesValue = Google.Protobuf.ByteString.CopyFrom((byte[])Convert.ChangeType(item.Value, typeof(byte[]))); + break; + case Tango.DataStore.DataType.Proto: + DataStoreProtoObject proto = item.Value as DataStoreProtoObject; + pmr.BytesValue = Google.Protobuf.ByteString.CopyFrom(proto.Data); + pmr.ProtoType = proto.MessageType; + break; + } + + return pmr; + } + + private Object GetPMRValue(DataStoreItem item) + { + if (item == null) return null; + + switch (item.DataType) + { + case PMR.DataStore.DataType.Int32: + return item.Int32Value; + case PMR.DataStore.DataType.Float: + return item.FloatValue; + case PMR.DataStore.DataType.Double: + return item.DoubleValue; + case PMR.DataStore.DataType.Boolean: + return item.BooleanValue; + case PMR.DataStore.DataType.String: + return item.StringValue; + case PMR.DataStore.DataType.Bytes: + return item.BytesValue.ToByteArray(); + case PMR.DataStore.DataType.Proto: + return DataStoreProtoObject.FromPMRDataStoreItem(item); + } + + throw new NotSupportedException("The specified data type if not supported."); + } + + #endregion + + #endregion + + private void ValidateCollectionAndKey(String collection = null, String key = null) + { + if (collection != null) + { + if (!DataStoreHelper.ValidateCollectionOrKeyName(collection)) + { + throw new ArgumentException("Collection name contains invalid characters."); + } + } + + if (key != null) + { + if (!DataStoreHelper.ValidateCollectionOrKeyName(key)) + { + throw new ArgumentException("Item key contains invalid characters."); + } + } + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + //Do nothing. + _listenerReceivers.RemoveAll(x => x.Receiver == receiver); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/IDataStoreService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/IDataStoreService.cs new file mode 100644 index 000000000..94ae3fa30 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/DataStore/IDataStoreService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.DataStore; + +namespace Tango.PPC.Common.DataStore +{ + public interface IDataStoreService : IDisposable + { + IDataStoreManager GetManager(); + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/DefaultEventLogger.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/DefaultEventLogger.cs index f9674e409..ee96a77a5 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/DefaultEventLogger.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/DefaultEventLogger.cs @@ -16,6 +16,10 @@ using Tango.Integration.Operation; using Tango.PPC.Common.Application; using Tango.PPC.Common.Authentication; using Tango.PPC.Common.Connection; +using Tango.Transport; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.Events; +using Tango.Core.DI; namespace Tango.PPC.Common.EventLogging { @@ -35,6 +39,7 @@ namespace Tango.PPC.Common.EventLogging private bool _isInitialized; private List _pendingEvents; private List _currentEvents; + private Machine _machine; #region Events @@ -50,6 +55,21 @@ namespace Tango.PPC.Common.EventLogging #endregion + private IPPCExternalBridgeService _externalBridge; + [TangoInject(Mode = TangoInjectMode.WhenAvailable)] + public IPPCExternalBridgeService ExternalBridgeService + { + get { return _externalBridge; } + set + { + if (_externalBridge != value) + { + _externalBridge = value; + _externalBridge.RegisterRequestHandler(this); + } + } + } + #region Constructors /// @@ -84,7 +104,6 @@ namespace Tango.PPC.Common.EventLogging _machineProvider.MachineOperator.PrintingAborted += MachineOperator_PrintingAborted; _machineProvider.MachineOperator.PrintingCompleted += MachineOperator_PrintingCompleted; _machineProvider.MachineOperator.PrintingFailed += MachineOperator_PrintingFailed; - } #endregion @@ -99,18 +118,28 @@ namespace Tango.PPC.Common.EventLogging { _db = ObservablesContext.CreateDefault(); + _machine = _db.Machines.FirstOrDefault(); + _db.EventTypes.ToList(); foreach (var type in _db.EventTypes) { - _eventTypesGuids.Add((EventTypes)type.Code, type); + try + { + _eventTypesGuids.Add((EventTypes)type.Code, type); + } + catch (Exception ex) + { + LogManager.Log(ex, $"Error initializing event type '{type.Name}'."); + } } _isInitialized = true; } - catch + catch (Exception ex) { _isInitialized = false; + LogManager.Log(ex, "Error initializing event types."); } } } @@ -250,7 +279,7 @@ namespace Tango.PPC.Common.EventLogging machineEvent.HostName = _hostName; machineEvent.EventType = _eventTypesGuids[machineEvent.Type]; - if (_machineProvider.MachineOperator == null || _authentication.CurrentUser == null) + if (_machine == null) { _pendingEvents.Add(machineEvent); } @@ -268,14 +297,14 @@ namespace Tango.PPC.Common.EventLogging } LogManager.Log("Logging event " + machineEvent.EventType.Name); - machineEvent.MachineGuid = _machineProvider.Machine.Guid; - machineEvent.UserGuid = _authentication.CurrentUser.Guid; - machineEvent.User = _authentication.CurrentUser; + machineEvent.MachineGuid = _machine.Guid; + machineEvent.UserGuid = null; + machineEvent.User = null; _events.Enqueue(machineEvent); if (!_currentEvents.Exists(x => x.Type == machineEvent.Type)) { - if (machineEvent.Group != EventTypeGroups.Application && machineEvent.Group != EventTypeGroups.Transport) + if (machineEvent.Group != EventTypeGroups.Application && machineEvent.Group != EventTypeGroups.Transport && machineEvent.Group != EventTypeGroups.Jobs) { _currentEvents.Add(machineEvent); } @@ -385,5 +414,21 @@ namespace Tango.PPC.Common.EventLogging } #endregion + + #region External Bridge Handler + + [ExternalBridgeRequestHandlerMethod(typeof(PushEmulatedEventRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnStartPerformanceUpdatesRequest(PushEmulatedEventRequest request, String token, ExternalBridgeReceiver receiver) + { + _machineProvider.MachineOperator.PushEmulatedEvent(request.Event, request.Timeout); + await receiver.SendGenericResponse(new PushEmulatedEventResponse(), token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + + } + + #endregion } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/IEventLogger.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/IEventLogger.cs index 10560e034..81cce927d 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/IEventLogger.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/EventLogging/IEventLogger.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Tango.BL.Entities; using Tango.BL.Enumerations; +using Tango.Integration.ExternalBridge; using Tango.PMR.Diagnostics; namespace Tango.PPC.Common.EventLogging @@ -12,7 +13,7 @@ namespace Tango.PPC.Common.EventLogging /// /// Represents a database events logger. /// - public interface IEventLogger + public interface IEventLogger : IExternalBridgeRequestHandler { /// /// Occurs when a new machine event has been received. diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/ExternalBridge/PPCExternalBridgeService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/ExternalBridge/PPCExternalBridgeService.cs index c50202e86..99951d812 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/ExternalBridge/PPCExternalBridgeService.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/ExternalBridge/PPCExternalBridgeService.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.BL.Entities; using Tango.Core.DI; +using Tango.Core.Helpers; +using Tango.CSV; using Tango.Integration.ExternalBridge; using Tango.Integration.Operation; +using Tango.PMR.Common; using Tango.PPC.Common.Application; using Tango.PPC.Common.Connection; using Tango.PPC.Common.Messages; @@ -14,6 +18,11 @@ using Tango.Settings; namespace Tango.PPC.Common.ExternalBridge { + public class CsvEntry + { + public String MessageType { get; set; } + } + /// /// Represents the PPC external bridge service capable of exposing a remote API for communicating and controlling the machine through the PPC. /// @@ -28,11 +37,36 @@ namespace Tango.PPC.Common.ExternalBridge /// The machine provider. public PPCExternalBridgeService(IPPCApplicationManager applicationManager, IMachineProvider machineProvider) { - applicationManager.ApplicationReady += (_, __) => + var csvStream = EmbeddedResourceHelper.GetEmbeddedResourceStream("Tango.PPC.Common.SafetyLevelOperations.csv"); + + List entries = CsvFile.Read(new CsvSource(csvStream)).ToList(); + + foreach (var entry in entries) + { + MessageType type; + if (Enum.TryParse(entry.MessageType, out type)) + { + ExternalBridgeService.SafetyLevelOperations.Add(type); + } + } + + applicationManager.ApplicationReady += (_, __) => { var settings = SettingsManager.Default.GetOrCreate(); + //TODO: Configure external bridge for configure protocol enforce... MachineOperator = machineProvider.MachineOperator; Machine = machineProvider.Machine; + SignalRConfiguration.Enabled = settings.EnableExternalBridgeSignalR; + TcpTransportAdapterWriteMode = settings.TcpTransportAdapterWriteMode; + if (Environment.CommandLine.Contains("-webDebug")) + { + SignalRConfiguration.Address = "http://localhost:1111/"; //settings.DeploymentSlot.ToAddress(); + } + else + { + SignalRConfiguration.Address = settings.DeploymentSlot.ToAddress(); + } + SignalRConfiguration.Hub = settings.ExternalBridgeSignalRHub; Enabled = settings.EnableExternalBridge; }; } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs new file mode 100644 index 000000000..8272ea34d --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Core.DI; +using Tango.Core.IO; +using Tango.FileSystem; +using Tango.FileSystem.Network; +using Tango.Integration.ExternalBridge; +using Tango.Integration.Operation; +using Tango.Logging; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.Logs; +using Tango.Settings; +using Tango.Transport; +using Tango.Transport.Transporters; +using Tango.WebRTC; + +namespace Tango.PPC.Common.FileSystem +{ + /// + /// Represents the default implementation. + /// + /// + /// + /// + [TangoCreateWhenRegistered] + public class DefaultFileSystemService : ExtendedObject, IFileSystemService, IExternalBridgeRequestHandler + { + private FileSystemManager _manager; + private Dictionary _operations; + private Dictionary _webRtcClients; + private PPCSettings _settings; + + public bool Enabled { get; set; } = true; + public bool EnableWebRTC { get; set; } = true; + + public DefaultFileSystemService(IPPCExternalBridgeService externalBridge) + { + _webRtcClients = new Dictionary(); + _manager = new FileSystemManager(); + _operations = new Dictionary(); + externalBridge.RegisterRequestHandler(this); + _settings = SettingsManager.Default.GetOrCreate(); + } + + [ExternalBridgeRequestHandlerMethod(typeof(InitWebRtcRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnInitWebRtcRequest(InitWebRtcRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + try + { + if (!EnableWebRTC) + { + await receiver.SendErrorResponse(new InvalidOperationException("The file system service WebRTC channel is disabled on this machine."), token); + return; + } + + LogManager.Log("Initializing WebRTC channel for file system service."); + + if (_webRtcClients.ContainsKey(receiver)) + { + _webRtcClients[receiver].Dispose(); + } + + LogManager.Log("Initializing WebRTC transport adapter on 'Passive' mode."); + var webRtcAdapter = new WebRtcTransportAdapter(receiver, WebRtcTransportAdapterMode.Passive, request.DataChannelName) + { + EnableCompression = receiver.Adapter.EnableCompression + }; + webRtcAdapter.Ready += (x, e) => + { + LogManager.Log("The file system service WebRTC channel is ready."); + }; + + BasicTransporter webRtcTransporter = new BasicTransporter(webRtcAdapter); + webRtcTransporter.GenericProtocol = receiver.GenericProtocol; + webRtcTransporter.ComponentName = "File System Passive WebRTC Transporter"; + webRtcTransporter.UseKeepAlive = false; + webRtcTransporter.RegisterRequestHandler(WebRtcChunkDownloadRequestReceived); + webRtcTransporter.RegisterRequestHandler(WebRtcChunkUploadRequestReceived); + await webRtcTransporter.Connect(); + + LogManager.Log("Sending WebRTC initialization response..."); + + await receiver.SendGenericResponse(new InitWebRtcResponse(), token); + _webRtcClients[receiver] = webRtcTransporter; + } + catch (Exception ex) + { + LogManager.Log(ex, "Error initializing WebRTC channel for file system service."); + await receiver.SendErrorResponse(ex, token); + } + } + + private async void WebRtcChunkDownloadRequestReceived(ITransporter transporter, ChunkDownloadRequest request, string token) + { + await OnChunkDownloadRequest(request, token, transporter); + } + + private async void WebRtcChunkUploadRequestReceived(ITransporter transporter, ChunkUploadRequest request, string token) + { + await OnChunkUploadRequest(request, token, transporter); + } + + [ExternalBridgeRequestHandlerMethod(typeof(GetFileSystemItemRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnGetFileSystemItemRequest(GetFileSystemItemRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + FileSystemItemDTO dto = _manager.GetFolder(request); + await receiver.SendGenericResponse(new GetFileSystemItemResponse() { FileSystemItem = dto }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FileUploadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFileUploadRequest(FileUploadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + var tempFile = TemporaryManager.CreateFile(); + using (var stream = new FileStream(tempFile, FileMode.Create)) { } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Upload, tempFile) { UploadPostPath = request.Path }; + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FileUploadResponse() { OperationId = operation.Id }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FolderUploadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFolderUploadRequest(FolderUploadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + var tempFile = TemporaryManager.CreateFile(); + using (var stream = new FileStream(tempFile, FileMode.Create)) { } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Upload, tempFile) { UploadPostPath = request.Path, IsPathTempZip = true }; + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FolderUploadResponse() { OperationId = operation.Id }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FileDownloadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFileDownloadRequest(FileDownloadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + if (!File.Exists(request.Path)) + { + throw new FileNotFoundException("Could not find the specified file."); + } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Download, request.Path); + + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FileDownloadResponse() + { + OperationId = operation.Id, + Length = new FileInfo(request.Path).Length + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FolderDownloadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFolderDownloadRequest(FolderDownloadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + if (!Directory.Exists(request.Path)) + { + throw new FileNotFoundException("Could not find the specified directory."); + } + + var tempFile = TemporaryManager.CreateImaginaryFile(); + + ZipFile.CreateFromDirectory(request.Path, tempFile); + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Download, tempFile); + operation.IsPathTempZip = true; + + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FolderDownloadResponse() + { + OperationId = operation.Id, + Length = new FileInfo(tempFile).Length + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(ChunkUploadRequest))] + public async Task OnChunkUploadRequest(ChunkUploadRequest request, String token, ITransporter receiver) + { + this.ThrowIfDisabled(); + + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + throw new ArgumentException("Invalid operation id."); + } + + using (var stream = new FileStream(operation.Path, FileMode.Append)) + { + stream.Write(request.Data, 0, request.Data.Length); + } + + if (request.IsCompleted) + { + if (!operation.IsPathTempZip) + { + File.Copy(operation.Path, operation.UploadPostPath, true); + try + { + File.Delete(operation.Path); + } + catch { } + } + else + { + using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile(operation.Path)) + { + zip.ExtractAll(operation.UploadPostPath, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently); + } + + try + { + File.Delete(operation.Path); + } + catch { } + } + } + + await receiver.SendGenericResponse(new ChunkUploadResponse(), token, new TransportResponseConfig() { Priority = QueuePriority.Low }); + } + + [ExternalBridgeRequestHandlerMethod(typeof(ChunkDownloadRequest))] + public async Task OnChunkDownloadRequest(ChunkDownloadRequest request, String token, ITransporter receiver) + { + this.ThrowIfDisabled(); + + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + throw new ArgumentException("Invalid operation id."); + } + + FileStream stream = null; + bool removeTempZipFile = false; + + try + { + stream = new FileStream(operation.Path, FileMode.Open); + stream.Position = request.Position; + byte[] data = new byte[Math.Min(request.MaxChunkSize, stream.Length - stream.Position)]; + + if (stream.Position + data.Length == stream.Length) + { + removeTempZipFile = true; + } + + await stream.ReadAsync(data, 0, data.Length); + stream.Dispose(); + stream = null; + await receiver.SendGenericResponse(new ChunkDownloadResponse() + { + Data = data + }, token, new TransportResponseConfig() { Priority = QueuePriority.Low }); + } + catch (Exception ex) + { + stream?.Dispose(); + throw ex; + } + finally + { + if (operation.IsPathTempZip && removeTempZipFile) + { + try + { + if (File.Exists(operation.Path)) + { + File.Delete(operation.Path); + } + } + catch { } + } + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(AbortOperationRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnAbortOperationRequest(AbortOperationRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + throw new ArgumentException("Invalid operation id."); + } + + if (operation.Mode == FileSystemOperationMode.Upload) + { + if (File.Exists(operation.Path)) + { + File.Delete(operation.Path); + } + } + else if (operation.IsPathTempZip) + { + if (File.Exists(operation.Path)) + { + File.Delete(operation.Path); + } + } + + await receiver.SendGenericResponse(new AbortOperationResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(MoveRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnMoveRequest(MoveRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + _manager.Move(request); + await receiver.SendGenericResponse(new MoveResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(CopyRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnCopyRequest(CopyRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + _manager.Copy(request); + await receiver.SendGenericResponse(new CopyResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(DeleteRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnDeleteRequest(DeleteRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + _manager.Delete(request.Path); + await receiver.SendGenericResponse(new DeleteResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(CreateFolderRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnCreateFolderRequest(CreateFolderRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + var dto = _manager.CreateFolder(request.Path, request.FolderName); + await receiver.SendGenericResponse(new CreateFolderResponse() { FolderItem = dto }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(PerformDiskSpaceOptimizationRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnPerformDiskSpaceOptimizationRequest(PerformDiskSpaceOptimizationRequest request, String token, ExternalBridgeReceiver receiver) + { + var deletedBytes = _manager.PerformDiskSpaceOptimization(); + await receiver.SendGenericResponse(new PerformDiskSpaceOptimizationResponse() { DeletedBytes = deletedBytes }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(GetLogFilesRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnGetLogFilesRequest(GetLogFilesRequest request, String token, ExternalBridgeReceiver receiver) + { + FolderItem folder = null; + + if (request.LogFileType == RemoteLogFileType.Application) + { + var fileLogger = LogManager.RegisteredLoggers.SingleOrDefault(x => x.GetType() == typeof(FileLogger)) as FileLogger; + + if (fileLogger == null) + { + throw new InvalidOperationException("Could not locate the application file logger."); + } + + folder = await _manager.GetFolder(fileLogger.Folder, false, "*.log") as FolderItem; + } + else + { + if (MachineOperator.EmbeddedLogsFolder == null) + { + throw new InvalidOperationException("The firmware file logger folder could not be read."); + } + + folder = await _manager.GetFolder(MachineOperator.EmbeddedLogsFolder, false, "*.log") as FolderItem; + } + + GetLogFilesResponse response = new GetLogFilesResponse(); + + foreach (var file in folder.Items.OfType().OrderByDescending(x => x.DateCreated).DistinctBy(x => x.Name)) + { + response.LogFiles.Add(new RemoteLogFile() + { + DateModified = file.DateModified, + DateCreated = file.DateCreated, + Name = file.Name, + Path = file.Path, + Length = new FileInfo(file.Path).Length + }); + } + + await receiver.SendGenericResponse(response, token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + if (_webRtcClients.ContainsKey(receiver)) + { + try + { + LogManager.Log("External bridge receiver disconnected. Disposing file system service WebRTC channel..."); + var webRtcTransporter = _webRtcClients[receiver]; + _webRtcClients.Remove(receiver); + webRtcTransporter.Dispose(); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error disposing the WebRTC channel."); + } + } + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperation.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperation.cs new file mode 100644 index 000000000..9fba7a874 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperation.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.FileSystem +{ + /// + /// Represents an active file system file/folder download/upload operation + /// + public class FileSystemOperation + { + /// + /// Gets or sets the operation mode. + /// + public FileSystemOperationMode Mode { get; set; } + + /// + /// Gets or sets the operation identifier. + /// + public String Id { get; set; } + + /// + /// Gets or sets the path for the operation. + /// + public String Path { get; set; } + + /// + /// Should be set to true when the is a temporary zip file when performing download of a folder. + /// + public bool IsPathTempZip { get; set; } + + /// + /// Gets or sets the actual path to extract the zip file when uploading folder. + /// + public String UploadPostPath { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The mode. + /// The path. + public FileSystemOperation(FileSystemOperationMode mode, String path) + { + Mode = mode; + Id = Guid.NewGuid().ToString(); + Path = path; + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperationMode.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperationMode.cs new file mode 100644 index 000000000..e28843bce --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperationMode.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.FileSystem +{ + public enum FileSystemOperationMode + { + Upload, + Download + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs new file mode 100644 index 000000000..7a80db9c7 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.FileSystem +{ + /// + /// Represents a PPC file system remote service. + /// + /// + public interface IFileSystemService : IPPCService + { + /// + /// Gets or sets a value indicating whether to enable the WebRTC transport channel. + /// + bool EnableWebRTC { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/KeyboardHelper.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/KeyboardHelper.cs new file mode 100644 index 000000000..202f0378e --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/KeyboardHelper.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Touch.Keyboard; + +namespace Tango.PPC.Common.Helpers +{ + public static class KeyboardHelper + { + public static void OpenKeyboard(KeyboardActionKeyMode action, TouchKeyboardMode mode = TouchKeyboardMode.AlphaNumeric) + { + KeyboardView.Keyboard.ActionKeyMode = action; + KeyboardView.Keyboard.Mode = mode; + KeyboardView.Default.IsOpened = true; + } + + public static void CloseKeyboard() + { + KeyboardView.Default.IsOpened = false; + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/LogsHelper.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/LogsHelper.cs new file mode 100644 index 000000000..b7ab2d5b8 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Helpers/LogsHelper.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Logging; + +namespace Tango.PPC.Common.Helpers +{ + public static class LogsHelper + { + private static LogSafe _logSafe; + + public static void SetLogSafe(LogSafe logSafe) + { + _logSafe = logSafe; + } + + public static LogSafe GetLogSafe() + { + return _logSafe; + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/IPPCService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/IPPCService.cs new file mode 100644 index 000000000..5dfe5335c --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/IPPCService.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PPC.Common; + +namespace Tango.PPC.Common +{ + /// + /// Represents a PPC remoting service. + /// + public interface IPPCService + { + /// + /// Gets or sets a value indicating whether this is enabled. + /// + bool Enabled { get; set; } + } +} + +public static class ExtensionMethods +{ + /// + /// Throws an exception is the service is disabled. + /// + /// The service. + /// + public static void ThrowIfDisabled(this IPPCService service) + { + if (!service.Enabled) + { + throw new NotSupportedException($"The {service.GetType().Name} is currently disabled. Could not perform the requested action."); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Images/cl-full.png b/Software/Visual_Studio/PPC/Tango.PPC.Common/Images/cl-full.png new file mode 100644 index 000000000..5aaea8e6c Binary files /dev/null and b/Software/Visual_Studio/PPC/Tango.PPC.Common/Images/cl-full.png differ diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Images/lubricant2.png b/Software/Visual_Studio/PPC/Tango.PPC.Common/Images/lubricant2.png new file mode 100644 index 000000000..554c16305 Binary files /dev/null and b/Software/Visual_Studio/PPC/Tango.PPC.Common/Images/lubricant2.png differ diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/DefaultInsightsService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/DefaultInsightsService.cs new file mode 100644 index 000000000..75c5ae9cd --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/DefaultInsightsService.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.BL; +using Tango.Core; +using Tango.Core.DI; +using Tango.Insights; +using Tango.Integration.ExternalBridge; +using Tango.Logging; +using Tango.PPC.Common.Application; +using Tango.PPC.Common.Connection; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.Insights; +using Tango.Settings; + +namespace Tango.PPC.Common.Insights +{ + [TangoCreateWhenRegistered] + public class DefaultInsightsService : ExtendedObject, IInsightsService, IExternalBridgeRequestHandler + { + private InsightsListener _listener; + + private IMachineProvider MachineProvider { get; set; } + + public DefaultInsightsService(IPPCExternalBridgeService externalBridge, IMachineProvider machineProvider, IPPCApplicationManager applicationManager) + { + externalBridge.RegisterRequestHandler(this); + MachineProvider = machineProvider; + applicationManager.ApplicationStarted += ApplicationManager_ApplicationStarted; + } + + private void ApplicationManager_ApplicationStarted(object sender, EventArgs e) + { + Task.Factory.StartNew(() => + { + try + { + var settings = SettingsManager.Default.GetOrCreate(); + settings.Save(); + + if (settings.InsightsEnabled) + { + LogManager.Log("Starting insights service..."); + + _listener = new InsightsListener(MachineProvider.MachineOperator); + _listener.SamplingInterval = settings.InsightsSamplingInterval; + _listener.StorageCleanupInterval = settings.InsightsStorageCleanupInterval; + _listener.MaxStorageDuration = settings.InsightsMaxStorageDuration; + + LogManager.Log($"Insights configuration:\nSampling Interval: {_listener.SamplingInterval}\nStorage Cleanup Interval: {_listener.StorageCleanupInterval}\nMax Storage Duration: {_listener.MaxStorageDuration}"); + + _listener.Start(); + } + else + { + LogManager.Log("Insights service is disabled.", LogCategory.Warning); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error initializing insights listener."); + } + }); + } + + [ExternalBridgeRequestHandlerMethod(typeof(InsightsRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnInsightsRequest(InsightsRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + InsightsFile insightsFile = new InsightsFile(); + var filePath = TemporaryManager.CreateImaginaryFile(); + + await Task.Factory.StartNew(() => + { + var frames = InsightsManager.Default.GetFrames(request.StartDateUTC, request.EndDateUTC); + insightsFile.Frames = frames; + + if (request.IncludeEvents) + { + insightsFile.Events = InsightsManager.Default.GetEvents(request.StartDateUTC, request.EndDateUTC); + } + + if (request.IncludeStatuses) + { + insightsFile.Statuses = InsightsManager.Default.GetStatuses(request.StartDateUTC, request.EndDateUTC); + } + + if (request.IncludeApplicationExceptions) + { + insightsFile.ApplicationExceptions = InsightsManager.Default.GetApplicationExceptions(request.StartDateUTC, request.EndDateUTC); + } + + insightsFile.ToFile(filePath); + }); + + await receiver.SendGenericResponse(new InsightsResponse() + { + InisightsFilePath = filePath, + InsightsFileLength = new FileInfo(filePath).Length + }, token); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error processing insights request."); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(InsightsDownloadCompletedRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnInsightsDownloadCompletedRequest(InsightsDownloadCompletedRequest request, String token, ExternalBridgeReceiver receiver) + { + await Task.Factory.StartNew(() => + { + try + { + File.Delete(request.InisightsFilePath); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error deleting insights request temp file after download completion."); + } + }); + await receiver.SendGenericResponse(new InsightsResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(InsightsMinDateRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnInsightsMinDateRequest(InsightsMinDateRequest request, String token, ExternalBridgeReceiver receiver) + { + DateTime? minDate = null; + + try + { + await Task.Factory.StartNew(() => + { + minDate = InsightsManager.Default.GetFramesMinDate(); + }); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error retrieving insights frames minimum date."); + } + + await receiver.SendGenericResponse(new InsightsMinDateResponse() { MinDate = minDate }, token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + //Do Nothing... + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/IInsightsService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/IInsightsService.cs new file mode 100644 index 000000000..268bb269b --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Insights/IInsightsService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Insights +{ + public interface IInsightsService + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs index 004c37096..15902f629 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs @@ -11,12 +11,15 @@ using System.Net.Http.Headers; 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.Logging; using Tango.PMR.Synchronization; using Tango.PPC.Common.Application; using Tango.PPC.Common.Connection; @@ -28,6 +31,7 @@ using Tango.Settings; using Tango.SharedUI.Helpers; using Tango.SQLExaminer; using Tango.Transport.Web; +using System.Data.Entity; namespace Tango.PPC.Common.MachineSetup { @@ -42,6 +46,9 @@ namespace Tango.PPC.Common.MachineSetup private IUnifiedWriteFilterManager _uwf; private IOperationSystemManager _windows_manager; private PPCWebClient _client; + private List _logs; + private bool _isUpdating; + private DateTime _setupStartDate; #region Events @@ -80,6 +87,21 @@ namespace Tango.PPC.Common.MachineSetup _remoteAssistance = remoteAssistance; _uwf = unifiedWriterFilterManager; _windows_manager = operationSystemManager; + + _logs = new List(); + LogManager.NewLog += LogManager_NewLog; + } + + #endregion + + #region Event Handlers + + private void LogManager_NewLog(object sender, LogItemBase e) + { + if (_isUpdating) + { + _logs.Add(e); + } } #endregion @@ -99,6 +121,82 @@ namespace Tango.PPC.Common.MachineSetup }); } + private async void OnFailed(Exception ex, TaskCompletionSource completionSource, MachineSetupResponse response) + { + LogManager.Log(ex, "An error occurred in machine setup."); + + completionSource.SetException(ex); + + if (response != null) + { + try + { + var result = await _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() + { + Token = response.NotifyCompletedToken, + Status = BL.Enumerations.TangoUpdateStatuses.SetupFailed, + FailedReason = ex.FlattenMessage(), + FailedLog = GetLogsStringAndClear(), + }); + } + catch (Exception xx) + { + LogManager.Log(xx, "Error notifying setup completed."); + } + } + + _isUpdating = false; + } + + private async void OnCompleted(MachineSetupResult result, TaskCompletionSource completionSource, MachineSetupResponse response) + { + completionSource.SetResult(result); + + if (response != null) + { + try + { + var r = await _client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() + { + Token = response.NotifyCompletedToken, + Status = BL.Enumerations.TangoUpdateStatuses.SetupCompleted, + }); + } + catch (Exception xx) + { + LogManager.Log(xx, "Error notifying setup completed."); + } + + try + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + TangoUpdate update = new TangoUpdate(); + update.ApplicationVersion = response.Version; + update.FirmwareVersion = response.FirmwareVersion; + update.MachineGuid = (await db.Machines.FirstAsync()).Guid; + update.UpdateStatus = BL.Enumerations.TangoUpdateStatuses.SetupCompleted; + update.StartDate = _setupStartDate; + update.EndDate = DateTime.UtcNow; + await db.SaveChangesAsync(); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error saving tango setup information to database."); + } + } + + _isUpdating = false; + } + + private String GetLogsStringAndClear() + { + String logsString = String.Join(Environment.NewLine, _logs.ToList().Select(x => x.ToString())); + _logs.Clear(); + return logsString; + } + #endregion #region Public Methods @@ -111,10 +209,17 @@ namespace Tango.PPC.Common.MachineSetup /// public async Task Setup(string serialNumber) { + _logs.Clear(); + TaskCompletionSource result = new TaskCompletionSource(); + MachineSetupResponse setup_response = null; + _setupStartDate = DateTime.UtcNow; + try { + _isUpdating = true; + LogManager.Log($"Starting machine setup for serial number {serialNumber}..."); var machineServiceAddress = SettingsManager.Default.GetOrCreate().GetMachineServiceAddress(); @@ -130,16 +235,24 @@ namespace Tango.PPC.Common.MachineSetup Login(serialNumber).Wait(); + String deviceName = $"Tango-{serialNumber}-{settings.DeploymentSlot.ToString()}"; + LogManager.Log($"Settings device name: '{deviceName}'..."); + try + { + await _windows_manager.SetDeviceName(deviceName); + } + catch + { + throw new IOException("Error setting device name."); + } + MachineSetupRequest request = new MachineSetupRequest(); - request.SerialNumber = serialNumber; request.DeviceID = await _windows_manager.GetDeviceId(); - request.DeviceName = await _windows_manager.GetDeviceName(); - - - MachineSetupResponse setup_response = null; + request.DeviceName = deviceName; try { + LogManager.Log($"Sending setup request...\n{request.ToJsonString()}"); setup_response = await _client.MachineSetup(request); } catch (Exception ex) @@ -175,20 +288,20 @@ namespace Tango.PPC.Common.MachineSetup UpdateProgress("Activating operation system license", "Activating..."); await _windows_manager.Activate(setup_response.OSKey); } + } - if (setup_response.SetupRemoteAssistance) - { - LogManager.Log("Installing remote assistance..."); - UpdateProgress("Installing remote assistance", "Installing..."); - await _remoteAssistance.InstallRemoteAssistance(serialNumber); - } + if (setup_response.SetupRemoteAssistance) + { + LogManager.Log("Installing remote assistance..."); + UpdateProgress("Installing remote assistance", "Installing..."); + await _remoteAssistance.InstallRemoteAssistance(serialNumber, setup_response.Organization, settings.DeploymentSlot.ToString()); + } - if (setup_response.SetupUWF) - { - LogManager.Log("Activating unified write filter..."); - UpdateProgress("Activating disk protection", "Activating..."); - await _uwf.Setup(); - } + if (setup_response.SetupUWF) + { + LogManager.Log("Activating unified write filter..."); + UpdateProgress("Activating disk protection", "Activating..."); + await _uwf.Setup(); } //Create temporary folders for packages. @@ -204,27 +317,29 @@ namespace Tango.PPC.Common.MachineSetup LogManager.Log("Downloading software package..."); - long fileSize = 0; - UpdateProgress("Downloading software package", "Downloading...", false); - - await Task.Factory.StartNew(() => + using (AutoFileDownloader downloader = new AutoFileDownloader(setup_response.BlobAddress, setup_response.CdnAddress, tempFile)) { - using (FileStreamWrapper fs = new FileStreamWrapper(tempFile.Path, FileMode.Create, (current) => + await downloader.ResolveMode(); + + if (downloader.Mode == AutoFileDownloader.DownloadMode.Standard) { - UpdateProgress("Downloading software package", "Downloading...", false, current, fileSize); - })) + LogManager.Log($"Connecting to storage CDN with address {downloader.Address}"); + } + else { - - LogManager.Log($"Connecting to storage blob with address {setup_response.BlobAddress}"); - CloudBlockBlob blob = new CloudBlockBlob(new Uri(setup_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($"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..."); @@ -320,25 +435,25 @@ namespace Tango.PPC.Common.MachineSetup UpdateProgress("Updating Firmware", "Loading firmware package..."); var tfpPath = Path.Combine(_newPackageTempFolder, "firmware_package.tfp"); var stream = new FileStream(tfpPath, FileMode.Open); - var handler = await op.UpgradeFirmware(stream); + var handler = await op.UpgradeFirmware(stream, setup_response.IsDemo); handler.Failed += (_, ex) => { stream.Dispose(); - result.SetException(ex); + OnFailed(ex, result, setup_response); }; handler.Completed += (_, __) => { UpdateProgress("Updating Firmware", "Firmware update completed successfully."); stream.Dispose(); - result.SetResult(new MachineSetupResult() + OnCompleted(new MachineSetupResult() { UpdatePackagePath = _newPackageTempFolder, - }); + }, result, setup_response); }; handler.Canceled += (_, __) => { stream.Dispose(); - result.SetException(new Exception("The operation has been canceled.")); + OnFailed(new Exception("The operation has been canceled."), result, setup_response); }; handler.Progress += (_, e) => { @@ -347,16 +462,15 @@ namespace Tango.PPC.Common.MachineSetup } else { - result.SetResult(new MachineSetupResult() + OnCompleted(new MachineSetupResult() { UpdatePackagePath = _newPackageTempFolder, - }); + }, result, setup_response); } } catch (Exception ex) { - LogManager.Log(ex, "An error occurred in machine setup."); - result.SetException(ex); + OnFailed(ex, result, setup_response); } return await result.Task; diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs index 85d61d4cc..7c835165f 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/IMachineUpdateManager.cs @@ -4,12 +4,27 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.PMR.Synchronization; +using Tango.PPC.Common.Publish; +using Tango.PPC.Common.UpdatePackages; using Tango.PPC.Common.Web; namespace Tango.PPC.Common.MachineUpdate { public interface IMachineUpdateManager { + /// + /// Occurs when an application update is available. + /// + event EventHandler UpdateAvailable; + + /// + /// Gets or sets a value indicating whether to automatically check for new application updates. + /// + bool EnableAutoCheckForUpdates { get; set; } + + /// + /// Gets the current machine update progress status. + /// MachineUpdateProgress Status { get; } /// @@ -23,46 +38,68 @@ namespace Tango.PPC.Common.MachineUpdate event EventHandler Progress; /// - /// Performs a machine update using the specified serial number and machine service address. + /// Performs a machine update. /// - /// 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. /// - Task Update(String serialNumber, bool setupFirmware, bool setupFPGA); + Task Update(bool setupFirmware, bool setupFPGA); /// /// Performs a machine update using the specified software update package path. /// /// Name of the file. /// - Task UpdateFromTUP(String fileName); + Task UpdateFromTUP(String fileName, bool setupFirmware, bool setupFPGA); + + /// + /// Performs a firmware upgrade from the specified TFP file. + /// + /// Name of the file. + /// + Task UpdateFromTFP(String fileName); /// - /// Checks if any update are available for the specified machine serial number. + /// Checks if any update are available. /// - /// The serial number. /// - Task CheckForUpdate(String serialNumber); + Task CheckForUpdate(); /// /// Checks whether it is necessary to updates all the "overwrite-able" database tables. /// - /// The serial number. /// - Task UpdateDBCheck(String serialNumber); + Task UpdateDBCheck(); /// /// Updates all the "overwrite-able" database tables. /// /// - Task UpdateDB(DbCompareResult dbCompareResult, String serialNumber); + Task UpdateDB(DbCompareResult dbCompareResult); /// /// Gets the update package file information. /// /// The file path. /// - Task GetUpdatePackageFileInfo(String filePath); + Task GetUpdatePackageFileInfo(String filePath); + + /// + /// Checks whether any post update packages needs to be installed. + /// + /// + Task PostUpdatePackagesRequired(); + + /// + /// Runs all post update packages. + /// + /// + Task RunPostUpdatePackages(); + + /// + /// Restores the last database backup. + /// + /// + Task RestoreLastDatabaseBackup(); } } 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 } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateResult.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateResult.cs index 17ae394ee..85dd5b7d2 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateResult.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateResult.cs @@ -12,5 +12,18 @@ namespace Tango.PPC.Common.MachineUpdate /// Gets or sets the temporary update package path from which to get the last downloaded software version. /// public String UpdatePackagePath { get; set; } + + /// + /// Gets or sets a value indicating whether the application should replace it's binaries. + /// + public bool RequiresBinariesUpdate { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public MachineUpdateResult() + { + RequiresBinariesUpdate = true; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/UpdatePackageFile.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/UpdatePackageFile.cs deleted file mode 100644 index df496c3be..000000000 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/UpdatePackageFile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.PPC.Common.MachineUpdate -{ - public class UpdatePackageFile - { - public Version Version { get; set; } - } -} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Models/FineTuneItem.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Models/FineTuneItem.cs index 2eea5c3ce..c03be1ae9 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Models/FineTuneItem.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Models/FineTuneItem.cs @@ -18,6 +18,8 @@ namespace Tango.PPC.Common.Models { public event Action SelectedChanged; + public BrushStop BrushStop { get; set; } + /// /// Gets or sets the brush stops. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/INavigationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/INavigationManager.cs index 8df0a7fb8..1fcdb4410 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/INavigationManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/INavigationManager.cs @@ -12,6 +12,11 @@ namespace Tango.PPC.Common.Navigation /// public interface INavigationManager { + /// + /// Occurs when the current view model has changed. + /// + event EventHandler CurrentVMChanged; + /// /// Gets the current module. /// @@ -27,6 +32,16 @@ namespace Tango.PPC.Common.Navigation /// bool CanNavigateBack { get; } + /// + /// Gets a value indicating whether the back should be enabled. + /// + bool IsBackEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether the navigation system is currently navigating. + /// + bool IsNavigating { get; set; } + /// /// Navigates to the previous view if is true. /// @@ -79,7 +94,7 @@ namespace Tango.PPC.Common.Navigation /// Navigates to the specified module and view by full path (e.g Jobs.JobsView). /// /// The full path. - Task NavigateTo(String fullPath, bool pushToHistory = true); + Task NavigateTo(String fullPath, bool pushToHistory = true, Action onNavigating = null, Action onNavigated = null); /// /// Navigates to the specified module and view with the specified object and expecting a return parameter. diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/NavigationView.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/NavigationView.cs index b4562054c..643908e87 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/NavigationView.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Navigation/NavigationView.cs @@ -22,6 +22,7 @@ namespace Tango.PPC.Common.Navigation HomeModule, ShutdownView, RestartingSystemView, - EmergencyView + EmergencyView, + RestartingView, } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarItem.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarItem.cs index 1c47d2a97..fdd66a56b 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarItem.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarItem.cs @@ -13,6 +13,16 @@ namespace Tango.PPC.Common.Notifications /// public abstract class AppBarItem : ItemBase { + private AppBarPriority _priority; + public AppBarPriority Priority + { + get { return _priority; } + set { _priority = value; RaisePropertyChangedAuto(); } + } + public AppBarItem() + { + Priority = AppBarPriority.Normal; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarPriority.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarPriority.cs new file mode 100644 index 000000000..bd8547f5d --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/AppBarPriority.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Notifications +{ + public enum AppBarPriority + { + Low, + Normal, + High + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/INotificationProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/INotificationProvider.cs index c4e82b7d2..950b8d23f 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/INotificationProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/INotificationProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -22,20 +23,25 @@ namespace Tango.PPC.Common.Notifications /// ObservableCollection NotificationItems { get; } + /// + /// Gets the notification items view. + /// + ICollectionView NotificationItemsView { get; } + /// /// Gets the collection of taskbar items. /// ObservableCollection TaskBarItems { get; } /// - /// Gets the current application bar item. + /// Gets the application bar items. /// - AppBarItem CurrentAppBarItem { get; } + ObservableCollection AppBarItems { get; } /// - /// Gets a value indicating whether this instance has application bar item. + /// Gets a value indicating whether this instance has any application bar items. /// - bool HasAppBarItem { get; } + bool HasAppBarItems { get; } /// /// Gets a value indicating whether this instance has notification items. @@ -176,7 +182,7 @@ namespace Tango.PPC.Common.Notifications /// /// /// - AppBarItem PushAppBarItem() where T : AppBarItem; + T PushAppBarItem() where T : AppBarItem; /// /// Pops the application bar item. diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItem.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItem.cs index c96fe9dee..6a29511a9 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItem.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItem.cs @@ -14,22 +14,22 @@ namespace Tango.PPC.Common.Notifications /// public abstract class NotificationItem : ItemBase { - /// - /// Initializes a new instance of the class. - /// - public NotificationItem() : base() + public enum NotificationPriority { - CanClose = true; + Low, + Normal, + High, + VeryHigh, + Critical, } - private bool _isExpanded; /// - /// Gets or sets a value indicating whether the notification panel is expanded. + /// Initializes a new instance of the class. /// - public bool IsExpanded + public NotificationItem() : base() { - get { return _isExpanded; } - set { _isExpanded = value; RaisePropertyChangedAuto(); } + CanClose = true; + Priority = NotificationPriority.Normal; } private bool _canClose; @@ -42,6 +42,11 @@ namespace Tango.PPC.Common.Notifications set { _canClose = value; RaisePropertyChangedAuto(); } } + /// + /// Gets or sets the notification priority. + /// + public NotificationPriority Priority { get; set; } + /// /// Called when the item has been pressed. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItem.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItem.cs index a9de336a1..7d85ef6a7 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItem.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItem.cs @@ -77,11 +77,12 @@ namespace Tango.PPC.Common.Notifications.NotificationItems /// The expanded message. /// The type. /// The pressed action. - public MessageNotificationItem(String message, String expandedMessage, MessageNotificationItemTypes type, Action pressedAction) : this() + public MessageNotificationItem(String message, String expandedMessage, MessageNotificationItemTypes type, Action pressedAction, NotificationPriority priority = NotificationPriority.Normal) : this() { Message = message; ExpandedMessage = expandedMessage; MessageType = type; + Priority = priority; Pressed += (_, __) => pressedAction?.Invoke(); } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItemView.xaml b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItemView.xaml index 33c58f51e..cab40e50e 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItemView.xaml +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Notifications/NotificationItems/MessageNotificationItemView.xaml @@ -111,20 +111,20 @@ - - + + - - - + + + - + diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/DefaultOperationSystemManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/DefaultOperationSystemManager.cs index 2164a71c3..32fd74646 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/DefaultOperationSystemManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/DefaultOperationSystemManager.cs @@ -232,5 +232,27 @@ namespace Tango.PPC.Common.OS return Environment.MachineName; }); } + + /// + /// Sets the device host name. + /// + /// + public async Task SetDeviceName(String name) + { + var command = new CmdCommand("wmic", $"computersystem where caption='{Environment.MachineName}' rename '{name}'"); + await command.Run(); + } + + /// + /// Opens the operating system shell (explorer). + /// + public void OpenShell() + { + Process.Start(new ProcessStartInfo() + { + FileName = @"C:\Windows\Sysnative\cmd.exe", + Arguments = @"/c start /B explorer.exe" + }); + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/IOperationSystemManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/IOperationSystemManager.cs index 3e24ffe72..4faef33f9 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/IOperationSystemManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/OS/IOperationSystemManager.cs @@ -56,6 +56,12 @@ namespace Tango.PPC.Common.OS /// Task GetDeviceName(); + /// + /// Sets the device host name. + /// + /// + Task SetDeviceName(String name); + /// /// Restarts the system. /// @@ -67,5 +73,10 @@ namespace Tango.PPC.Common.OS /// /// void Shutdown(); + + /// + /// Opens the operating system shell (explorer). + /// + void OpenShell(); } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs index b1bc3faad..aacbe8901 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs @@ -4,9 +4,13 @@ using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; +using Tango.BL.Enumerations; +using Tango.Integration.Operation; using Tango.Logging; +using Tango.PMR.Integration; using Tango.PMR.Printing; using Tango.Settings; +using Tango.Transport.Adapters; using Tango.Web; namespace Tango.PPC.Common @@ -17,11 +21,6 @@ namespace Tango.PPC.Common /// public class PPCSettings : SettingsBase { - /// - /// Gets or sets the logging categories. - /// - public List LoggingCategories { get; set; } - /// /// Gets or sets the state of the application. /// @@ -137,6 +136,157 @@ namespace Tango.PPC.Common /// public bool EnableJobLiquidQuantityValidation { get; set; } + /// + /// Gets or sets the job number of units method. + /// + public JobUnitsMethods JobUnitsMethod { get; set; } + + /// + /// Gets or sets the loaded RML unique identifier. + /// + public String LoadedRmlGuid { get; set; } + + /// + /// Gets or sets the default RML unique identifier. + /// + public String DefaultRmlGuid { get; set; } + + /// + /// Gets or sets the default color space unique identifier. + /// + public List SupportedColorSpaces { get; set; } + + /// + /// Gets or sets the target job types. + /// + public List SupportedJobTypes { get; set; } + + /// + /// Gets or sets the default spool type unique identifier. + /// + public String DefaultSpoolTypeGuid { get; set; } + + /// + /// Gets or sets the default length of the segment. + /// + public int DefaultSegmentLength { get; set; } + + /// + /// Gets or sets the previous application version. + /// + public String PreviousApplicationVersion { get; set; } + + /// + /// Gets or sets a value indicating whether synchronize jobs with twine server. + /// + public bool SynchronizeJobs { get; set; } + + /// + /// Gets or sets a value indicating whether synchronize diagnostics data. + /// + public bool SynchronizeDiagnostics { get; set; } + + /// + /// Gets or sets the synchronization interval. + /// + public TimeSpan SynchronizationInterval { get; set; } + + /// + /// Gets or sets the known firmware version. + /// + public String FirmwareVersion { get; set; } + + /// + /// Gets or sets a value indicating whether to display the power up screen. + /// + public bool DisplayPowerUpScreen { get; set; } + + /// + /// Gets or sets the power up screen timeout. + /// + public TimeSpan PowerUpScreenTimeout { get; set; } + + /// + /// Gets or sets a value indicating whether to automatically check for software and database (quick) updates. + /// + public bool AutoCheckForUpdates { get; set; } + + /// + /// Gets or sets the automatic update check interval. + /// + public TimeSpan AutoUpdateCheckInterval { get; set; } + + /// + /// Gets or sets a value indicating whether to enable the automatic thread loading support. + /// + public bool EnableAutomaticThreadLoading { get; set; } + + /// + /// Gets or sets a value indicating whether to display the thread loading screen. + /// + public bool DisplayAutomaticThreadLoadingScreen { get; set; } + + /// + /// Gets or sets a value indicating whether to enable embedded debug logs. + /// + public bool EnableEmbeddedDebugLogs { get; set; } + + /// + /// Gets or sets the TCP transport adapter write mode. + /// + public TcpTransportAdapterWriteMode TcpTransportAdapterWriteMode { get; set; } + + /// + /// Gets or sets a value indicating whether to expose the external bridge service via SignalR. + /// + public bool EnableExternalBridgeSignalR { get; set; } + + /// + /// Gets or sets the name of the exteral bridge SignalR hub. + /// + public String ExternalBridgeSignalRHub { get; set; } + + /// + /// Gets or sets a value indicating whether to enable the internal remote desktop service. + /// + public bool EnableRemoteDesktop { get; set; } + + /// + /// Gets or sets the internal remote desktop service frame rate (1-20). + /// + public int RemoteDesktopFrameRate { get; set; } + + /// + /// Gets or sets a value indicating whether to enable insights. + /// + public bool InsightsEnabled { get; set; } + + /// + /// Gets or sets the insights sampling interval. + /// + public TimeSpan InsightsSamplingInterval { get; set; } + + /// + /// Gets or sets the insights storage cleanup interval. + /// + public TimeSpan InsightsStorageCleanupInterval { get; set; } + + /// + /// Gets or sets the duration of the insights maximum storage duration. + /// + public TimeSpan InsightsMaxStorageDuration { get; set; } + + /// + /// Gets or sets a value indicating whether to bypass Internet connectivity checks before attempting to perform an update for example. + /// + public bool BypassInternetConnectivityCheck { get; set; } + + /// + /// Gets or sets the last database backup file that was generated before application update. + /// If updater utility was successful, this file should be deleted. Otherwise should be restored. + /// + public String LastDatabaseBackupFile { get; set; } + /// /// Gets the machine service address. /// @@ -155,18 +305,44 @@ namespace Tango.PPC.Common EnableGradientGeneration = true; GradientGenerationResolution = 20; MachineScanningTimeoutSeconds = 20; - LoggingCategories = new List(); EmbeddedComPort = "COM10"; EmbeddedDeviceHint = "Tango USB Serial Port"; ExternalBridgePassword = "Aa123456"; HotSpotPassword = "Aa123456"; LockScreenTimeout = TimeSpan.FromMinutes(10); LockScreenPassword = "1111"; - DeploymentSlot = DeploymentSlot.TEST; + DeploymentSlot = DeploymentSlot.DEV; EnableWatchDog = true; EnableEmergencyNotifications = true; EmergencyComPort = "COM2"; EnableJobLiquidQuantityValidation = true; + JobUnitsMethod = JobUnitsMethods.Device; + DefaultSegmentLength = 100; + SupportedColorSpaces = new List(); + SupportedJobTypes = new List(); + PreviousApplicationVersion = "1.0.0.0"; + SynchronizeJobs = false; + SynchronizeDiagnostics = true; + SynchronizationInterval = TimeSpan.FromMinutes(60); + FirmwareVersion = "1.0.0.0"; + DisplayPowerUpScreen = true; + PowerUpScreenTimeout = TimeSpan.FromSeconds(60); + AutoCheckForUpdates = true; + AutoUpdateCheckInterval = TimeSpan.FromMinutes(30); + EnableAutomaticThreadLoading = true; + DisplayAutomaticThreadLoadingScreen = true; + EnableEmbeddedDebugLogs = true; + TcpTransportAdapterWriteMode = TcpTransportAdapterWriteMode.Interval; + EnableExternalBridgeSignalR = true; + ExternalBridgeSignalRHub = "ExternalBridgeHub"; + EnableRemoteDesktop = true; + RemoteDesktopFrameRate = 5; + BypassInternetConnectivityCheck = false; + + InsightsEnabled = true; + InsightsSamplingInterval = TimeSpan.FromMinutes(1); + InsightsMaxStorageDuration = TimeSpan.FromDays(30); + InsightsStorageCleanupInterval = TimeSpan.FromMinutes(60); } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs index 5e584f891..98eef6883 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs @@ -16,7 +16,10 @@ using Tango.PPC.Common.Navigation; using Tango.PPC.Common.Notifications; using Tango.PPC.Common.Printing; using Tango.PPC.Common.RemoteAssistance; +using Tango.PPC.Common.RemoteDesktop; using Tango.PPC.Common.Storage; +using Tango.PPC.Common.Synchronization; +using Tango.PPC.Common.ThreadLoading; using Tango.Settings; using Tango.SharedUI; using static Tango.SharedUI.Controls.NavigationControl; @@ -27,7 +30,7 @@ namespace Tango.PPC.Common /// Represents a PPC view model base class. /// /// - public abstract class PPCViewModel : ViewModel, INavigationViewModel, INavigationBlocker + public abstract class PPCViewModel : ViewModel, INavigationBlocker { /// /// Gets the static observable entities adapter. @@ -109,6 +112,24 @@ namespace Tango.PPC.Common [TangoInject] public IEventLogger EventLogger { get; set; } + /// + /// Gets or sets the machine data synchronizer. + /// + [TangoInject] + public IMachineDataSynchronizer MachineDataSynchronizer { get; set; } + + /// + /// Gets or sets the remote desktop service. + /// + [TangoInject] + public IRemoteDesktopService RemoteDesktopService { get; set; } + + /// + /// Gets or sets the thread loading service. + /// + [TangoInject] + public IThreadLoadingService ThreadLoadingService { get; set; } + private PPCSettings _settings; /// /// Gets the main PPC settings. @@ -158,6 +179,15 @@ namespace Tango.PPC.Common IsVisible = true; } + /// + /// Called when the navigation system has navigated to this VM view. + /// + /// The view model instance of the previous view model + public virtual void OnNavigatedTo(PPCViewModel fromVM) + { + + } + /// /// Called when the navigation system has navigated from this VM view. /// @@ -166,6 +196,22 @@ namespace Tango.PPC.Common IsVisible = false; } + /// + /// Called before the navigation system has navigated to this VM view. + /// + public virtual void OnBeforeNavigatedTo() + { + + } + + /// + /// Called before the navigation system has navigated from this VM view. + /// + public virtual void OnBeforeNavigatedFrom() + { + IsVisible = false; + } + /// /// Raises the specified message using the default . /// @@ -220,7 +266,7 @@ namespace Tango.PPC.Common /// public virtual void OnApplicationReady() { - + } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/DefaultPerformanceService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/DefaultPerformanceService.cs new file mode 100644 index 000000000..59236f667 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/DefaultPerformanceService.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Core.DI; +using Tango.Integration.ExternalBridge; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.Performance; + +namespace Tango.PPC.Common.Performance +{ + [TangoCreateWhenRegistered] + public class DefaultPerformanceService : ExtendedObject, IPerformanceService + { + #region Nested Classes + + public static class PerformanceInfo + { + [DllImport("psapi.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetPerformanceInfo([Out] out PerformanceInformation PerformanceInformation, [In] int Size); + + [StructLayout(LayoutKind.Sequential)] + public struct PerformanceInformation + { + public int Size; + public IntPtr CommitTotal; + public IntPtr CommitLimit; + public IntPtr CommitPeak; + public IntPtr PhysicalTotal; + public IntPtr PhysicalAvailable; + public IntPtr SystemCache; + public IntPtr KernelTotal; + public IntPtr KernelPaged; + public IntPtr KernelNonPaged; + public IntPtr PageSize; + public int HandlesCount; + public int ProcessCount; + public int ThreadCount; + } + + public static Int64 GetPhysicalAvailableMemoryInMiB() + { + PerformanceInformation pi = new PerformanceInformation(); + if (GetPerformanceInfo(out pi, Marshal.SizeOf(pi))) + { + return Convert.ToInt64((pi.PhysicalAvailable.ToInt64() * pi.PageSize.ToInt64() / 1048576)); + } + else + { + return -1; + } + + } + + public static Int64 GetTotalMemoryInMiB() + { + PerformanceInformation pi = new PerformanceInformation(); + if (GetPerformanceInfo(out pi, Marshal.SizeOf(pi))) + { + return Convert.ToInt64((pi.PhysicalTotal.ToInt64() * pi.PageSize.ToInt64() / 1048576)); + } + else + { + return -1; + } + + } + } + + #endregion + + private class PerformanceClient + { + public ExternalBridgeReceiver Receiver { get; set; } + public String Token { get; set; } + } + + private List _clients; + private PerformancePackage _package; + private bool _isStarted; + private Thread _performanceThread; + + public bool Enabled { get; set; } = true; + + public DefaultPerformanceService(IPPCExternalBridgeService externalBridge) + { + _package = new PerformancePackage(); + _clients = new List(); + externalBridge.RegisterRequestHandler(this); + } + + [ExternalBridgeRequestHandlerMethod(typeof(StartPerformanceUpdatesRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnStartPerformanceUpdatesRequest(StartPerformanceUpdatesRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + if (!_clients.Exists(x => x.Receiver == receiver)) + { + _clients.Add(new PerformanceClient() { Receiver = receiver, Token = token }); + OnReceiversChanged(); + } + + await receiver.SendGenericResponse(new StartPerformanceUpdatesResponse() { Package = _package }, token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + _clients.RemoveAll(x => x.Receiver == receiver); + OnReceiversChanged(); + } + + private void OnReceiversChanged() + { + if (_clients.Count > 0 && !_isStarted) + { + _isStarted = true; + _performanceThread = new Thread(PerformanceThreadMethod); + _performanceThread.IsBackground = true; + _performanceThread.Start(); + } + else if (_clients.Count == 0 && _isStarted) + { + _isStarted = false; + } + } + + private async void PerformanceThreadMethod() + { + while (_isStarted) + { + try + { + _package.ApplicationCPU = (int)GetAppCPU(); + _package.CPU = (int)GetTotalCPU(); + _package.ApplicationRAM = (int)BytesToMegaBytes(GetAppRam()); + _package.MaxRAM = (int)BytesToMegaBytes((long)new Microsoft.VisualBasic.Devices.ComputerInfo().TotalPhysicalMemory); + _package.RAM = _package.MaxRAM - (int)PerformanceInfo.GetPhysicalAvailableMemoryInMiB(); + + DriveInfo info = new DriveInfo("C"); + _package.DiskCapacity = (int)BytesToMegaBytes(info.TotalSize); + _package.AvailableDiskSpace = (int)BytesToMegaBytes(info.AvailableFreeSpace); + _package.DateTime = DateTime.Now; + + foreach (var client in _clients.ToList()) + { + try + { + await client.Receiver.SendGenericResponse(new StartPerformanceUpdatesResponse() { Package = _package }, client.Token); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error sending performance package."); + } + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error creating performance package."); + } + + Thread.Sleep(200); + } + } + + #region Helpers + + private float BytesToMegaBytes(long bytes) + { + return bytes / 1024f / 1024f; + } + + public float GetAppCPU() + { + PerformanceCounter cpuCounter = new PerformanceCounter(); + cpuCounter.CategoryName = "Process"; + cpuCounter.CounterName = "% Processor Time"; + cpuCounter.InstanceName = Process.GetCurrentProcess().ProcessName; + + // will always start at 0 + float firstValue = cpuCounter.NextValue(); + System.Threading.Thread.Sleep(1000); + // now matches task manager reading + float secondValue = cpuCounter.NextValue(); + + return secondValue / Environment.ProcessorCount; + } + + public float GetTotalCPU() + { + PerformanceCounter cpuCounter = new PerformanceCounter(); + cpuCounter.CategoryName = "Processor"; + cpuCounter.CounterName = "% Processor Time"; + cpuCounter.InstanceName = "_Total"; + + // will always start at 0 + float firstValue = cpuCounter.NextValue(); + System.Threading.Thread.Sleep(1000); + // now matches task manager reading + float secondValue = cpuCounter.NextValue(); + + return secondValue; + } + + public long GetAppRam() + { + Process proc = Process.GetCurrentProcess(); + return proc.PrivateMemorySize64; + } + + #endregion + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/IPerformanceService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/IPerformanceService.cs new file mode 100644 index 000000000..29e69aee2 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Performance/IPerformanceService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Integration.ExternalBridge; + +namespace Tango.PPC.Common.Performance +{ + public interface IPerformanceService : IPPCService, IExternalBridgeRequestHandler + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.Designer.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.Designer.cs index 60e2bdb01..30828af87 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.Designer.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Tango.PPC.Common.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -59,5 +59,25 @@ namespace Tango.PPC.Common.Properties { resourceCulture = value; } } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap finger3 { + get { + object obj = ResourceManager.GetObject("finger3", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap tap { + get { + object obj = ResourceManager.GetObject("tap", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.resx b/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.resx index af7dbebba..ca6197f54 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.resx +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Properties/Resources.resx @@ -46,7 +46,7 @@ mimetype: application/x-microsoft.net.object.binary.base64 value : The object must be serialized with - : System.Serialization.Formatters.Binary.BinaryFormatter + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.soap.base64 @@ -60,6 +60,7 @@ : and then encoded with base64 encoding. --> + @@ -68,9 +69,10 @@ - + + @@ -85,9 +87,10 @@ - + + @@ -109,9 +112,16 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\finger3.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\tap.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PPCPublisher.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PPCPublisher.cs index 526d4465a..1a289ff50 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PPCPublisher.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PPCPublisher.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Tango.AdvancedInstaller; using Tango.Core; using Tango.Core.Helpers; +using Tango.Git; using Tango.PMR.FirmwareUpgrade; using Tango.PPC.Common.Web; using Tango.SQLExaminer; @@ -59,14 +60,11 @@ namespace Tango.PPC.Common.Publish /// Gets the latest version. /// /// - public async Task GetRemoteVersion(String machineVersionGuid) + public async Task GetRemoteVersion(String machineVersionGuid) { _client.Environment = Options.Environment; - var response = await _client.GetLatestVersion(new LatestVersionRequest() - { - MachineVersionGuid = machineVersionGuid, - }); - return response.Version; + var response = await _client.GetLatestVersion(new LatestVersionRequest() { MachineVersionGuid = machineVersionGuid }); + return response; } /// @@ -163,8 +161,12 @@ namespace Tango.PPC.Common.Publish OnPublishProgress(0, 100, $"Fetching remote version from {Options.Environment.ToAddress()}..."); - String remote_version = GetRemoteVersion(Options.MachineVersionGuid).Result; + var r = GetRemoteVersion(Options.MachineVersionGuid).Result; + String remote_version = r.Version; + String remote_firmware_version = r.FirmwareVersion; + String local_version = GetLocalVersion(); + String local_firmware_version = GetLocalFirmwareVersion(Options.TfpPath); OnPublishProgress(0, 100, $"Remote version: {remote_version}"); OnPublishProgress(0, 100, $"Local version: {local_version}"); @@ -174,6 +176,11 @@ namespace Tango.PPC.Common.Publish throw new InvalidOperationException($"The local version '{local_version}' is not greater than the remote version '{remote_version}'."); } + if (Version.Parse(local_firmware_version) < Version.Parse(remote_firmware_version)) + { + throw new InvalidOperationException($"The local firmware version '{local_firmware_version}' is not greater than the remote version '{remote_firmware_version}'."); + } + OnPublishProgress(0, 100, $"Requesting version upload..."); var response = _client.UploadVersion(new UploadVersionRequest() @@ -185,6 +192,7 @@ namespace Tango.PPC.Common.Publish FirmwareVersion = GetVersionInfoFromTFP(Options.TfpPath).FileDescriptors.SingleOrDefault(x => x.Destination == VersionFileDestination.Mcu).Version, }).Result; + CreateTupPackage(tempFile).Wait(); if (!String.IsNullOrWhiteSpace(Options.InstallerProject)) { @@ -210,8 +218,6 @@ namespace Tango.PPC.Common.Publish } } - CreateTupPackage(tempFile).Wait(); - OnPublishProgress(0, 100, $"Starting version upload..."); using (StorageBlobUploader uploader = new StorageBlobUploader(response.BlobAddress, tempFile)) @@ -234,7 +240,7 @@ namespace Tango.PPC.Common.Publish Token = response.Token, }).Wait(); - remote_version = GetRemoteVersion(Options.MachineVersionGuid).Result; + remote_version = GetRemoteVersion(Options.MachineVersionGuid).Result.Version; local_version = GetLocalVersion(); OnPublishProgress(0, 0, $"Remote version: {remote_version}"); @@ -245,6 +251,55 @@ namespace Tango.PPC.Common.Publish throw new InvalidOperationException("The remote version does not seems to have been updated."); } + if (Options.CreateTag) + { + String repoPath = Path.GetFullPath("../../../../../"); + String tagVersion = System.Version.Parse(GetLocalVersion()).ToString(3); + String tagName = $"PPC_v{tagVersion}"; + + using (GitRepositoryManager git = new GitRepositoryManager(repoPath, Options.Email, Options.PersonalAccessToken)) + { + OnPublishProgress(0, 100, "Checking repository changes..."); + int changes = git.GetChanges().Count; + if (changes > 0) + { + if (Options.AutoCommitAndPush) + { + OnPublishProgress(0, 100, "Committing repository changes..."); + git.Commit(tagName); + } + else + { + throw new InvalidOperationException($"There are {changes} uncommitted changes on the repository. Please commit and push all changes before creating the Tag"); + } + } + + OnPublishProgress(0, 100, "Checking outgoing commits..."); + int commits = git.GetOutgoingCommits().Count; + if (commits > 0) + { + if (Options.AutoCommitAndPush) + { + OnPublishProgress(0, 100, "Pushing repository changes..."); + git.Sync(); + } + else + { + throw new InvalidOperationException($"There are {commits} outgoing commits on the repository. Please push all commits before creating the Tag"); + } + } + + git.Progress += (x, e) => + { + OnPublishProgress(e.Progress.Value, e.Progress.Maximum, $"Pushing Tag '{tagName}'..."); + }; + + OnPublishProgress(0, 100, $"Creating Tag '{tagName}'..."); + + git.CreatePushTag(tagName, Options.Comments, "Roy Ben Shabat"); + } + } + OnPublishProgress(0, 0, "Version published successfully."); } catch (Exception ex) @@ -289,13 +344,21 @@ namespace Tango.PPC.Common.Publish using (ZipFile zip = new ZipFile()) { - zip.AddFile(Options.TfpPath, "/").FileName = "firmware_package.tfp"; + zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression; + + if (Options.BuildConfig != "Debug") + { + zip.AddFile(Options.TfpPath, "/").FileName = "firmware_package.tfp"; + } PublishInfo versionInfo = new PublishInfo(); versionInfo.ApplicationVersion = GetLocalVersion(); versionInfo.Comments = Options.Comments; versionInfo.Firmware = GetVersionInfoFromTFP(Options.TfpPath); + //Validate the package. + versionInfo.Firmware.Validate(); + var versionInfoFile = TemporaryManager.CreateImaginaryFile(); File.WriteAllText(versionInfoFile, versionInfo.ToJson()); zip.AddFile(versionInfoFile, "/").FileName = "version.json"; @@ -362,6 +425,8 @@ namespace Tango.PPC.Common.Publish var cuf = zip.AddFile(update_config_file, update_dir); cuf.FileName = update_dir + "\\config.xml"; + zip.AddDirectory(folder + "\\" + "Packages", "/Packages"); + foreach (var file in Directory.GetFiles(folder, "*.*", SearchOption.TopDirectoryOnly)) { zip.AddFile(file, "/"); @@ -403,6 +468,16 @@ namespace Tango.PPC.Common.Publish } } + /// + /// Gets the MCU version from the specified TFP file. + /// + /// The TFP file. + /// + public String GetLocalFirmwareVersion(String tfpFile) + { + return GetVersionInfoFromTFP(tfpFile).GetMcuVersion().ToString(); + } + /// /// Raises the publish progress event. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishInfo.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishInfo.cs index 77717254e..1bbdb80d0 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishInfo.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishInfo.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.PMR.FirmwareUpgrade; +using Tango.Web; namespace Tango.PPC.Common.Publish { @@ -13,6 +14,9 @@ namespace Tango.PPC.Common.Publish public String ApplicationVersion { get; set; } public VersionPackageDescriptor Firmware { get; set; } public String Comments { get; set; } + public bool IsMachineTupPackage { get; set; } + public String MachineSerialNumber { get; set; } + public DeploymentSlot MachineDeploymentSlot { get; set; } public PublishInfo() { @@ -24,9 +28,25 @@ namespace Tango.PPC.Common.Publish return JsonConvert.SerializeObject(this); } - public PublishInfo FromJson(String json) + public static PublishInfo FromJson(String json) { return JsonConvert.DeserializeObject(json); } + + public String GetFirmwareVersion() + { + Version version = new Version("1.0.0.0"); + + var s = Firmware.FileDescriptors.FirstOrDefault(x => x.Destination == VersionFileDestination.Mcu); + if (s != null) + { + if (Version.TryParse(s.Version,out version)) + { + return version.ToString(); + } + } + + return version.ToString(); + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishOptions.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishOptions.cs index 4c40acb44..399a19f0d 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishOptions.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Publish/PublishOptions.cs @@ -17,6 +17,7 @@ namespace Tango.PPC.Common.Publish public event EventHandler BuidConfigChanged; public event EventHandler BasicInfoChanged; public event EventHandler MachineVersionGuidChanged; + public event EventHandler TfpPathChanged; private String basePath; [Option("path", HelpText = "Specifies the application base path.", Required = false)] @@ -79,7 +80,7 @@ namespace Tango.PPC.Common.Publish public String TfpPath { get { return _tfpPath; } - set { _tfpPath = value; RaisePropertyChangedAuto(); } + set { _tfpPath = value; RaisePropertyChangedAuto(); TfpPathChanged?.Invoke(this, new EventArgs()); } } private String _installerProject; @@ -105,6 +106,28 @@ namespace Tango.PPC.Common.Publish set { _synchronization = value; RaisePropertyChangedAuto(); } } + private String _personalAccessToken; + public String PersonalAccessToken + { + get { return _personalAccessToken; } + set { _personalAccessToken = value; RaisePropertyChangedAuto(); } + } + + private bool _createTag; + public bool CreateTag + { + get { return _createTag; } + set { _createTag = value; RaisePropertyChangedAuto(); } + } + + private bool _autoCommitAndSync; + public bool AutoCommitAndPush + { + get { return _autoCommitAndSync; } + set { _autoCommitAndSync = value; RaisePropertyChangedAuto(); } + } + + public PublishOptions() { BasePath = AppDomain.CurrentDomain.BaseDirectory + "..\\"; diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteActions/IRemoteActionsService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteActions/IRemoteActionsService.cs new file mode 100644 index 000000000..477663342 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteActions/IRemoteActionsService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.RemoteActions +{ + public interface IRemoteActionsService + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/DefaultRemoteAssistanceProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/DefaultRemoteAssistanceProvider.cs index eae13a882..c266ba7c0 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/DefaultRemoteAssistanceProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/DefaultRemoteAssistanceProvider.cs @@ -96,8 +96,12 @@ namespace Tango.PPC.Common.RemoteAssistance /// Installs the remote assistance. /// /// The machine serial number. + /// The remote assistance group. + /// The machine working environment. /// - public async Task InstallRemoteAssistance(String machineSerialNumber) + /// + /// The remote assistance service was installed but could not be found. + public async Task InstallRemoteAssistance(String machineSerialNumber, String group, String environment) { try { @@ -106,26 +110,21 @@ namespace Tango.PPC.Common.RemoteAssistance throw new FileNotFoundException($"The remote assistance installer file could not be found at {_installer_path}."); } - if (!(await IsRemoteAssistanceInstalled())) - { - CmdCommand command = new CmdCommand("msiexec.exe", $"/i \"{_installer_path}\" /qn CUSTOMCONFIGID=ke43ann APITOKEN=4765529-gon1LwO1N1TTrlLI21ji ASSIGNMENTOPTIONS=\" --reassign --alias {"TANGO-" + machineSerialNumber} --grant-easy-access\""); - command.Timeout = TimeSpan.FromSeconds(30); - await command.Run(); + String q = "\""; + //CmdCommand command = new CmdCommand("msiexec.exe", $"/i \"{_installer_path}\" /qn CUSTOMCONFIGID=ke43ann APITOKEN=4765529-gon1LwO1N1TTrlLI21ji ASSIGNMENTOPTIONS=\" --reassign --alias {"TANGO-" + machineSerialNumber}-{environment} --grant-easy-access\""); + CmdCommand command = new CmdCommand("msiexec.exe", $"/i {q}{_installer_path}{q} /qn CUSTOMCONFIGID=ke43ann APITOKEN=4765529-gon1LwO1N1TTrlLI21ji ASSIGNMENTOPTIONS={q} --group {q}{q} {group} {q}{q} --reassign --alias TANGO-{machineSerialNumber}-{environment} --grant-easy-access{q}"); + command.Timeout = TimeSpan.FromSeconds(30); + await command.Run(); - bool exist = await IsRemoteAssistanceInstalled(); + bool exist = await IsRemoteAssistanceInstalled(); - if (exist) - { - await DisableRemoteAssistance(); - } - else - { - throw new ApplicationException("The remote assistance service was installed but could not be found."); - } + if (exist) + { + await DisableRemoteAssistance(); } else { - LogManager.Log("Remote assistance is already installed."); + throw new ApplicationException("The remote assistance service was installed but could not be found."); } } catch (Exception ex) diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/IRemoteAssistanceProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/IRemoteAssistanceProvider.cs index cafb8dab9..3b7d489e6 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/IRemoteAssistanceProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteAssistance/IRemoteAssistanceProvider.cs @@ -32,7 +32,9 @@ namespace Tango.PPC.Common.RemoteAssistance /// Installs the remote assistance. /// /// The machine serial number. + /// The remote assistance group. + /// The machine working environment. /// - Task InstallRemoteAssistance(String machineSerialNumber); + Task InstallRemoteAssistance(String machineSerialNumber, String group, String environment); } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs new file mode 100644 index 000000000..8535d45d4 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs @@ -0,0 +1,584 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Authentication; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using Tango.Core; +using Tango.Core.DI; +using Tango.Integration.ExternalBridge; +using Tango.Logging; +using Tango.PPC.Common.Application; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Common.OS; +using Tango.RemoteDesktop; +using Tango.RemoteDesktop.CaptureMethods; +using Tango.RemoteDesktop.Encoders; +using Tango.RemoteDesktop.Engines; +using Tango.RemoteDesktop.Frames; +using Tango.RemoteDesktop.Input; +using Tango.RemoteDesktop.Network; +using Tango.Settings; +using Tango.Transport; +using Tango.WebRTC; +using static Tango.RemoteDesktop.Input.MouseController; + +namespace Tango.PPC.Common.RemoteDesktop +{ + [TangoCreateWhenRegistered] + public class DefaultRemoteDesktopService : ExtendedObject, IRemoteDesktopService, IExternalBridgeRequestHandler + { + private RemoteDesktopPacket _initialPacket; + private RasterScreenCaptureEngine _engine; + private PPCSettings _settings; + private List _clients; + private JsonSerializerSettings _jsonSettings; + private IOperationSystemManager _osManager; + private IPPCApplicationManager _appManager; + private bool _drawCursor; + private bool _isMouseDown; + private bool _ensureMouseDown; + + /// + /// Gets or sets a value indicating whether this is enabled. + /// + public bool Enabled { get; set; } = true; + + private bool _isStarted; + /// + /// Gets a value indicating whether the remote desktop service has started. + /// + public bool IsStarted + { + get { return _isStarted; } + private set { _isStarted = value; RaisePropertyChangedAuto(); } + } + + /// + /// Gets a value indicating whether there is any active remote desktop session with a remote peer. + /// + public bool InSession + { + get + { + return _clients.Count > 0; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The application manager. + /// The external bridge. + /// The os manager. + public DefaultRemoteDesktopService(IPPCApplicationManager applicationManager, IPPCExternalBridgeService externalBridge, IOperationSystemManager osManager) + { + _osManager = osManager; + _appManager = applicationManager; + + _jsonSettings = new JsonSerializerSettings() + { + TypeNameHandling = TypeNameHandling.All + }; + + _settings = SettingsManager.Default.GetOrCreate(); + Enabled = _settings.EnableRemoteDesktop; + + applicationManager.ApplicationReady += ApplicationManager_ApplicationReady; + + externalBridge.RegisterRequestHandler(this); + + _clients = new List(); + _engine = new RasterScreenCaptureEngine(); + + _engine.CaptureCursor = false; + + _engine.FrameRate = Math.Min(Math.Max(_settings.RemoteDesktopFrameRate, 1), 20); + _engine.FrameReceived += _engine_FrameReceived; + } + + private void ApplicationManager_ApplicationReady(object sender, EventArgs e) + { + + + var mainWindow = System.Windows.Application.Current.MainWindow; + + if (mainWindow.WindowStyle != System.Windows.WindowStyle.None) + { + mainWindow.LocationChanged += (_, __) => + { + _engine.CaptureRegion = new CaptureRegion() + { + Left = (int)mainWindow.Left + 10, + Top = (int)mainWindow.Top + 5, + Width = (int)mainWindow.Width - 20, + Height = (int)mainWindow.Height - 15 + }; + }; + + _engine.CaptureRegion = new CaptureRegion() + { + Left = (int)mainWindow.Left + 10, + Top = (int)mainWindow.Top + 5, + Width = (int)mainWindow.Width - 20, + Height = (int)mainWindow.Height - 15 + }; + } + else + { + //DirectX capturing is not working on PPC !! Maybe when we replace ? + //try + //{ + // _engine.CaptureMethod = new DirectXScreenCapture(); + //} + //catch (Exception ex) + //{ + // LogManager.Log(ex, "Could not initialize DirectX screen capture method on this device. Falling back to GDI."); + //} + } + + mainWindow.PreviewMouseDown += (_, __) => + { + _isMouseDown = true; + _ensureMouseDown = true; + }; + + mainWindow.PreviewMouseUp += (_, __) => + { + _isMouseDown = false; + }; + + _engine.Comparer.MaxDifferencesThrow = _engine.CaptureRegion.Width * _engine.CaptureRegion.Height / 2; + } + + [ExternalBridgeRequestHandlerMethod(typeof(StartRemoteDesktopSessionRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnStartRemoteDesktopSessionRequestReceived(StartRemoteDesktopSessionRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + var client = _clients.SingleOrDefault(x => x.Receiver == receiver); + + if (client != null) + { + _clients.Remove(client); + } + + RemoteDesktopClient newClient = new RemoteDesktopClient(); + newClient.Receiver = receiver; + newClient.Token = token; + newClient.WebRtcClient = new WebRtcClient(); + newClient.WebRtcClient.TextMessageReceived += WebRtcClient_TextMessageReceived; + _clients.Add(newClient); + + await receiver.SendGenericResponse(new StartRemoteDesktopSessionResponse() + { + FrameRate = _engine.FrameRate + }, token); + + + if (!_engine.IsStarted) + { + _engine.Start(); + } + + RaisePropertyChanged(nameof(InSession)); + } + + [ExternalBridgeRequestHandlerMethod(typeof(WebRtcIceCandidateRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnWebRtcIceCandidateRequestReceived(WebRtcIceCandidateRequest request, String token, ExternalBridgeReceiver receiver) + { + var client = _clients.SingleOrDefault(x => x.Receiver == receiver); + + if (client != null) + { + try + { + await receiver.SendGenericResponse(new WebRtcIceCandidateResponse() { }, token); + client.WebRtcClient.AddIceCandidate(request.IceCandidate); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error adding WebRTC ice candidate received from the remote connection."); + } + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(WebRtcOfferRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnWebRtcOfferRequestReceived(WebRtcOfferRequest request, String token, ExternalBridgeReceiver receiver) + { + var client = _clients.SingleOrDefault(x => x.Receiver == receiver); + + if (client != null) + { + try + { + + try + { + client.WebRtcClient.NewIceCandidate += async (x, e) => + { + try + { + await receiver.SendGenericRequest(new WebRtcIceCandidateRequest() { IceCandidate = e.IceCandidate }); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error sending ice candidate to remote peer."); + } + }; + client.WebRtcClient.Ready += (x, e) => + { + client.IsWebRtcReady = true; + }; + client.WebRtcClient.Disconnected += (x, e) => + { + client.IsWebRtcReady = false; + }; + + client.WebRtcClient.FrameWidth = 800; + client.WebRtcClient.FrameHeight = 1280; + client.WebRtcClient.FrameRate = _engine.FrameRate; + + await client.WebRtcClient.Init(); + + var answer = await client.WebRtcClient.CreateAnswer(request.Offer); + await receiver.SendGenericResponse(new WebRtcOfferResponse() { Answer = answer }, token); + } + catch (Exception ex) + { + throw LogManager.Log(ex, "Error initializing the WebRTC client."); + } + } + catch (Exception ex) + { + throw LogManager.Log(ex, "Error responding to WebRTC offer request."); + } + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(StopRemoteDesktopSessionRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnStopRemoteDesktopSessionRequestReceived(StopRemoteDesktopSessionRequest request, String token, ExternalBridgeReceiver receiver) + { + var client = _clients.SingleOrDefault(x => x.Receiver == receiver); + + if (client != null) + { + _clients.Remove(client); + + if (client.WebRtcClient != null) + { + client.WebRtcClient.Dispose(); + } + } + + if (_clients.Count == 0) + { + _engine.Stop(); + } + + await receiver.SendGenericResponse(new StopRemoteDesktopSessionResponse(), token); + + if (client != null) + { + await receiver.SendGenericResponse(new StartRemoteDesktopSessionResponse(), client.Token, new TransportResponseConfig() { Completed = true }); + } + + RaisePropertyChanged(nameof(InSession)); + } + + [ExternalBridgeRequestHandlerMethod(typeof(MouseStateRequest))] + public async Task OnMouseStateRequestReceived(MouseStateRequest request, String token, ExternalBridgeReceiver receiver) + { + MouseController.SetCursorPosition((int)request.Location.X, (int)request.Location.Y); + + if (request.EventType == MouseEventType.Up || request.EventType == MouseEventType.Down) + { + MouseEventFlags flag = MouseEventFlags.LeftUp; + + switch (request.EventType) + { + case MouseEventType.Down: + flag = request.Button == MouseButton.Right ? MouseEventFlags.RightDown : MouseEventFlags.LeftDown; + break; + case MouseEventType.Up: + flag = request.Button == MouseButton.Right ? MouseEventFlags.RightUp : MouseEventFlags.LeftUp; + break; + } + + MouseController.MouseEvent(flag); + } + else if (request.EventType == MouseEventType.DoubleClick) + { + MouseController.DoubleClick(); + } + else if (request.EventType == MouseEventType.Scroll) + { + MouseController.Scroll(request.ScrollDelta); + } + + if (receiver != null) + { + await receiver.SendGenericResponse(new MouseStateResponse(), token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(TouchStateRequest))] + public async Task OnTouchStateRequestReceived(TouchStateRequest request, String token, ExternalBridgeReceiver receiver) + { + switch (request.EventType) + { + case TouchEventType.TouchDown: + TouchController.TouchDown((int)request.Location.X, (int)request.Location.Y); + break; + case TouchEventType.TouchMove: + TouchController.TouchMove(request.MoveDeltaX, request.MoveDeltaY); + break; + case TouchEventType.TouchUp: + TouchController.TouchUp(); + break; + } + + if (receiver != null) + { + await receiver.SendGenericResponse(new TouchStateResponse(), token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(KeyboardStateRequest))] + public async Task OnKeyboardStateRequestReceived(KeyboardStateRequest request, String token, ExternalBridgeReceiver receiver) + { + if (request.EventType == KeyboardEventType.Down) + { + KeyboardController.KeyDown(request.Key, request.IsCtrlDown, request.IsShiftDown, request.IsAltDown); + } + else + { + KeyboardController.KeyUp(request.Key, request.IsCtrlDown, request.IsShiftDown, request.IsAltDown); + } + + if (receiver != null) + { + await receiver.SendGenericResponse(new KeyboardStateResponse(), token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteDesktopCommandRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRemoteDesktopCommandRequest(RemoteDesktopCommandRequest request, String token, ExternalBridgeReceiver receiver) + { + switch (request.Command) + { + case RemoteDesktopCommand.HideAndOpenShell: + _osManager.OpenShell(); + _appManager.SetWindowState(System.Windows.WindowState.Minimized); + break; + } + + await receiver.SendGenericResponse(new RemoteDesktopCommandResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(SetCursorVisibilityRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnSetCursorVisibilityRequest(SetCursorVisibilityRequest request, String token, ExternalBridgeReceiver receiver) + { + _drawCursor = request.Visible; + await receiver.SendGenericResponse(new SetCursorVisibilityResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(GetScreenshotRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnGetScreenshotRequest(GetScreenshotRequest request, String token, ExternalBridgeReceiver receiver) + { + GdiScreenCapture capture = new GdiScreenCapture(); + var bitmap = capture.GetDesktopBitmap(CaptureRegion.PrimaryScreenBounds()); + RasterFrame frame = new RasterFrame(bitmap); + var data = frame.ToEncoder().ToArray(80); + frame.Dispose(); + + await receiver.SendGenericResponse(new GetScreenshotResponse() { Bitmap = data }, token); + } + + private async void _engine_FrameReceived(object sender, ScreenCaptureFrameReceivedEventArgs e) + { + try + { + if (_drawCursor) + { + e.Frame.DrawImage((_isMouseDown || _ensureMouseDown) ? Properties.Resources.tap : Properties.Resources.finger3, new System.Drawing.Point(System.Windows.Forms.Cursor.Position.X - 5, System.Windows.Forms.Cursor.Position.Y - 4)); + _ensureMouseDown = false; + } + + _initialPacket = new RemoteDesktopPacket() + { + Bitmap = e.Frame.ToEncoder().ToArray(), + }; + + if (_clients.Count > 0) + { + bool useWebRTC = _clients.ToList().All(x => x.IsWebRtcReady); + + if (useWebRTC) + { + _engine.EnableComparer = false; + + foreach (var client in _clients.ToList()) + { + try + { + client.WebRtcClient.PushFrame(e.Frame.ToBitmap()); + } + catch (Exception ex) + { + LogManager.Log(ex, LogCategory.Debug, "Error pushing remote desktop frame via WebRTC channel."); + } + } + + e.Frame.Dispose(); + } + else + { + _engine.EnableComparer = true; + + foreach (var client in _clients.ToList().Where(x => !x.InitialPacketSent)) + { + try + { + await client.Receiver.SendGenericResponse(new StartRemoteDesktopSessionResponse() + { + Packet = _initialPacket, + }, client.Token, new TransportResponseConfig() + { + Immediate = false, + }); + + client.InitialPacketSent = true; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + + if (e.Frame.DifferenceCount > 0) + { + RemoteDesktopPacket packet = null; + + Point mousePosition = new Point(System.Windows.Forms.Cursor.Position.X, System.Windows.Forms.Cursor.Position.Y); + + if (!e.Frame.DifferenceAvailable) + { + packet = new RemoteDesktopPacket() + { + Bitmap = e.Frame.ToEncoder().ToArray(30), + MousePosition = mousePosition, + CursorVisible = _drawCursor + }; + } + else + { + var diffFrame = e.Frame.ToDifference(); + diffFrame = diffFrame.OptimizeBounds(); + + packet = new RemoteDesktopPacket() + { + Bitmap = diffFrame.ToEncoder().ToArray(), + IsPartial = true, + PartialRegion = new CaptureRegion(diffFrame.Left, diffFrame.Top, diffFrame.Width, diffFrame.Height), + MousePosition = mousePosition, + CursorVisible = _drawCursor + }; + + diffFrame.Dispose(); + } + + Debug.WriteLine($"Remote Desktop Bitmap Size: {packet.Bitmap.Length / 1000} kb"); + + foreach (var client in _clients.ToList().Where(x => x.InitialPacketSent)) + { + try + { + await client.Receiver.SendGenericResponse(new StartRemoteDesktopSessionResponse() + { + Packet = packet + }, client.Token, new TransportResponseConfig() + { + Immediate = false, + }); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + } + } + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error occurred on remote desktop engine frame received event."); + } + finally + { + e.Frame.Dispose(); + } + } + + private async void WebRtcClient_TextMessageReceived(object sender, DataMessageReceivedEventArgs e) + { + try + { + var request = JsonConvert.DeserializeObject(e.Data, _jsonSettings); + + if (request.GetType() == typeof(MouseStateRequest)) + { + await OnMouseStateRequestReceived(request as MouseStateRequest, null, null); + } + else if (request.GetType() == typeof(KeyboardStateRequest)) + { + await OnKeyboardStateRequestReceived(request as KeyboardStateRequest, null, null); + } + else if (request.GetType() == typeof(TouchStateRequest)) + { + await OnTouchStateRequestReceived(request as TouchStateRequest, null, null); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error deserializing incoming message on the WebRTC data Channel."); + } + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + var client = _clients.SingleOrDefault(x => x.Receiver == receiver); + + if (client != null) + { + LogManager.Log("Remote desktop client disconnected. Disposing WebRTC channel..."); + + _clients.Remove(client); + + try + { + if (client.WebRtcClient != null) + { + client.WebRtcClient.Dispose(); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error disposing the WebRTC channel."); + } + } + + if (_clients.Count == 0) + { + _engine.Stop(); + } + + RaisePropertyChanged(nameof(InSession)); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs new file mode 100644 index 000000000..5e4a801d7 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.RemoteDesktop +{ + /// + /// Represents a PPC remote desktop service. + /// + public interface IRemoteDesktopService : IPPCService + { + /// + /// Gets a value indicating whether the remote desktop service has started. + /// + bool IsStarted { get; } + + /// + /// Gets a value indicating whether there is any active remote desktop session with a remote peer. + /// + bool InSession { get; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/RemoteDesktopClient.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/RemoteDesktopClient.cs new file mode 100644 index 000000000..f0f0a87de --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/RemoteDesktopClient.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Integration.ExternalBridge; +using Tango.WebRTC; + +namespace Tango.PPC.Common.RemoteDesktop +{ + public class RemoteDesktopClient + { + public String Token { get; set; } + public ExternalBridgeReceiver Receiver { get; set; } + public bool InitialPacketSent { get; set; } + public WebRtcClient WebRtcClient { get; set; } + public bool IsWebRtcReady { get; set; } + } + +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/DefaultRemoteJobService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/DefaultRemoteJobService.cs new file mode 100644 index 000000000..8826a8be3 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/DefaultRemoteJobService.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Core; +using Tango.Core.DI; +using Tango.Integration.ExternalBridge; +using Tango.Integration.Operation; +using Tango.PPC.Common.Connection; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.Jobs; + +namespace Tango.PPC.Common.RemoteJob +{ + [TangoCreateWhenRegistered] + public class DefaultRemoteJobService : IRemoteJobService, IExternalBridgeRequestHandler + { + private class RunningJobUpdateClient + { + public String Token { get; set; } + public ExternalBridgeReceiver Receiver { get; set; } + public bool JobSent { get; set; } + } + + private List _clients; + private JobHandler _handler; + private JobDTO _currentJobDTO; + private ProcessParametersTableDTO _currentJobProcessParameters; + + public bool Enabled { get; set; } = true; + + private IMachineProvider MachineProvider { get; set; } + + public DefaultRemoteJobService(IPPCExternalBridgeService externalBridge, IMachineProvider machineProvider) + { + externalBridge.RegisterRequestHandler(this); + + MachineProvider = machineProvider; + + _clients = new List(); + MachineProvider.MachineOperator.PrintingStarted += MachineOperator_PrintingStarted; + } + + private async void MachineOperator_PrintingStarted(object sender, Integration.Operation.PrintingEventArgs e) + { + _handler = e.JobHandler; + + e.JobHandler.StatusChanged += JobHandler_StatusChanged; + e.JobHandler.Stopped += JobHandler_Stopped; + + _currentJobDTO = JobDTO.FromObservable(e.Job); + _currentJobProcessParameters = ProcessParametersTableDTO.FromObservable(_handler.ProcessParameters); + + foreach (var client in _clients.ToList()) + { + try + { + RemoteJobProgress progress = new RemoteJobProgress(); + progress.Stage = RemoteJobStage.Started; + progress.JobStatus = _handler.JobStatus; + + await client.Receiver.SendGenericResponse(new RemoteJobUpdateResponse() + { + Job = _currentJobDTO, + ProcessParameters = _currentJobProcessParameters, + Progress = progress + }, client.Token); + + client.JobSent = true; + } + catch { } + } + } + + private async void JobHandler_StatusChanged(object sender, RunningJobStatus e) + { + foreach (var client in _clients.ToList()) + { + if (client.JobSent) + { + try + { + await client.Receiver.SendGenericResponse(new RemoteJobUpdateResponse() + { + Progress = GetJobProgress(_handler) + }, client.Token); + } + catch { } + } + else + { + try + { + RemoteJobProgress progress = new RemoteJobProgress(); + progress.Stage = RemoteJobStage.Started; + progress.JobStatus = _handler.JobStatus; + + await client.Receiver.SendGenericResponse(new RemoteJobUpdateResponse() + { + Job = _currentJobDTO, + ProcessParameters = _currentJobProcessParameters, + Progress = progress + }, client.Token); + + client.JobSent = true; + } + catch { } + } + } + } + + private async void JobHandler_Stopped(object sender, EventArgs e) + { + foreach (var client in _clients.ToList().Where(x => x.JobSent)) + { + try + { + await client.Receiver.SendGenericResponse(new RemoteJobUpdateResponse() + { + Progress = GetJobProgress(_handler), + }, client.Token); + + client.JobSent = false; + } + catch { } + } + } + + private RemoteJobProgress GetJobProgress(JobHandler handler) + { + RemoteJobStage stage = RemoteJobStage.Running; + + if (_handler.Status.IsCanceled) + { + stage = RemoteJobStage.Aborted; + } + else if (_handler.Status.IsCompleted) + { + stage = RemoteJobStage.Completed; + } + else if (_handler.Status.IsFailed) + { + stage = RemoteJobStage.Failed; + } + + return new RemoteJobProgress() + { + Stage = stage, + JobStatus = handler.JobStatus, + }; + } + + [ExternalBridgeRequestHandlerMethod(typeof(RemoteJobUpdateRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnRunningJobUpdateRequest(RemoteJobUpdateRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + if (!_clients.ToList().Exists(x => x.Receiver == receiver)) + { + _clients.Add(new RunningJobUpdateClient() + { + Receiver = receiver, + Token = token + }); + } + + await receiver.SendGenericResponse(new RemoteJobUpdateResponse(), token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + _clients.RemoveAll(x => x.Receiver == receiver); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/IRemoteJobService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/IRemoteJobService.cs new file mode 100644 index 000000000..e7bfdbec1 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteJob/IRemoteJobService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Integration.ExternalBridge; + +namespace Tango.PPC.Common.RemoteJob +{ + public interface IRemoteJobService : IPPCService + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Colors.xaml b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Colors.xaml index 03f4b6f36..5fdbcf5f8 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Colors.xaml +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Colors.xaml @@ -2,6 +2,17 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Tango.PPC.Common.Resources"> + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Merged.xaml b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Merged.xaml index 730066b46..09762d7ce 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Merged.xaml +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/Merged.xaml @@ -21,6 +21,9 @@ + + + @@ -51,6 +54,13 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/finger3.png b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/finger3.png new file mode 100644 index 000000000..c0a6ce9cd Binary files /dev/null and b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/finger3.png differ diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/tap.png b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/tap.png new file mode 100644 index 000000000..4fa679b81 Binary files /dev/null and b/Software/Visual_Studio/PPC/Tango.PPC.Common/Resources/tap.png differ diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/DefaultRemoteSqlService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/DefaultRemoteSqlService.cs new file mode 100644 index 000000000..e5ac43d3f --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/DefaultRemoteSqlService.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.BL; +using Tango.Core.DI; +using Tango.Integration.ExternalBridge; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.SQL; + +namespace Tango.PPC.Common.SQL +{ + [TangoCreateWhenRegistered] + public class DefaultRemoteSqlService : IRemoteSqlService, IExternalBridgeRequestHandler + { + public bool Enabled { get; set; } = true; + + public DefaultRemoteSqlService(IPPCExternalBridgeService externalBridge) + { + externalBridge.RegisterRequestHandler(this); + } + + [ExternalBridgeRequestHandlerMethod(typeof(ExecuteSqlRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnExecuteSqlRequest(ExecuteSqlRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + RemoteSqlDataSet dataSet = new RemoteSqlDataSet(); + int affected = 0; + + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + using (SqlConnection connection = new SqlConnection(db.Database.Connection.ConnectionString)) + { + SqlCommand command = new SqlCommand(request.SQL, connection); + connection.Open(); + + SqlDataReader reader = command.ExecuteReader(); + affected = reader.RecordsAffected; + + dataSet = await RemoteSqlDataSet.Load(reader); + } + } + + await receiver.SendGenericResponse(new ExecuteSqlResponse() + { + DataSet = dataSet, + AffectedRecords = affected + }, token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/IRemoteSqlService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/IRemoteSqlService.cs new file mode 100644 index 000000000..f70589090 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/SQL/IRemoteSqlService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.SQL +{ + public interface IRemoteSqlService : IPPCService + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/SafetyLevelOperations.csv b/Software/Visual_Studio/PPC/Tango.PPC.Common/SafetyLevelOperations.csv new file mode 100644 index 000000000..e8fe002b6 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/SafetyLevelOperations.csv @@ -0,0 +1,3 @@ +MessageType, +MotorHomingRequest, +MotorJoggingRequest \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/DefaultStorageProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/DefaultStorageProvider.cs index 46315e4b8..5f097d303 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/DefaultStorageProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/DefaultStorageProvider.cs @@ -21,7 +21,7 @@ namespace Tango.PPC.Common.Storage public class DefaultStorageProvider : ExtendedObject, IStorageProvider { private Thread _scanThread; - private Dictionary> _fileHandlers; + private Dictionary>> _fileHandlers; /// /// Occurs when a new storage drive has been inserted. @@ -58,7 +58,7 @@ namespace Tango.PPC.Common.Storage /// public DefaultStorageProvider(IPPCApplicationManager applicationManager) { - _fileHandlers = new Dictionary>(); + _fileHandlers = new Dictionary>>(); var drives = DriveInfo.GetDrives().Where(x => x.DriveType == DriveType.Removable).ToList(); if (drives.Count > 0) @@ -86,7 +86,7 @@ namespace Tango.PPC.Common.Storage /// The file extension. /// The handler. /// Cannot register multiple file handlers for the same extension. - public void RegisterFileHandler(string extension, Action handler) + public void RegisterFileHandler(string extension, Action> handler) { if (_fileHandlers.ContainsKey(extension)) { @@ -99,7 +99,7 @@ namespace Tango.PPC.Common.Storage /// Unregisters the file handler. /// /// The handler. - public void UnregisterFileHandler(Action handler) + public void UnregisterFileHandler(Action> handler) { var h = _fileHandlers.SingleOrDefault(x => x.Value == handler); @@ -112,14 +112,17 @@ namespace Tango.PPC.Common.Storage /// /// Submits a file selection. /// - /// The file item. - public void SubmitFileSelection(ExplorerFileItem fileItem) + /// The file item. + public void SubmitFileSelection(List fileItems) { - String extension = Path.GetExtension(fileItem.Path); - - if (_fileHandlers.ContainsKey(extension)) + if (fileItems != null && fileItems.Count > 0) { - _fileHandlers[extension].Invoke(fileItem); + String extension = Path.GetExtension(fileItems.First().Path); + + if (_fileHandlers.ContainsKey(extension)) + { + _fileHandlers[extension].Invoke(fileItems); + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/IStorageProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/IStorageProvider.cs index 902021002..2a9cf4e90 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/IStorageProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Storage/IStorageProvider.cs @@ -38,18 +38,18 @@ namespace Tango.PPC.Common.Storage /// /// The file extension. /// The handler. - void RegisterFileHandler(String extension, Action handler); + void RegisterFileHandler(String extension, Action> handler); /// /// Unregisters the file handler. /// /// The handler. - void UnregisterFileHandler(Action handler); + void UnregisterFileHandler(Action> handler); /// /// Submits a file selection. /// /// The file item. - void SubmitFileSelection(ExplorerFileItem fileItem); + void SubmitFileSelection(List fileItems); } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/DefaultMachineDataSynchronizer.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/DefaultMachineDataSynchronizer.cs new file mode 100644 index 000000000..5a951e9fa --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/DefaultMachineDataSynchronizer.cs @@ -0,0 +1,662 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Timers; +using Tango.BL; +using Tango.PPC.Common.Web; +using System.Data.Entity; +using Tango.BL.DTO; +using Tango.PPC.Common.Connection; +using Tango.BL.Builders; +using Tango.Settings; +using Tango.Core; +using Tango.PPC.Common.Authentication; +using Tango.Logging; +using System.Diagnostics; +using Tango.BL.Enumerations; +using Tango.PPC.Common.Application; +using Tango.Core.DI; + +namespace Tango.PPC.Common.Synchronization +{ + public class DefaultMachineDataSynchronizer : ExtendedObject, IMachineDataSynchronizer + { + private Timer _synchTimer; + private PPCWebClient client; + private IMachineProvider _machineProvider; + private IAuthenticationProvider _authenticationProvider; + private List _logs; + private bool _synchronizedOnce; + + [TangoInject(TangoInjectMode.WhenAvailable)] + private IPPCApplicationManager _appManager; + + public event EventHandler CurrentStatusChanged; + public event EventHandler SynchronizationStarted; + public event EventHandler SynchronizationEnded; + + public int MaxJobs { get; set; } + public int MaxJobRuns { get; set; } + public int MaxMachinesEvents { get; set; } + public int MaxOfflineUpdates { get; set; } + public int MaxDataStoreItems { get; set; } + + private SynchronizationStatus _currentStatus; + public SynchronizationStatus CurrentStatus + { + get { return _currentStatus; } + private set { _currentStatus = value; RaisePropertyChangedAuto(); } + } + + private SynchronizationStatus _lastStatus; + public SynchronizationStatus LastStatus + { + get { return _lastStatus; } + private set { _lastStatus = value; RaisePropertyChangedAuto(); } + } + + public SynchronizedObservableCollection StatusHistory { get; private set; } + + public TimeSpan Interval { get; set; } + + private bool _isEnabled; + public bool IsEnabled + { + get { return _isEnabled; } + set { _isEnabled = value; OnEnableChanged(); RaisePropertyChangedAuto(); } + } + + private bool _isSynchronizing; + public bool IsSynchronizing + { + get { return _isSynchronizing; } + set { _isSynchronizing = value; RaisePropertyChangedAuto(); } + } + + public DefaultMachineDataSynchronizer() + { + StatusHistory = new SynchronizedObservableCollection(); + + MaxJobs = 10; + MaxJobRuns = 10; + MaxMachinesEvents = 10; + MaxOfflineUpdates = 10; + MaxDataStoreItems = 100; + + var settings = SettingsManager.Default.GetOrCreate(); + Interval = settings.SynchronizationInterval; + + _synchTimer = new Timer(Interval.TotalMilliseconds); + _synchTimer.Elapsed += _synchTimer_Elapsed; + _synchTimer.Enabled = true; + + ExecuteNewStatus(TimeSpan.FromMinutes(2)); + LastStatus = CurrentStatus; + } + + public DefaultMachineDataSynchronizer(PPCWebClient ppcWebClient, IMachineProvider machineProvider, IAuthenticationProvider authenticationProvider) : this() + { + _logs = new List(); + _machineProvider = machineProvider; + client = new PPCWebClient(ppcWebClient, TimeSpan.FromMinutes(10)); + _authenticationProvider = authenticationProvider; + + LogManager.NewLog += LogManager_NewLog; + } + + private void LogManager_NewLog(object sender, LogItemBase e) + { + if (IsSynchronizing) + { + _logs.Add(e); + } + } + + private String GetLogsStringAndClear() + { + String logsString = String.Join(Environment.NewLine, _logs.ToList().Select(x => x.ToString())); + _logs.Clear(); + return logsString; + } + + private void OnEnableChanged() + { + _synchTimer.Interval = Interval.TotalMilliseconds; + + if (IsEnabled) + { + CurrentStatus.State = SynchronizationState.Pending; + } + else + { + CurrentStatus.State = SynchronizationState.Disabled; + } + } + + private void _synchTimer_Elapsed(object sender, ElapsedEventArgs e) + { + _synchTimer.Interval = Interval.TotalMilliseconds; + + try + { + Synchronize().GetAwaiter().GetResult(); + } + catch { } + } + + private async Task CreateUploadMachineDataRequest(bool syncJobs, bool syncDiagnostics) + { + UploadMachineDataRequest request = new UploadMachineDataRequest(); + + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + if (syncJobs) + { + LogManager.Log("Checking Jobs..."); + + var jobs = await new JobsCollectionBuilder(db).Set(x => !x.IsSynchronized).WithSegments().WithBrushStops().Query(x => x.Take(MaxJobs).OrderByDescending(z => z.LastUpdated)).BuildListAsync(); + List dtos = new List(); + + foreach (var job in jobs) + { + var dto = JobDTO.FromObservable(job); + request.Jobs.Add(dto); + } + } + + if (syncDiagnostics) + { + LogManager.Log("Checking Job Runs..."); + + var jobRuns = await db.JobRuns.Where(x => !x.IsSynchronized).Take(MaxJobRuns).OrderByDescending(x => x.LastUpdated).ToListAsync(); + List dtos = new List(); + + foreach (var jobRun in jobRuns) + { + var dto = JobRunDTO.FromObservable(jobRun); + request.JobRuns.Add(dto); + } + } + + if (syncDiagnostics) + { + LogManager.Log("Checking Events..."); + + var machineEvents = await db.MachinesEvents.Where(x => !x.IsSynchronized).Take(MaxMachinesEvents).OrderByDescending(x => x.LastUpdated).ToListAsync(); + List dtos = new List(); + + foreach (var machineEvent in machineEvents) + { + machineEvent.IsSynchronized = true; + var dto = MachinesEventDTO.FromObservable(machineEvent); + request.MachineEvents.Add(dto); + } + } + + if (syncDiagnostics) + { + LogManager.Log("Checking Offline Updates..."); + + var tangoUpdates = await db.TangoUpdates.Where(x => !x.IsSynchronized && (x.Status == (int)TangoUpdateStatuses.OfflineUpdateCompleted || x.Status == (int)TangoUpdateStatuses.OfflineUpdateFailed || x.Status == (int)TangoUpdateStatuses.OfflineFirmwareUpgradeCompleted || x.Status == (int)TangoUpdateStatuses.OfflineFirmwareUpgradeFailed)).Take(MaxOfflineUpdates).OrderByDescending(x => x.LastUpdated).ToListAsync(); + List dtos = new List(); + + foreach (var tangoUpdate in tangoUpdates) + { + tangoUpdate.IsSynchronized = true; + var dto = TangoUpdateDTO.FromObservable(tangoUpdate); + request.OfflineUpdates.Add(dto); + } + } + + { //Always synchronize data store items + LogManager.Log("Checking Data Store Items..."); + + var dataStoreItems = await db.DataStoreItems.Where(x => !x.IsSynchronized).Take(MaxDataStoreItems).ToListAsync(); + List dtos = new List(); + + foreach (var item in dataStoreItems) + { + item.IsSynchronized = true; + var dto = DataStoreItemDTO.FromObservable(item); + dto.MachineGuid = null; + request.DataStoreItems.Add(dto); + } + } + } + + return request; + } + + private async Task FinalizeMachineDataUpload(UploadMachineDataRequest request, UploadMachineDataResponse response) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + //Finalize jobs + foreach (var job in request.Jobs) + { + var failedJob = response.FailedJobs.SingleOrDefault(x => x.Guid == job.Guid); + + if (failedJob == null) + { + var dbJob = await db.Jobs.SingleOrDefaultAsync(x => x.Guid == job.Guid); + dbJob.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - Job '{job.Name}' cannot be stored on the server due to the following reason:\n{failedJob.Reason}", LogCategory.Error); + } + } + + //Finalize job runs + foreach (var jobRun in request.JobRuns) + { + var failedJobRun = response.FailedJobRuns.SingleOrDefault(x => x.Guid == jobRun.Guid); + + if (failedJobRun == null) + { + var dbJobRun = await db.JobRuns.SingleOrDefaultAsync(x => x.Guid == jobRun.Guid); + dbJobRun.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - JobRun '{jobRun.ID}' cannot be stored on the server due to the following reason:\n{failedJobRun.Reason}", LogCategory.Error); + } + } + + //Finalize machine events + foreach (var machineEvent in request.MachineEvents) + { + var failedMachineEvent = response.FailedMachineEvents.SingleOrDefault(x => x.Guid == machineEvent.Guid); + + if (failedMachineEvent == null) + { + var dbMachineEvent = await db.MachinesEvents.SingleOrDefaultAsync(x => x.Guid == machineEvent.Guid); + dbMachineEvent.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - Event '{machineEvent.ID}' cannot be stored on the server due to the following reason:\n{failedMachineEvent.Reason}", LogCategory.Error); + } + } + + //Finalize tango updates + foreach (var tangoUpdate in request.OfflineUpdates) + { + var failedTangoUpdate = response.FailedOfflineUpdates.SingleOrDefault(x => x.Guid == tangoUpdate.Guid); + + if (failedTangoUpdate == null) + { + var dbTangoUpdate = await db.TangoUpdates.SingleOrDefaultAsync(x => x.Guid == tangoUpdate.Guid); + dbTangoUpdate.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - TangoUpdate '{tangoUpdate.ID}' cannot be stored on the server due to the following reason:\n{failedTangoUpdate.Reason}", LogCategory.Error); + } + } + + //Finalize data store items + foreach (var dataStoreItem in request.DataStoreItems) + { + var failedDataStoreItem = response.FailedDataStoreItems.SingleOrDefault(x => x.Guid == dataStoreItem.Guid); + + if (failedDataStoreItem == null) + { + var dbDataStoreItem = await db.DataStoreItems.SingleOrDefaultAsync(x => x.Guid == dataStoreItem.Guid); + dbDataStoreItem.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - DataStoreItem '{dataStoreItem.Key}' cannot be stored on the server due to the following reason:\n{failedDataStoreItem.Reason}", LogCategory.Error); + } + } + + await db.SaveChangesAsync(); + } + } + + private async Task DownloadMachineData(bool syncJobs, bool syncDiagnostics) + { + return await client.DownloadMachineData(new DownloadMachineDataRequest() + { + RequestJobs = syncJobs, + RequestJobRuns = syncDiagnostics, + RequestMachineEvents = syncDiagnostics, + RequestDataStoreItems = true, + MaxJobs = MaxJobs, + MaxJobRuns = MaxJobRuns, + MaxMachinesEvents = MaxMachinesEvents, + MaxDataStoreItems = MaxDataStoreItems, + }); + } + + private async Task InsertReplaceMachineData(DownloadMachineDataResponse response) + { + NotifyMachineDataDownloadCompletedRequest request = new NotifyMachineDataDownloadCompletedRequest(); + + //Insert/Replace Jobs. + if (response.Jobs.Count > 0) + { + LogManager.Log("Inserting/Replacing Jobs..."); + } + foreach (var dto in response.Jobs) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + try + { + var job = dto.ToObservable(); + + job.ID = 0; + job.UserGuid = null; + job.CustomerGuid = null; + job.IsSynchronized = true; + + var existingJob = await db.Jobs.SingleOrDefaultAsync(x => x.Guid == job.Guid); + + if (existingJob == null) + { + db.Jobs.Add(job); + await db.SaveChangesAsync(); + } + else if (job.LastUpdated > existingJob.LastUpdated) + { + existingJob.Delete(db); + db.Jobs.Add(job); + await db.SaveChangesAsync(); + } + + request.SynchronizedJobs.Add(job.Guid); + } + catch (Exception ex) + { + LogManager.Log($"Synchronization Error - Job '{dto.Name}' cannot be stored locally due to the following reason:\n{ex.FlattenMessage()}", LogCategory.Error); + } + } + } + + //Insert/Update Data Store Items. + if (response.DataStoreItems.Count > 0) + { + LogManager.Log("Inserting/Updating Data Store Items..."); + } + foreach (var dto in response.DataStoreItems) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + try + { + var dataStoreItem = dto.ToObservable(); + + dataStoreItem.ID = 0; + dataStoreItem.MachineGuid = null; + dataStoreItem.IsSynchronized = true; + + var existingItem = db.DataStoreItems.SingleOrDefault(x => x.Guid == dataStoreItem.Guid); + + if (existingItem == null) + { + db.DataStoreItems.Add(dataStoreItem); + db.SaveChanges(); + } + else if (dataStoreItem.LastUpdated >= existingItem.LastUpdated) + { + existingItem.DataType = dataStoreItem.DataType; + existingItem.Value = dataStoreItem.Value; + existingItem.IsSynchronized = true; + existingItem.LastUpdated = dataStoreItem.LastUpdated; + db.SaveChanges(); + } + + request.SynchronizedDataStoreItems.Add(dataStoreItem.Guid); + } + catch (Exception ex) + { + LogManager.Log($"Synchronization Error - DataStoreItem '{dto.Key}' cannot be stored locally due to the following reason:\n{ex.FlattenMessage()}", LogCategory.Error); + } + } + } + + //Insert JobRuns. + if (response.JobRuns.Count > 0) + { + LogManager.Log("Inserting/Replacing Job Runs..."); + } + foreach (var dto in response.JobRuns) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + try + { + var run = dto.ToObservable(); + run.ID = 0; + run.IsSynchronized = true; + + if (await db.JobRuns.SingleOrDefaultAsync(x => x.Guid == run.Guid) == null) + { + db.JobRuns.Add(run); + await db.SaveChangesAsync(); + } + + request.SynchronizedJobRuns.Add(run.Guid); + } + catch (Exception ex) + { + LogManager.Log($"Synchronization Error - JobRun '{dto.ID}' cannot be stored locally due to the following reason:\n{ex.FlattenMessage()}", LogCategory.Error); + } + } + } + + //Insert MachineEvents. + if (response.MachineEvents.Count > 0) + { + LogManager.Log("Inserting/Replacing Events..."); + } + foreach (var dto in response.MachineEvents) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + try + { + var ev = dto.ToObservable(); + ev.ID = 0; + ev.UserGuid = null; + ev.IsSynchronized = true; + + if (await db.MachinesEvents.SingleOrDefaultAsync(x => x.Guid == ev.Guid) == null) + { + db.MachinesEvents.Add(ev); + await db.SaveChangesAsync(); + } + + request.SynchronizedMachineEvents.Add(ev.Guid); + } + catch (Exception ex) + { + LogManager.Log($"Synchronization Error - Event '{dto.ID}' cannot be stored locally due to the following reason:\n{ex.FlattenMessage()}", LogCategory.Error); + } + } + } + + return request; + } + + public async Task Synchronize() + { + _synchronizedOnce = true; + + if (!IsEnabled || IsSynchronizing) return; + + var settings = SettingsManager.Default.GetOrCreate(); + + var syncJobs = settings.SynchronizeJobs; + var syncDiagnostics = settings.SynchronizeDiagnostics; + + IsSynchronizing = true; + SynchronizationStarted?.Invoke(this, new EventArgs()); + + _logs.Clear(); + + _synchTimer.Stop(); + + LogManager.Log("Starting machine data synchronization..."); + LogManager.Log($"Synchronization interval: {Interval}."); + + CurrentStatus.StartDateTime = DateTime.Now; + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Starting synchronization..."); + + Stopwatch watch = new Stopwatch(); + watch.Start(); + + String notifyToken = null; + + int newChangedJobs = 0; + int newJobRuns = 0; + int newMachineEvents = 0; + + try + { + LogManager.Log("Authenticating with machine service..."); + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Authenticating with machine service..."); + + if (!this.client.IsAuthenticated) + { + await this.client.Login(new LoginRequest() + { + Mode = LoginMode.Machine, + MachineGuid = _machineProvider.Machine.Guid, + }); + } + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Preparing machine data for upload..."); + LogManager.Log("Preparing machine data for upload..."); + var request = await CreateUploadMachineDataRequest(syncJobs, syncDiagnostics); + request.ApplicationVersion = _appManager.Version.ToString(); + request.FirmwareVersion = _appManager.FirmwareVersion.ToString(); + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Uploading machine data..."); + LogManager.Log($"Uploading machine data:\nJobs: {request.Jobs.Count}\nJob Runs: {request.JobRuns.Count}\nEvents: {request.MachineEvents.Count}\nOffline Updates: {request.OfflineUpdates.Count}"); + var response = await this.client.UploadMachineData(request); + notifyToken = response.NotifyCompletedToken; + LogManager.Log($"Upload response received:\nFailed Jobs: {response.FailedJobs.Count}\nFailed Job Runs: {response.FailedJobRuns.Count}\nFailed Events: {response.FailedMachineEvents.Count}\nFailed Offline Updates: {response.FailedOfflineUpdates.Count}"); + LogManager.Log("Finalizing upload..."); + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Finalizing upload..."); + await FinalizeMachineDataUpload(request, response); + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Downloading machine data from service..."); + LogManager.Log("Downloading machine data from server..."); + var downloadResponse = await DownloadMachineData(syncJobs, syncDiagnostics); + + newChangedJobs = downloadResponse.Jobs.Count; + newJobRuns = downloadResponse.JobRuns.Count; + newMachineEvents = downloadResponse.MachineEvents.Count; + + LogManager.Log($"Download response received:\nJobs: {downloadResponse.Jobs.Count}\nJob Runs: {downloadResponse.JobRuns.Count}\nEvents: {downloadResponse.MachineEvents.Count}"); + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Updating local database..."); + LogManager.Log("Updating local database..."); + var notifyRequest = await InsertReplaceMachineData(downloadResponse); + LogManager.Log($"Finalizing download:\nSynchronized Jobs: {notifyRequest.SynchronizedJobs.Count}\nSynchronized Job Runs: {notifyRequest.SynchronizedJobRuns.Count}\nSynchronized Events: {notifyRequest.SynchronizedMachineEvents.Count}"); + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Finalizing download..."); + var notifyResponse = await this.client.NotifyMachineDataDownloadCompleted(notifyRequest); + + if (notifyToken != null) + { + try + { + await client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() + { + Token = notifyToken, + Status = TangoUpdateStatuses.SynchronizationCompleted, + }); + } + catch (Exception ex) + { + LogManager.Log(ex, "Synchronization completed successfully but an error occurred when trying to notify about the completion."); + } + } + + LogManager.Log("Machine data synchronization completed successfully."); + UpdateCurrentStatus(SynchronizationState.Completed, "Synchronization completed successfully.", null, watch.Elapsed); + } + catch (Exception ex) + { + if (notifyToken != null) + { + try + { + await client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() + { + Token = notifyToken, + Status = TangoUpdateStatuses.SynchronizationFailed, + FailedReason = ex.FlattenMessage(), + FailedLog = GetLogsStringAndClear(), + }); + } + catch (Exception ee) + { + LogManager.Log(ee, "Synchronization completed successfully but an error occurred when trying to notify about the completion."); + } + } + + UpdateCurrentStatus(SynchronizationState.Failed, "Synchronization failed.", ex.FlattenMessage(), watch.Elapsed); + throw LogManager.Log(ex, "Error occurred while synchronizing machine data."); + } + finally + { + watch.Stop(); + LogManager.Log($"Synchronization duration: {watch.Elapsed}."); + LastStatus = CurrentStatus; + CreateNewStatus(); + IsSynchronizing = false; + SynchronizationEnded?.Invoke(this, new SynchronizationEndedEventArgs() + { + NewChangedJobs = newChangedJobs, + NewJobRuns = newJobRuns, + NewMachineEvents = newMachineEvents, + }); + } + + _synchTimer.Start(); + } + + private void CreateNewStatus() + { + CurrentStatus = new SynchronizationStatus(); + CurrentStatus.State = SynchronizationState.Pending; + CurrentStatus.StartDateTime = DateTime.Now.Add(Interval); + StatusHistory.Insert(0, CurrentStatus); + } + + private async void ExecuteNewStatus(TimeSpan delay) + { + CurrentStatus = new SynchronizationStatus(); + CurrentStatus.State = SynchronizationState.Pending; + CurrentStatus.StartDateTime = DateTime.Now.Add(delay); + StatusHistory.Insert(0, CurrentStatus); + await Task.Delay(delay); + try + { + if (!_synchronizedOnce) + { + await Synchronize(); + } + } + catch { } + } + + private void UpdateCurrentStatus(SynchronizationState state, String message, String errorReason = null, TimeSpan? duration = null) + { + CurrentStatus.State = state; + CurrentStatus.Message = message; + CurrentStatus.ErrorReason = errorReason; + CurrentStatus.Duration = duration; + CurrentStatusChanged?.Invoke(this, new SynchronizationStatusChangedEventArgs() + { + Status = CurrentStatus, + }); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/IMachineDataSynchronizer.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/IMachineDataSynchronizer.cs new file mode 100644 index 000000000..bfd527a05 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/IMachineDataSynchronizer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.PPC.Common.Synchronization +{ + public interface IMachineDataSynchronizer + { + event EventHandler SynchronizationStarted; + event EventHandler SynchronizationEnded; + event EventHandler CurrentStatusChanged; + int MaxJobs { get; set; } + int MaxJobRuns { get; set; } + int MaxMachinesEvents { get; set; } + SynchronizationStatus CurrentStatus { get; } + SynchronizationStatus LastStatus { get; } + SynchronizedObservableCollection StatusHistory { get; } + TimeSpan Interval { get; set; } + bool IsEnabled { get; set; } + bool IsSynchronizing { get; } + Task Synchronize(); + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationEndedEventArgs.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationEndedEventArgs.cs new file mode 100644 index 000000000..4b8040e95 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationEndedEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Synchronization +{ + public class SynchronizationEndedEventArgs : EventArgs + { + public int NewChangedJobs { get; set; } + public int NewJobRuns { get; set; } + public int NewMachineEvents { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationState.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationState.cs new file mode 100644 index 000000000..5797f449f --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationState.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Synchronization +{ + public enum SynchronizationState + { + [Description("Pending...")] + Pending, + [Description("Synchronizing...")] + Synchronizing, + [Description("Failed")] + Failed, + [Description("Completed")] + Completed, + [Description("Disabled")] + Disabled + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatus.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatus.cs new file mode 100644 index 000000000..5b1d5d1d2 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatus.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.PPC.Common.Synchronization +{ + public class SynchronizationStatus : ExtendedObject + { + private SynchronizationState _state; + public SynchronizationState State + { + get { return _state; } + set { _state = value; RaisePropertyChangedAuto(); } + } + + private String _message; + public String Message + { + get { return _message; } + set { _message = value; RaisePropertyChangedAuto(); } + } + + private String _errorReason; + public String ErrorReason + { + get { return _errorReason; } + set { _errorReason = value; RaisePropertyChangedAuto(); } + } + + private TimeSpan? _duration; + public TimeSpan? Duration + { + get { return _duration; } + set { _duration = value; RaisePropertyChangedAuto(); } + } + + private DateTime _startDateTime; + public DateTime StartDateTime + { + get { return _startDateTime; } + set { _startDateTime = value; RaisePropertyChangedAuto(); } + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatusChangedEventArgs.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatusChangedEventArgs.cs new file mode 100644 index 000000000..1f0a9a27f --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatusChangedEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Synchronization +{ + public class SynchronizationStatusChangedEventArgs : EventArgs + { + public SynchronizationStatus Status { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/DefaultSystemInfoService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/DefaultSystemInfoService.cs new file mode 100644 index 000000000..0c818483c --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/DefaultSystemInfoService.cs @@ -0,0 +1,138 @@ +using Microsoft.WindowsAPICodePack.Net; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Core.DI; +using Tango.Integration.ExternalBridge; +using Tango.PPC.Common.Application; +using Tango.PPC.Common.Connectivity; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.Information; +using Tango.Settings; +using Tango.SystemInfo; + +namespace Tango.PPC.Common.SystemInfo +{ + [TangoCreateWhenRegistered] + public class DefaultSystemInfoService : ExtendedObject, ISystemInfoService, IExternalBridgeRequestHandler + { + public bool Enabled { get; set; } = true; + + private List _baseSystemInfo; + private IPPCApplicationManager _applicationManager; + private IConnectivityProvider _connectivityProvider; + + public DefaultSystemInfoService(IPPCExternalBridgeService externalBridge, IPPCApplicationManager applicationManager, IConnectivityProvider connectivityProvider) + { + _applicationManager = applicationManager; + _connectivityProvider = connectivityProvider; + externalBridge.RegisterRequestHandler(this); + } + + [ExternalBridgeRequestHandlerMethod(typeof(GetMachineInformationRequest), RequestHandlerLoggingMode.LogRequestName)] + public async Task OnGetMachineInformationRequest(GetMachineInformationRequest request, String token, ExternalBridgeReceiver receiver) + { + if (_baseSystemInfo == null) //Create hardware info just once. + { + _baseSystemInfo = new List(); + + if (!Debugger.IsAttached) + { + _baseSystemInfo = SystemObjectsCollection.Create(); + } + } + + //Now always update the latest custom objects.. + var system = _baseSystemInfo.ToList(); + + //Get the networks that are currently connected to + var connectedNetwork = NetworkListManager.GetNetworks(NetworkConnectivityLevels.Connected).FirstOrDefault(); + + var settings = SettingsManager.Default.GetOrCreate(); + + var applicationCollection = new SystemObjectsCollection(); + applicationCollection.Name = "Application"; + system.Insert(0, applicationCollection); + + //Application. + applicationCollection.Objects.Add(new SystemObject() + { + Name = "Tango PPC", + Properties = new List() + { + new SystemObjectProperty() { Name = "Version", Value = _applicationManager.Version.ToString(3) }, + new SystemObjectProperty() { Name = "Build Date", Value = _applicationManager.BuildDate }, + new SystemObjectProperty() { Name = "Previous Version", Value = settings.PreviousApplicationVersion }, + new SystemObjectProperty() { Name = "Firmware Version", Value = _applicationManager.FirmwareVersion.ToStringSafe() }, + new SystemObjectProperty() { Name = "Technician Mode", Value = _applicationManager.IsInTechnicianMode.ToStringYesNo() }, + new SystemObjectProperty() { Name = "After Update", Value = _applicationManager.IsAfterUpdate.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Startup Date", Value = _applicationManager.StartUpDate.ToString() }, + }, + }); + + + //Network. + if (connectedNetwork != null) + { + applicationCollection.Objects.Add(new SystemObject() + { + Name = "Network", + Properties = new List() + { + new SystemObjectProperty() { Name = "Network Name", Value = connectedNetwork.Name }, + new SystemObjectProperty() { Name = "Category", Value = connectedNetwork.Category.ToString() }, + new SystemObjectProperty() { Name = "Type", Value = connectedNetwork.Connectivity.ToString() }, + new SystemObjectProperty() { Name = "Domain", Value = connectedNetwork.DomainType.ToString() }, + new SystemObjectProperty() { Name = "Connected Time", Value = connectedNetwork.ConnectedTime.ToString() }, + new SystemObjectProperty() { Name = "Internet Connection", Value = connectedNetwork.IsConnectedToInternet.ToStringYesNo() }, + }, + }); + } + + //Settings. + applicationCollection.Objects.Add(new SystemObject() + { + Name = "Settings", + Properties = new List() + { + new SystemObjectProperty() { Name = "Application State", Value = settings.ApplicationState.ToString() }, + new SystemObjectProperty() { Name = "Auto Update Check", Value = settings.AutoCheckForUpdates.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Auto Update Interval", Value = settings.AutoUpdateCheckInterval.ToString() }, + new SystemObjectProperty() { Name = "Automatic Thread Loading", Value = settings.EnableAutomaticThreadLoading.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Firmware Logs Enabled", Value = settings.EnableEmbeddedDebugLogs.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Emergency Switch Enabled", Value = settings.EnableEmergencyNotifications.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Liquid Quantity Validation Enabled", Value = settings.EnableJobLiquidQuantityValidation.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Remote Assistance Enabled", Value = settings.EnableRemoteAssistance.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Remote Desktop Enabled", Value = settings.EnableRemoteDesktop.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Start in Technician Mode", Value = settings.EnableTechnicianModeByDefault.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Watchdog Enabled", Value = settings.EnableWatchDog.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Job Units Method", Value = settings.JobUnitsMethod.ToString() }, + new SystemObjectProperty() { Name = "PowerUp Screen Enabled", Value = settings.DisplayPowerUpScreen.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Firmware COM Port", Value = settings.EmbeddedComPort.ToStringOrEmpty() }, + new SystemObjectProperty() { Name = "Emergency COM Port", Value = settings.EmergencyComPort.ToStringOrEmpty() }, + new SystemObjectProperty() { Name = "Job Upload Method", Value = settings.JobUploadStrategy.ToString() }, + new SystemObjectProperty() { Name = "Diagnostics Synchronization", Value = settings.SynchronizeDiagnostics.ToStringYesNo() }, + new SystemObjectProperty() { Name = "Jobs Synchronization", Value = settings.SynchronizeJobs.ToStringYesNo() }, + new SystemObjectProperty() { Name = "TCP Write Mode", Value = settings.TcpTransportAdapterWriteMode.ToString() }, + }.OrderBy(x => x.Name).ToList(), + }); + + await receiver.SendGenericResponse(new GetMachineInformationResponse() + { + Package = new InformationPackage() + { + System = system, + } + }, token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/ISystemInfoService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/ISystemInfoService.cs new file mode 100644 index 000000000..47ea7bd4b --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/SystemInfo/ISystemInfoService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.SystemInfo +{ + /// + /// Represents a PPC system information service. + /// + /// + public interface ISystemInfoService : IPPCService + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj index d130a8002..9d39c96d9 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj @@ -72,6 +72,10 @@ + + + ..\..\packages\Microsoft.WindowsAPICodePack-Core.1.1.0.0\lib\Microsoft.WindowsAPICodePack.dll + ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll @@ -120,7 +124,15 @@ GlobalVersionInfo.cs - + + + + + + + + + @@ -131,23 +143,66 @@ + + + TwineCatalogControl.xaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -158,12 +213,16 @@ + + + + @@ -171,9 +230,9 @@ + - @@ -205,8 +264,13 @@ + + + + + @@ -217,6 +281,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -293,8 +361,29 @@ SettingsSingleFileGenerator Settings.Designer.cs + + + {ee088ff7-04d1-41fb-9d6a-cedeee7a7b9c} + Tango.DataStore.Editing + + + {88d9906b-8fc4-4fe0-b7eb-127a0a8fcee4} + Tango.DataStore.EF + + + {fa96bc0c-4055-475c-9dcc-70a5a9436b10} + Tango.DataStore.Lite + + + {29448f3c-9b3e-4da6-8555-46a8b9a6b3aa} + Tango.DataStore.Remote + + + {e0364dfa-0721-4637-9d32-9d22aac109d6} + Tango.DataStore + {c5df1816-34e5-4700-824c-29623a1baa22} Tango.AdvancedInstaller @@ -307,10 +396,18 @@ {b4fe6485-4161-4b36-bc08-67e0b53d01b7} Tango.ColorConversion + + {199e8359-cad3-433d-9eed-2027652b24a4} + Tango.Console + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} Tango.Core + + {58e8825f-0c96-449c-b320-1e82b0aa876b} + Tango.CSV + {63561e19-ff5a-414b-a5ef-e30711543e1d} Tango.Emulations @@ -319,6 +416,18 @@ {4399AF76-DB52-4CFB-8020-6F85BDB29FD5} Tango.Explorer + + {c6ebbbbe-2123-44dc-aef7-a0d47d736ac0} + Tango.FileSystem + + + {99081c0e-065c-4d68-bf60-f82330cca02d} + Tango.Git + + + {4a55c185-3f8d-41b0-8815-c15f6213a14a} + Tango.Insights + {4206ac58-3b57-4699-8835-90bf6db01a61} Tango.Integration @@ -331,6 +440,10 @@ {e4927038-348d-4295-aaf4-861c58cb3943} Tango.PMR + + {a78068d4-2061-4376-8ede-583d8d880dec} + Tango.RemoteDesktop + {d8f1ad85-526a-4f50-b6dc-d437af63d8d8} Tango.Settings @@ -343,6 +456,10 @@ {e1e66ed9-597d-45fa-8048-de90a6930484} Tango.SQLExaminer + + {997a961c-beda-4b56-aa0f-c39e532f7ffa} + Tango.SystemInfo + {fd86424c-6e84-491b-8df9-3d0f5c236a2a} Tango.Touch @@ -351,6 +468,10 @@ {74e700b0-1156-4126-be40-ee450d3c3026} Tango.Transport + + {09f81a12-0f77-4336-854d-9e0a74a17f9e} + Tango.WebRTC + {5001990f-977b-48ff-b217-0236a5022ad8} Tango.Web @@ -359,6 +480,10 @@ {6aa425c9-ea6a-4b01-aaed-5ff122e8b663} Tango.WiFi + + {208c8bd8-72c6-4e3c-acaa-351091a2acc7} + Tango.PPC.Shared + @@ -375,6 +500,16 @@ + + + + + + + + + + @@ -385,7 +520,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/ThreadLoading/IThreadLoadingService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/ThreadLoading/IThreadLoadingService.cs new file mode 100644 index 000000000..0394a17c2 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/ThreadLoading/IThreadLoadingService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.ThreadLoading +{ + public interface IThreadLoadingService + { + void StartThreadLoadingWizard(); + void StartThreadBreakWizard(); + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/AlternativeUnifiedWriteFilterManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/AlternativeUnifiedWriteFilterManager.cs index 5fb74b2fe..ea57ca0d8 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/AlternativeUnifiedWriteFilterManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/AlternativeUnifiedWriteFilterManager.cs @@ -4,19 +4,15 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Tango.Core; using Tango.Core.Components; namespace Tango.PPC.Common.UWF { - public class AlternativeUnifiedWriteFilterManager : IUnifiedWriteFilterManager + public class AlternativeUnifiedWriteFilterManager : ExtendedObject, IUnifiedWriteFilterManager { private const string UWF_PATH = "C:\\Windows\\Sysnative\\uwfmgr.exe"; - /// - /// Gets a value indicating whether UWF if currently enabled on the system. - /// - public bool IsEnabled { get; private set; } - /// /// Installs and configures the service (requires restart). /// @@ -47,14 +43,15 @@ namespace Tango.PPC.Common.UWF command.OutputEncoding = CmdCommand.OutEncoding.Unicode; await command.Run(); - command = new CmdCommand("wmic", $"computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False"); - await command.Run(); + //We don't use page file anymore since upgrade to 4GB RAM. But keep this just in case. + //command = new CmdCommand("wmic", $"computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False"); + //await command.Run(); - command = new CmdCommand("wmic", $"pagefileset create name=\"p:\\pagefile.sys\""); - await command.Run(); + //command = new CmdCommand("wmic", $"pagefileset create name=\"p:\\pagefile.sys\""); + //await command.Run(); - command = new CmdCommand("wmic", $"pagefileset where name=\"p:\\\\pagefile.sys\" set InitialSize=16,MaximumSize=3870"); - await command.Run(); + //command = new CmdCommand("wmic", $"pagefileset where name=\"p:\\\\pagefile.sys\" set InitialSize=16,MaximumSize=3870"); + //await command.Run(); } /// @@ -67,5 +64,32 @@ namespace Tango.PPC.Common.UWF command.OutputEncoding = CmdCommand.OutEncoding.Unicode; await command.Run(); } + + /// + /// Gets a value indicating whether UWF if currently enabled on the system. + /// + /// + public async Task IsEnabled() + { + String pattern = @"Next Session Settings[\n\r]+FILTER SETTINGS[\n\r]+\s+Filter state:\s+(OFF|ON)"; + + LogManager.Log($"Getting UWF status using pattern '{pattern}'..."); + CmdCommand command = new CmdCommand(UWF_PATH, $"get-config"); + command.ExitStrategy = CmdCommand.ExitStrategies.StandardOutput; + command.OutputEncoding = CmdCommand.OutEncoding.Unicode; + var result = await command.Run(); + + Match match = Regex.Match(result.StandardOutput, pattern); + if (match != null && match.Success && match.Groups.Count > 1) + { + var value = match.Groups[1].Value; + + LogManager.Log($"UWF pattern parsing result: '{value}'."); + + return value.ToStringOrEmpty().Trim() == "ON"; + } + + return false; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/DefaultUnifiedWriteFilterManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/DefaultUnifiedWriteFilterManager.cs index 65cb3f466..5684f6926 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/DefaultUnifiedWriteFilterManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/DefaultUnifiedWriteFilterManager.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; +using Tango.Core; using Tango.Core.Components; namespace Tango.PPC.Common.UWF @@ -11,7 +13,7 @@ namespace Tango.PPC.Common.UWF /// Represents the default unified writer filter manager. /// /// - public class DefaultUnifiedWriteFilterManager : IUnifiedWriteFilterManager + public class DefaultUnifiedWriteFilterManager : ExtendedObject, IUnifiedWriteFilterManager { private const int UWF_CAPACITY_MB = 5000; private const string UWF_PATH = "C:\\Windows\\Sysnative\\uwfmgr.exe"; @@ -23,11 +25,6 @@ namespace Tango.PPC.Common.UWF @"C:\Program Files (x86)\Twine", }; - /// - /// Gets a value indicating whether UWF if currently enabled on the system. - /// - public bool IsEnabled { get; } - /// /// Installs and configures the service (requires restart). /// @@ -95,5 +92,31 @@ namespace Tango.PPC.Common.UWF command.OutputEncoding = CmdCommand.OutEncoding.Unicode; await command.Run(); } + + /// + /// Gets a value indicating whether UWF if currently enabled on the system. + /// + /// + public async Task IsEnabled() + { + String pattern = @"Next Session Settings[\n\r]+FILTER SETTINGS[\n\r]+\s+Filter state:\s+(OFF|ON)"; + + LogManager.Log($"Getting UWF status using pattern '{pattern}'..."); + CmdCommand command = new CmdCommand(UWF_PATH, $"get-config"); + command.OutputEncoding = CmdCommand.OutEncoding.Unicode; + var result = await command.Run(); + + Match match = Regex.Match(result.StandardOutput, pattern); + if (match != null && match.Success && match.Groups.Count > 1) + { + var value = match.Groups[1].Value; + + LogManager.Log($"UWF pattern parsing result: '{value}'."); + + return value.ToStringOrEmpty().Trim() == "ON"; + } + + return false; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/IUnifiedWriteFilterManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/IUnifiedWriteFilterManager.cs index 05fa1876a..b6a661ab5 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/IUnifiedWriteFilterManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UWF/IUnifiedWriteFilterManager.cs @@ -14,7 +14,8 @@ namespace Tango.PPC.Common.UWF /// /// Gets a value indicating whether UWF if currently enabled on the system. /// - bool IsEnabled { get; } + /// + Task IsEnabled(); /// /// Installs and configures the service (requires restart). diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/DefaultPackageRunner.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/DefaultPackageRunner.cs new file mode 100644 index 000000000..68b31da4e --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/DefaultPackageRunner.cs @@ -0,0 +1,362 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Core.DI; +using Tango.Core.Helpers; +using Tango.PPC.Common.Application; +using Tango.PPC.Common.Connection; +using Tango.PPC.Common.Notifications; +using Tango.PPC.Shared.Updates; + +namespace Tango.PPC.Common.UpdatePackages +{ + public class DefaultPackageRunner : ExtendedObject, IPackageRunner + { + private JsonSerializerSettings _jsonSettings; + private String _configFile; + private PackagesFile _packagesFile; + + public event EventHandler PackageStateChanged; + public event EventHandler PackageProgress; + + public DefaultPackageRunner() + { + _jsonSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + Error = (sender, args) => + { + args.ErrorContext.Handled = true; + LogManager.Log(args.ErrorContext.Error.Message); + } + }; + + _jsonSettings.Converters.Add(new StringEnumConverter(false)); + + _configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Packages", "packages.json"); + } + + public Task GetPackagesFile() + { + return Task.Factory.StartNew(() => + { + if (_packagesFile != null) + { + return _packagesFile; + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(_configFile)); + + _packagesFile = new PackagesFile(); + + try + { + if (File.Exists(_configFile)) + { + LogManager.Log("Loading packages config from " + _configFile + "..."); + _packagesFile = JsonConvert.DeserializeObject(File.ReadAllText(_configFile), _jsonSettings); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error loading packages file."); + } + + return _packagesFile; + } + }); + } + + private void SavePackagesConfig() + { + Directory.CreateDirectory(Path.GetDirectoryName(_configFile)); + + String json = String.Empty; + + if (_packagesFile != null) + { + json = JsonConvert.SerializeObject(_packagesFile, _jsonSettings); + } + else + { + json = JsonConvert.SerializeObject(new PackagesFile(), _jsonSettings); + } + + File.WriteAllText(_configFile, json); + } + + public Task Run(PackageType type, Version deltaVersion, String packagesFolder) + { + return Task.Factory.StartNew(() => + { + PackageRunnerResult result = new PackageRunnerResult(); + + PackageContext context = new PackageContext(); + context.ApplicationManager = TangoIOC.Default.GetInstance(); + context.MachineProvider = TangoIOC.Default.GetInstance(); + context.NotificationProvider = TangoIOC.Default.GetInstance(); + context.InstalledVersion = context.ApplicationManager.Version; + context.DeltaVersion = deltaVersion; + + LogManager.Log($"Running {type}-update packages..."); + + //Get installed packages. + _packagesFile = GetPackagesFile().Result; + + LogManager.Log($"Installed packages file:\n{_packagesFile}"); + + if (Debugger.IsAttached) + { + LogManager.Log("Debugger attached detected. switching packages folder to main application path..."); + + //TO DEBUG PACKAGES -- + // + //On DEBUG build the packages assemblies are copied to the main application output folder using post-build events. + //Then, if a debugger is attached, we change the packages folder to the main output folder so they can be debugged easily. + packagesFolder = AssemblyHelper.GetCurrentAssemblyFolder(); + } + + LogManager.Log($"Scanning for packages on '{packagesFolder}'..."); + + //Get all packages in folder. + foreach (var packageFile in Directory.GetFiles(packagesFolder, "*.dll")) + { + LogManager.Log($"Loading assembly '{Path.GetFileName(packageFile)}'..."); + + Assembly asm; + + //Load assembly and investigate for types based on package type. + try + { + asm = Assembly.LoadFile(packageFile); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error loading assembly!"); + continue; + } + + try + { + foreach (var packageType in asm.GetTypes().Where( + x => typeof(IPPCPackage).IsAssignableFrom(x) && + x.GetCustomAttribute() != null && + x.GetCustomAttribute().Type == type)) + { + LogManager.Log($"Checking package '{packageType.FullName}'..."); + + try + { + //Getting installed package from file. + var installedPackage = _packagesFile.PackageInstallations.SingleOrDefault(x => x.PackageName == packageType.FullName); + + //Check if requires installation. + if (installedPackage == null || installedPackage.State != PackageInstallationState.Installed) + { + if (installedPackage == null) + { + LogManager.Log("Package was never installed."); + + installedPackage = new PackageInstallation(); + installedPackage.State = PackageInstallationState.NotInstalled; + installedPackage.PackageName = packageType.FullName; + installedPackage.Type = type; + _packagesFile.PackageInstallations.Add(installedPackage); + } + else + { + LogManager.Log($"Package installation state is '{installedPackage.State}' due to {installedPackage.FailedReason}"); + } + + LogManager.Log("Installing package..."); + + //Install package... + var att = packageType.GetCustomAttribute(); + + var packageInstance = Activator.CreateInstance(packageType) as IPPCPackage; + + if (packageInstance != null) + { + try + { + OnPackageRuns(att.Name, installedPackage.State, installedPackage.Type); + installedPackage.InstallationDate = DateTime.Now; + context.ProgressAction = (message, isIntermediate, progress, total) => + { + PackageProgress?.Invoke(this, new PackageProgressEventArgs() + { + PackageName = att.Name, + Message = message, + IsIntermediate = isIntermediate, + Progress = progress, + Total = total + }); + }; + + PackageProgress?.Invoke(this, new PackageProgressEventArgs() + { + PackageName = type == PackageType.Pre ? "Preparing" : "Finalizing", + Message = att.Name, + IsIntermediate = true, + Progress = 0, + Total = 100 + }); + + packageInstance.Run(context).GetAwaiter().GetResult(); + installedPackage.State = PackageInstallationState.Installed; + installedPackage.FailedReason = null; + OnPackageRuns(att.Name, installedPackage.State, installedPackage.Type); + LogManager.Log("Package installed successfully."); + + if (att.RequiresRestart) + { + result.RestartRequired = true; + } + + Thread.Sleep(2000); + } + catch (Exception ex) + { + LogManager.Log(ex, "Package installation failed."); + installedPackage.State = PackageInstallationState.Failed; + installedPackage.FailedReason = ex.FlattenMessage(); + OnPackageRuns(att.Name, installedPackage.State, installedPackage.Type); + continue; + } + } + } + else + { + LogManager.Log("Package is already installed."); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error in handling the package!"); + continue; + } + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error investigating assembly!"); + continue; + } + } + + //Save package file. + LogManager.Log("Running packages has completed. Saving packages config file."); + + try + { + SavePackagesConfig(); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error saving packages file!"); + } + + return result; + }); + } + + public Task IsPackageInstallationRequired(PackageType type, String packagesFolder) + { + return Task.Factory.StartNew(() => + { + LogManager.Log("Checking if any package installation is required..."); + + _packagesFile = GetPackagesFile().Result; + + //Get all packages in folder. + foreach (var packageFile in Directory.GetFiles(packagesFolder, "*.dll")) + { + LogManager.Log($"Loading assembly '{Path.GetFileName(packageFile)}'..."); + + Assembly asm; + + //Load assembly and investigate for types based on package type. + try + { + asm = Assembly.LoadFile(packageFile); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error loading assembly!"); + continue; + } + + try + { + foreach (var packageType in asm.GetTypes().Where( + x => typeof(IPPCPackage).IsAssignableFrom(x) && + x.GetCustomAttribute() != null && + x.GetCustomAttribute().Type == type)) + { + LogManager.Log($"Checking package '{packageType.FullName}'..."); + + try + { + //Getting installed package from file. + var installedPackage = _packagesFile.PackageInstallations.SingleOrDefault(x => x.PackageName == packageType.FullName); + + //Check if requires installation. + if (installedPackage == null || installedPackage.State != PackageInstallationState.Installed) + { + if (installedPackage == null) + { + LogManager.Log("Package was never installed."); + return true; + } + else + { + LogManager.Log($"Package installation state is '{installedPackage.State}' due to {installedPackage.FailedReason}"); + return true; + } + } + else + { + LogManager.Log("Package is already installed."); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error in handling the package!"); + continue; + } + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error investigating assembly!"); + continue; + } + } + + LogManager.Log("No packages to install."); + + return false; + }); + } + + protected virtual void OnPackageRuns(String packageName, PackageInstallationState state, PackageType type) + { + PackageStateChanged?.Invoke(this, new PackageStateChangedEventArgs() + { + PackageName = packageName, + State = state, + PackageType = type, + }); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPPCPackage.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPPCPackage.cs new file mode 100644 index 000000000..d9dc70135 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPPCPackage.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.UpdatePackages +{ + public interface IPPCPackage + { + Task Run(PackageContext context); + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPackageRunner.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPackageRunner.cs new file mode 100644 index 000000000..cdffd1ddc --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/IPackageRunner.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PPC.Shared.Updates; + +namespace Tango.PPC.Common.UpdatePackages +{ + public interface IPackageRunner + { + event EventHandler PackageStateChanged; + event EventHandler PackageProgress; + Task GetPackagesFile(); + Task Run(PackageType type, Version deltaVersion, String packagesFolder); + Task IsPackageInstallationRequired(PackageType type, String packagesFolder); + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PPCPackageAttribute.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PPCPackageAttribute.cs new file mode 100644 index 000000000..6067b0c18 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PPCPackageAttribute.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PPC.Shared.Updates; + +namespace Tango.PPC.Common.UpdatePackages +{ + [AttributeUsage(AttributeTargets.Class)] + public class PPCPackageAttribute : Attribute + { + public String Name { get; set; } + + public PackageType Type { get; set; } + + public bool RequiresRestart { get; set; } + + public PPCPackageAttribute(PackageType type, String name, bool requiresRestart) + { + Type = type; + Name = name; + RequiresRestart = requiresRestart; + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageContext.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageContext.cs new file mode 100644 index 000000000..7c8736112 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageContext.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PPC.Common.Application; +using Tango.PPC.Common.Connection; +using Tango.PPC.Common.Notifications; + +namespace Tango.PPC.Common.UpdatePackages +{ + public class PackageContext + { + public delegate void PackageProgressDelegate(String message, bool isIntermediate = true, double progress = 0, double maximum = 100); + + public IPPCApplicationManager ApplicationManager { get; set; } + public IMachineProvider MachineProvider { get; set; } + public INotificationProvider NotificationProvider { get; set; } + public Version InstalledVersion { get; set; } + public Version DeltaVersion { get; set; } + internal PackageProgressDelegate ProgressAction { get; set; } + + public void ReportProgress(String message, bool isIntermediate = true, double progress = 0, double maximum = 100) + { + ProgressAction?.Invoke(message, isIntermediate, progress, maximum); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageProgressEventArgs.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageProgressEventArgs.cs new file mode 100644 index 000000000..ebf0b23a6 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageProgressEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.UpdatePackages +{ + public class PackageProgressEventArgs : EventArgs + { + public String PackageName { get; set; } + public String Message { get; set; } + public bool IsIntermediate { get; set; } + public double Progress { get; set; } + public double Total { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageRunnerResult.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageRunnerResult.cs new file mode 100644 index 000000000..55f3a490a --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageRunnerResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.UpdatePackages +{ + public class PackageRunnerResult + { + public bool RestartRequired { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageStateChangedEventArgs.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageStateChangedEventArgs.cs new file mode 100644 index 000000000..cadba4c72 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackageStateChangedEventArgs.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PPC.Shared.Updates; + +namespace Tango.PPC.Common.UpdatePackages +{ + public class PackageStateChangedEventArgs : EventArgs + { + public PackageInstallationState State { get; set; } + public String PackageName { get; set; } + public PackageType PackageType { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackagesFile.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackagesFile.cs new file mode 100644 index 000000000..7af30d2ec --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/UpdatePackages/PackagesFile.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PPC.Shared.Updates; + +namespace Tango.PPC.Common.UpdatePackages +{ + public class PackagesFile + { + public List PackageInstallations { get; set; } + + public PackagesFile() + { + PackageInstallations = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateRequest.cs index b98848e4f..8e13ea7c4 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateRequest.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateRequest.cs @@ -9,7 +9,21 @@ namespace Tango.PPC.Common.Web { public class CheckForUpdateRequest : WebRequestMessage { - public String SerialNumber { get; set; } public String Version { get; set; } + public String FirmwareVersion { get; set; } + + public List Rmls { get; set; } + public List HardwareVersions { get; set; } + public List Catalogs { get; set; } + public DateTime MachineLastUpdated { get; set; } + public List UsedRmlsGuids { get; set; } + + public CheckForUpdateRequest() + { + Rmls = new List(); + HardwareVersions = new List(); + Catalogs = new List(); + UsedRmlsGuids = new List(); + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateResponse.cs index 370c0f5ea..2fb33ebdc 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateResponse.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/CheckForUpdateResponse.cs @@ -10,8 +10,18 @@ namespace Tango.PPC.Common.Web public class CheckForUpdateResponse : WebResponseMessage { public bool IsUpdateAvailable { get; set; } + public bool IsDatabaseUpdateAvailable { get; set; } public String Version { get; set; } + public String FirmwareVersion { get; set; } public bool SetupFirmware { get; set; } public bool SetupFPGA { get; set; } + public UpdateDBResponse UpdateDBResponse { get; set; } + public List UsedNotExistingRmlsGuids { get; set; } + + public CheckForUpdateResponse() + { + UpdateDBResponse = new UpdateDBResponse(); + UsedNotExistingRmlsGuids = new List(); + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataRequest.cs new file mode 100644 index 000000000..bbb0e883b --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataRequest.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class DownloadMachineDataRequest : WebRequestMessage + { + public bool RequestJobs { get; set; } + public bool RequestJobRuns { get; set; } + public bool RequestMachineEvents { get; set; } + public bool RequestDataStoreItems { get; set; } + + public int MaxJobs { get; set; } + public int MaxJobRuns { get; set; } + public int MaxMachinesEvents { get; set; } + public int MaxDataStoreItems { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataResponse.cs new file mode 100644 index 000000000..e90c7c2ac --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataResponse.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class DownloadMachineDataResponse : WebResponseMessage + { + public List Jobs { get; set; } + public List JobRuns { get; set; } + public List MachineEvents { get; set; } + public List DataStoreItems { get; set; } + + public DownloadMachineDataResponse() + { + Jobs = new List(); + JobRuns = new List(); + MachineEvents = new List(); + DataStoreItems = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateRequest.cs index a32d3d497..db4080dff 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateRequest.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateRequest.cs @@ -9,6 +9,6 @@ namespace Tango.PPC.Common.Web { public class DownloadUpdateRequest : WebRequestMessage { - public String SerialNumber { get; set; } + } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs index 3b09c1525..2fc7e4810 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs @@ -10,10 +10,16 @@ namespace Tango.PPC.Common.Web { public class DownloadUpdateResponse : WebResponseMessage { + public String NotifyCompletedToken { get; set; } + public String Version { get; set; } + public String FirmwareVersion { get; set; } + public String BlobAddress { get; set; } + public String CdnAddress { get; set; } + public DataSource DataSource { get; set; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LatestVersionResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LatestVersionResponse.cs index d2ed08f7d..eb5ef7f5a 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LatestVersionResponse.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LatestVersionResponse.cs @@ -11,5 +11,6 @@ namespace Tango.PPC.Common.Web public class LatestVersionResponse : WebResponseMessage { public String Version { get; set; } + public String FirmwareVersion { get; set; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LoginRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LoginRequest.cs index f8588f6b0..9ae0d65ae 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LoginRequest.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/LoginRequest.cs @@ -11,6 +11,7 @@ namespace Tango.PPC.Common.Web { public LoginMode Mode { get; set; } public String SerialNumber { get; set; } + public String MachineGuid { get; set; } public String Email { get; set; } public String Password { get; set; } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupRequest.cs index 821828a48..a9e68df36 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupRequest.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupRequest.cs @@ -9,7 +9,6 @@ namespace Tango.PPC.Common.Web { public class MachineSetupRequest : WebRequestMessage { - public String SerialNumber { get; set; } public String DeviceID { get; set; } public String DeviceName { get; set; } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs index b5a4c425d..f5d03c6ce 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs @@ -10,10 +10,16 @@ namespace Tango.PPC.Common.Web { public class MachineSetupResponse : WebResponseMessage { + public String NotifyCompletedToken { get; set; } + public String Version { get; set; } + public String FirmwareVersion { get; set; } + public String BlobAddress { get; set; } + public String CdnAddress { get; set; } + public DataSource DataSource { get; set; } public String OSKey { get; set; } @@ -25,5 +31,6 @@ namespace Tango.PPC.Common.Web public bool SetupFPGA { get; set; } public bool IsDemo { get; set; } public String DeviceComPort { get; set; } + public String Organization { get; set; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedRequest.cs new file mode 100644 index 000000000..dffe1f15b --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.Enumerations; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class MachineUpdateCompletedRequest : WebRequestMessage + { + public String Token { get; set; } + public TangoUpdateStatuses Status { get; set; } + public String FailedReason { get; set; } + public String FailedLog { get; set; } + public bool ReportsAboutDbCheckNoDifferences { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedResponse.cs new file mode 100644 index 000000000..72517d108 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineUpdateCompletedResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class MachineUpdateCompletedResponse : WebResponseMessage + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedRequest.cs new file mode 100644 index 000000000..fda7a4666 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedRequest.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class NotifyMachineDataDownloadCompletedRequest : WebRequestMessage + { + public List SynchronizedJobs { get; set; } + public List SynchronizedJobRuns { get; set; } + public List SynchronizedMachineEvents { get; set; } + public List SynchronizedDataStoreItems { get; set; } + + public NotifyMachineDataDownloadCompletedRequest() + { + SynchronizedJobs = new List(); + SynchronizedJobRuns = new List(); + SynchronizedMachineEvents = new List(); + SynchronizedDataStoreItems = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedResponse.cs new file mode 100644 index 000000000..6d5769885 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedResponse.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class NotifyMachineDataDownloadCompletedResponse : WebResponseMessage + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs index 52c9fdef3..318512fbb 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs @@ -30,5 +30,10 @@ namespace Tango.PPC.Common.Web public PPCWebClient(string address, string token) : base(address, token) { } + + public PPCWebClient(PPCWebClient other, TimeSpan requestTimeout) : base(other) + { + RequestTimeout = requestTimeout; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs index 2df343241..ff972acb2 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs @@ -40,6 +40,15 @@ namespace Tango.PPC.Common.Web } + /// + /// Initializes a new instance of the class. + /// + /// Other instance. + public PPCWebClientBase(PPCWebClientBase cloned) : base(cloned) + { + + } + /// /// Executes the MachineSetup action and returns Tango.PPC.Common.Web.MachineSetupResponse. /// @@ -58,6 +67,15 @@ namespace Tango.PPC.Common.Web return Post("MachineUpdate", request); } + /// + /// Executes the NotifyUpdateCompleted action and returns Tango.PPC.Common.Web.MachineUpdateCompletedResponse. + /// + /// + public Task NotifyUpdateCompleted(Tango.PPC.Common.Web.MachineUpdateCompletedRequest request) + { + return Post("NotifyUpdateCompleted", request); + } + /// /// Executes the CheckForUpdates action and returns Tango.PPC.Common.Web.CheckForUpdateResponse. /// @@ -76,6 +94,33 @@ namespace Tango.PPC.Common.Web return Post("UpdateDB", request); } + /// + /// Executes the UploadMachineData action and returns Tango.PPC.Common.Web.UploadMachineDataResponse. + /// + /// + public Task UploadMachineData(Tango.PPC.Common.Web.UploadMachineDataRequest request) + { + return Post("UploadMachineData", request); + } + + /// + /// Executes the DownloadMachineData action and returns Tango.PPC.Common.Web.DownloadMachineDataResponse. + /// + /// + public Task DownloadMachineData(Tango.PPC.Common.Web.DownloadMachineDataRequest request) + { + return Post("DownloadMachineData", request); + } + + /// + /// Executes the NotifyMachineDataDownloadCompleted action and returns Tango.PPC.Common.Web.NotifyMachineDataDownloadCompletedResponse. + /// + /// + public Task NotifyMachineDataDownloadCompleted(Tango.PPC.Common.Web.NotifyMachineDataDownloadCompletedRequest request) + { + return Post("NotifyMachineDataDownloadCompleted", request); + } + /// /// Executes the GetLatestVersion action and returns Tango.PPC.Common.Web.LatestVersionResponse. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/SynchronizationFailedEntity.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/SynchronizationFailedEntity.cs new file mode 100644 index 000000000..c50044cbe --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/SynchronizationFailedEntity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Web +{ + public class SynchronizationFailedEntity + { + public String Guid { get; set; } + public String Reason { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs index f3b4ccb34..4d8433a56 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs @@ -9,6 +9,7 @@ namespace Tango.PPC.Common.Web { public class UpdateDBRequest : WebRequestMessage { - public String SerialNumber { get; set; } + public String ApplicationVersion { get; set; } + public String FirmwareVersion { get; set; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBResponse.cs index be7c0d076..179cb7934 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBResponse.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBResponse.cs @@ -10,6 +10,7 @@ namespace Tango.PPC.Common.Web { public class UpdateDBResponse : WebResponseMessage { + public String NotifyCompletedToken { get; set; } public DataSource DataSource { get; set; } public bool PerformSchemaUpdate { get; set; } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdatedEntity.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdatedEntity.cs new file mode 100644 index 000000000..faee20678 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdatedEntity.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.BL; + +namespace Tango.PPC.Common.Web +{ + public class UpdatedEntity + { + public UpdatedEntity() + { + + } + + public UpdatedEntity(IObservableEntity entity) : this() + { + Guid = entity.Guid; + LastUpdated = entity.LastUpdated; + } + + public String Guid { get; set; } + public DateTime LastUpdated { get; set; } + + public override string ToString() + { + return $"{Guid} | {LastUpdated}"; + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataRequest.cs new file mode 100644 index 000000000..8eee667cd --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataRequest.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class UploadMachineDataRequest : WebRequestMessage + { + public List Jobs { get; set; } + public List JobRuns { get; set; } + public List MachineEvents { get; set; } + public List OfflineUpdates { get; set; } + public List DataStoreItems { get; set; } + public String ApplicationVersion { get; set; } + public String FirmwareVersion { get; set; } + + public UploadMachineDataRequest() + { + Jobs = new List(); + JobRuns = new List(); + MachineEvents = new List(); + OfflineUpdates = new List(); + DataStoreItems = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataResponse.cs new file mode 100644 index 000000000..ba0b4089b --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataResponse.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class UploadMachineDataResponse : WebResponseMessage + { + public List FailedJobs { get; set; } + public List FailedJobRuns { get; set; } + public List FailedMachineEvents { get; set; } + public List FailedOfflineUpdates { get; set; } + public List FailedDataStoreItems { get; set; } + + public String NotifyCompletedToken { get; set; } + + public UploadMachineDataResponse() + { + FailedJobs = new List(); + FailedJobRuns = new List(); + FailedMachineEvents = new List(); + FailedOfflineUpdates = new List(); + FailedDataStoreItems = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/packages.config b/Software/Visual_Studio/PPC/Tango.PPC.Common/packages.config index 50785f217..adc33d349 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/packages.config +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/packages.config @@ -6,6 +6,7 @@ + -- cgit v1.3.1