using FluentFTP; using Microsoft.WindowsAzure.Storage.Blob; using System; using System.Collections.Generic; using System.Data.SqlClient; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; 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; using Tango.PPC.Common.OS; using Tango.PPC.Common.RemoteAssistance; using Tango.PPC.Common.UWF; 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.Build; namespace Tango.PPC.Common.MachineSetup { /// /// Represents the PPC machine setup manager. /// /// /// public class MachineSetupManager : ExtendedObject, IMachineSetupManager { private IRemoteAssistanceProvider _remoteAssistance; private IUnifiedWriteFilterManager _uwf; private IOperationSystemManager _windows_manager; private IBuildProvider _buildProvider; private PPCWebClient _client; private List _logs; private bool _isUpdating; private DateTime _setupStartDate; #region Events /// /// Occurs when there is a text log message available. /// public event EventHandler ProgressLog; /// /// Occurs when the has changed. /// public event EventHandler Progress; #endregion #region Properties private MachineSetupProgress _status; public MachineSetupProgress Status { get { return _status; } private set { _status = value; RaisePropertyChangedAuto(); } } #endregion #region Constructor /// /// Initializes a new instance of the class. /// /// The remote assistance. public MachineSetupManager(PPCWebClient ppcWebClient, IRemoteAssistanceProvider remoteAssistance, IUnifiedWriteFilterManager unifiedWriterFilterManager, IOperationSystemManager operationSystemManager, IBuildProvider buildProvider) { _client = ppcWebClient; _remoteAssistance = remoteAssistance; _uwf = unifiedWriterFilterManager; _windows_manager = operationSystemManager; _buildProvider = buildProvider; _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 #region Private Methods private Task Login(String serialNumber) { return Task.Factory.StartNew(() => { return _client.Login(new LoginRequest() { Mode = LoginMode.Machine, SerialNumber = serialNumber, }).Result; }); } 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 = null, }); 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 /// /// Performs a machine setup using the specified serial number and machine service address. /// /// The serial number. /// The machine service address. /// 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(); IMachineOperator op = null; var settings = SettingsManager.Default.GetOrCreate(); //Connect to machine service and get matching packages for this machine. UpdateProgress("Validating serial number", "Connecting to machine service..."); LogManager.Log($"Connecting to machine service on {machineServiceAddress}..."); Login(serialNumber).Wait(); String deviceName = $"{(_buildProvider.BuildType == BuildType.TS1800 ? "PPC" : "TwineX4")}{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.DeviceID = await _windows_manager.GetDeviceId(); request.DeviceName = deviceName; try { LogManager.Log($"Sending setup request...\n{request.ToJsonString()}"); setup_response = await _client.MachineSetup(request); if (_buildProvider.MachineType.IsXMachine()) { setup_response.SetupActivation = false; setup_response.SetupUWF = false; setup_response.SetupRemoteAssistance = false; } } catch (Exception ex) { throw LogManager.Log(ex, $"An error occurred while trying to contact machine service: {ex.FlattenMessage()}"); } LogManager.Log($"Machine setup response received: {Environment.NewLine}{setup_response.ToJsonString()}"); if (!String.IsNullOrWhiteSpace(setup_response.DeviceComPort)) { settings.EmbeddedComPort = setup_response.DeviceComPort; settings.Save(); } if (setup_response.SetupFirmware) { //Connecting to machine... LogManager.Log("Initiating machine connection..."); UpdateProgress("Connecting to machine", "Connecting..."); op = await DefaultMachineProvider.CreateMinimalMachineOperator((msg) => { UpdateProgress("Connecting to machine", msg); }); } if (settings.ApplicationState == ApplicationStates.PreSetup) { if (setup_response.SetupActivation) { LogManager.Log("Activating windows license..."); 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, setup_response.Organization, settings.DeploymentSlot.ToString()); } if (setup_response.SetupUWF) { LogManager.Log("Activating unified write filter..."); UpdateProgress("Activating disk protection", "Activating..."); await _uwf.Setup(); } //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..."); using (AutoFileDownloader downloader = new AutoFileDownloader(setup_response.BlobAddress, setup_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..."); //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); //Synchronize database UpdateProgress("Updating Database", "Initializing..."); var localDataSource = SettingsManager.Default.GetOrCreate().DataSource; LogManager.Log($"Synchronizing database '{setup_response.DataSource.ToString()}' => '{localDataSource.ToString()}'..."); UpdateProgress("Updating Database", "Connecting to local database..."); LogManager.Log($"Connecting to local database at {localDataSource}..."); DbManager db = DbManager.FromAddress(localDataSource.Address); LogManager.Log($"Ensuring {localDataSource.Catalog} database exists on the local machine..."); if (!db.Exists(localDataSource.Catalog)) { UpdateProgress("Updating Database", "Creating new database..."); LogManager.Log("Database does not exist. Creating new database..."); db.Create(localDataSource.Catalog); } else { LogManager.Log("Database exists."); } db.Dispose(); LogManager.Log("Initializing database manager..."); db = DbManager.FromDataSource(localDataSource); UpdateProgress("Updating Database", "Clearing current database..."); LogManager.Log("Clearing database..."); db.ClearDb(); LogManager.Log("Disposing database manager."); db.Dispose(); LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}..."); UpdateProgress("Updating Database", "Initializing provisioning sequence..."); ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner( Path.Combine(_newPackageTempFolder, "Provision Scripts", "config.xml"), Path.Combine(_newPackageTempFolder, "Provision Scripts"), setup_response.DataSource, localDataSource, serialNumber); runner.Log += (x, msg) => { LogManager.Log(msg); ProgressLog?.Invoke(this, msg); }; runner.ScriptExecuting += (x, item) => { LogManager.Log($"Executing script {item.ToString()}..."); UpdateProgress("Updating Database", item.Name + "..."); }; LogManager.Log("Starting synchronization process..."); try { await runner.Run(); LogManager.Log("Synchronization completed successfully!"); UpdateProgress("Updating Database", "Database synchronization completed successfully."); } catch (Exception ex) { throw LogManager.Log(ex, "Setup manager error while trying to synchronize database."); } if (setup_response.SetupFirmware) { //Updating firmware 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); var handler = await op.UpgradeFirmware(stream, setup_response.IsDemo); handler.Failed += (_, ex) => { stream.Dispose(); OnFailed(ex, result, setup_response); }; handler.Completed += (_, __) => { UpdateProgress("Updating Firmware", "Firmware update completed successfully."); stream.Dispose(); OnCompleted(new MachineSetupResult() { UpdatePackagePath = _newPackageTempFolder, }, result, setup_response); }; handler.Canceled += (_, __) => { stream.Dispose(); OnFailed(new Exception("The operation has been canceled."), result, setup_response); }; handler.Progress += (_, e) => { UpdateProgress("Updating Firmware", e.Message, false, e.Current, e.Total); }; } else { OnCompleted(new MachineSetupResult() { UpdatePackagePath = _newPackageTempFolder, }, result, setup_response); } } catch (Exception ex) { OnFailed(ex, result, setup_response); } return await result.Task; } #endregion #region Protected Methods protected virtual void UpdateProgress(String name, String message = "", bool isIntermediate = true, double progress = 0, double total = 0) { InvokeUI(() => { Status = new MachineSetupProgress() { Name = name, Message = message, IsIntermediate = isIntermediate, Progress = progress, Total = total, }; Progress?.Invoke(this, Status); }); } #endregion } }