using FluentFTP; using Microsoft.WindowsAzure.Storage.Blob; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.Core; using Tango.Core.DB; using Tango.Core.Helpers; using Tango.Core.IO; using Tango.PMR.Synchronization; using Tango.PPC.Common.Application; using Tango.Settings; using Tango.SQLExaminer; using Tango.Transport.Web; namespace Tango.PPC.Common.MachineUpdate { public class MachineUpdateManager : ExtendedObject, IMachineUpdateManager { private IPPCApplicationManager _app_manager; #region Events /// /// Occurs when there is a text log message available. /// public event EventHandler ProgressLog; /// /// Occurs when the has changed. /// public event EventHandler ProgressStep; #endregion #region Properties private MachineUpdateSteps _currentStep; /// /// Gets the current setup step. /// public MachineUpdateSteps CurrentStep { get { return _currentStep; } set { if (_currentStep != value) { _currentStep = value; RaisePropertyChangedAuto(); ProgressStep?.Invoke(this, _currentStep); LogManager.Log("Machine Setup Manager Step: " + value.ToString()); } } } private double _downloadProgress; /// /// Gets the downloading packages step progress. /// public double DownloadingPackagesProgress { get { return _downloadProgress; } private set { _downloadProgress = value; RaisePropertyChangedAuto(); } } private String _updatingPackagesStatus; /// /// Gets the downloading packages step status. /// public String DownloadingPackagesStatus { get { return _updatingPackagesStatus; } set { _updatingPackagesStatus = value; RaisePropertyChangedAuto(); } } #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The application manager. public MachineUpdateManager(IPPCApplicationManager applicationManager) { _app_manager = applicationManager; } #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 Task Update(string serialNumber, string machineServiceAddress) { return Task.Factory.StartNew(() => { LogManager.Log($"Starting machine update for serial number {serialNumber}..."); //Connect to machine service and get matching packages for this machine. CurrentStep = MachineUpdateSteps.DownloadingPackage; DownloadingPackagesProgress = 0; DownloadingPackagesStatus = "Connecting to machine service..."; LogManager.Log($"Connecting to machine service on {machineServiceAddress}..."); DownloadUpdateRequest request = new DownloadUpdateRequest(); request.SerialNumber = serialNumber; DownloadUpdateResponse update_response = null; using (var http = new ProtoWebClient()) { update_response = http.Post(machineServiceAddress + "/api/Synchronization/MachineUpdate", request).Result; } 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}."); DownloadingPackagesStatus = "Downloading software package..."; LogManager.Log("Downloading software package..."); long fileSize = 0; DownloadingPackagesProgress = 0; using (FileStreamWrapper fs = new FileStreamWrapper(tempFile.Path, FileMode.Create, (current) => { InvokeUINow(() => { DownloadingPackagesProgress = ((double)current / (double)fileSize) * 100d; }); })) { 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("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 CurrentStep = MachineUpdateSteps.SynchronizingSchema; String db_name = "Tango"; String localAddress = SettingsManager.Default.GetOrCreate().DataSource.Address; String remote_address = update_response.DbAddress; LogManager.Log($"Synchronizing database '{remote_address}\\{db_name}' => '{localAddress}\\{db_name}'..."); LogManager.Log("Initializing database manager..."); DbManager db = DbManager.FromAddressAndName(localAddress, db_name); LogManager.Log("Checking Tango database exists on the local machine..."); if (!db.Exists(db_name)) { throw new InvalidProgramException("Database tango does not exists."); } LogManager.Log("Disposing database manager."); db.Dispose(); LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}..."); ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner( Path.Combine(_newPackageTempFolder, "Update Scripts", "config.xml"), Path.Combine(_newPackageTempFolder, "Update Scripts"), new ExaminerSequenceDataSource() { Address = remote_address, DataBaseName = db_name, IntegratedSecurity = false, UserName = update_response.DbUserName, Password = update_response.DbPassword, }, new ExaminerSequenceDataSource() { Address = localAddress, DataBaseName = db_name, IntegratedSecurity = true, }, serialNumber); runner.Log += (x, msg) => { LogManager.Log(msg); ProgressLog?.Invoke(this, msg); }; runner.ScriptExecuting += (x, item) => { LogManager.Log($"Executing script {item.ToString()}..."); if (item.Type == ExaminerSequenceItemType.Data && item.RequiresSerialNumber) { CurrentStep = MachineUpdateSteps.SynchronizingMachineConfiguration; } else if (item.Type == ExaminerSequenceItemType.Data) { CurrentStep = MachineUpdateSteps.SynchronizingData; } }; LogManager.Log("Starting synchronization process..."); try { runner.Run().Wait(); LogManager.Log("Synchronization completed successfully!"); } catch (Exception ex) { throw LogManager.Log(ex, "Setup manager error while trying to synchronize database."); } return new MachineUpdateResult() { UpdatePackagePath = _newPackageTempFolder, }; }); } /// /// Checks if any update are available for the specified machine serial number. /// /// The serial number. /// The machine service address. /// public Task CheckForUpdate(string serialNumber, string machineServiceAddress) { return Task.Factory.StartNew(() => { LogManager.Log($"Connecting to machine service on {machineServiceAddress}..."); LogManager.Log($"Checking if updates available..."); CheckForUpdateRequest request = new CheckForUpdateRequest(); request.SerialNumber = serialNumber; request.Version = _app_manager.Version.ToString(); CheckForUpdateResponse update_response = null; using (var http = new ProtoWebClient()) { update_response = http.Post(machineServiceAddress + "/api/Synchronization/CheckForUpdate", request).Result; } LogManager.Log($"Check for update response received: {Environment.NewLine}{update_response.ToJsonString()}"); return update_response; }); } /// /// Updates all the "overwrite-able" database tables. /// /// The serial number. /// The machine service address. /// public Task UpdateDB(DbCompareResult dbCompareResult) { return Task.Factory.StartNew(() => { LogManager.Log("Starting database update..."); LogManager.Log("Looking for OverrideData script on application path..."); String config_file = Path.Combine(PathHelper.GetStartupPath(), "Update Scripts", "OverrideData.xml"); if (!File.Exists(config_file)) { config_file = Path.Combine(PathHelper.GetStartupPath(), "Provision Scripts", "OverrideData.xml"); } if (!File.Exists(config_file)) { throw LogManager.Log(new FileNotFoundException("Could not locate OverrideData.xml file on application folder.")); } UpdateDBResponse update_response = dbCompareResult.UpdateDBResponse; String db_name = "Tango"; String localAddress = SettingsManager.Default.GetOrCreate().DataSource.Address; String remote_address = update_response.DbAddress; LogManager.Log($"Overriding database static tables '{remote_address}\\{db_name}' => '{localAddress}\\{db_name}'..."); ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(config_file); builder.SetSourceServer(remote_address, db_name, false, update_response.DbUserName, update_response.DbPassword); builder.SetTargetServer(localAddress, db_name, true); builder.Synchronize(); var config = builder.Build(); ExaminerProcess process = new ExaminerProcess(config, ExaminerProcessType.Data); process.Progress += (x, msg) => { LogManager.Log(msg); }; LogManager.Log("Starting synchronization process..."); try { var result = process.Execute().Result; if (result.ExitCode != ExaminerProcessExitCode.Success) { throw LogManager.Log(new InvalidDataException(String.Format("OverrideData script has terminated with exit code '{0}'.", result.ExitCode))); } LogManager.Log("Synchronization completed successfully!"); } catch (Exception ex) { throw LogManager.Log(ex, "Setup manager error while trying to update the database."); } }); } /// /// Checks whether it is necessary to updates all the "overwrite-able" database tables. /// /// The serial number. /// The machine service address. /// public Task UpdateDBCheck(string serialNumber, string machineServiceAddress) { return Task.Factory.StartNew(() => { LogManager.Log($"Checking if database update is required for serial number {serialNumber}..."); LogManager.Log("Looking for OverrideData script on application path..."); String config_file = Path.Combine(PathHelper.GetStartupPath(), "Update Scripts", "OverrideData.xml"); if (!File.Exists(config_file)) { config_file = Path.Combine(PathHelper.GetStartupPath(), "Provision Scripts", "OverrideData.xml"); } if (!File.Exists(config_file)) { throw LogManager.Log(new FileNotFoundException("Could not locate OverrideData.xml file on application folder.")); } LogManager.Log($"Connecting to machine service on {machineServiceAddress}..."); UpdateDBRequest request = new UpdateDBRequest(); request.SerialNumber = serialNumber; UpdateDBResponse update_response = null; using (var http = new ProtoWebClient()) { update_response = http.Post(machineServiceAddress + "/api/Synchronization/UpdateDB", request).Result; } LogManager.Log($"Update DB response received: {Environment.NewLine}{update_response.ToJsonString()}"); String db_name = "Tango"; String localAddress = SettingsManager.Default.GetOrCreate().DataSource.Address; String remote_address = update_response.DbAddress; LogManager.Log($"Comparing database static tables '{remote_address}\\{db_name}' => '{localAddress}\\{db_name}'..."); var report_file = TemporaryManager.CreateFile(".xml"); ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(config_file); builder.SetSourceServer(remote_address, db_name, false, update_response.DbUserName, update_response.DbPassword); builder.SetTargetServer(localAddress, db_name, true); builder.SetReportFile(report_file); 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("OverrideData 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()); return new DbCompareResult() { RequiresUpdate = report.HasDifferences, UpdateDBResponse = update_response, }; } catch (Exception ex) { throw LogManager.Log(ex, "Update manager error while trying to compare the database."); } }); } #endregion } }