using FluentFTP;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tango.Core;
using Tango.Core.DB;
using Tango.Core.Helpers;
using Tango.Core.IO;
using Tango.Integration.Operation;
using Tango.PMR.Synchronization;
using Tango.PPC.Common.Application;
using Tango.PPC.Common.Connection;
using Tango.PPC.Common.Web;
using Tango.Settings;
using Tango.SharedUI.Helpers;
using Tango.SQLExaminer;
using Tango.Transport.Web;
namespace Tango.PPC.Common.MachineUpdate
{
public class MachineUpdateManager : ExtendedObject, IMachineUpdateManager
{
private IPPCApplicationManager _app_manager;
private IMachineProvider _machineProvider;
private PPCWebClient _client;
#region Events
///
/// Occurs when there is a text log message available.
///
public event EventHandler ProgressLog;
///
/// Occurs when the update has made some progress.
///
public event EventHandler Progress;
#endregion
#region Properties
private MachineUpdateProgress _status;
public MachineUpdateProgress Status
{
get { return _status; }
private set { _status = value; RaisePropertyChangedAuto(); }
}
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The application manager.
public MachineUpdateManager(PPCWebClient ppcWebClient, IPPCApplicationManager applicationManager, IMachineProvider machineProvider)
{
_client = ppcWebClient;
_machineProvider = machineProvider;
_app_manager = applicationManager;
}
#endregion
#region Private Methods
private Task Login(String serialNumber)
{
return Task.Factory.StartNew(() =>
{
return _client.Login(new LoginRequest()
{
Mode = LoginMode.Machine,
SerialNumber = serialNumber,
}).Result;
});
}
#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 Update(string serialNumber)
{
TaskCompletionSource result = new TaskCompletionSource();
try
{
var machineServiceAddress = SettingsManager.Default.GetOrCreate().GetMachineServiceAddress();
LogManager.Log($"Starting machine update 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 (_machineProvider.Machine.SetupFirmware)
{
LogManager.Log("Machine is configured to update firmware...");
if (op.State != Transport.TransportComponentState.Connected)
{
throw LogManager.Log(new InvalidOperationException("Could not perform an update while the machine is not connected."));
}
if (op.Status != MachineStatuses.ReadyToDye)
{
throw LogManager.Log(new InvalidOperationException($"Could not perform an update while the machine is in a {op.Status} status."));
}
}
//Connect to machine service and get matching packages for this machine.
UpdateProgress("Downloading software package", "Connecting to machine service...");
LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");
await Login(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) =>
{
UpdateProgress("Downloading software package", "Downloading...", false, current, fileSize);
}))
{
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);
}
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 '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");
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.");
}
LogManager.Log("Disposing database manager.");
db.Dispose();
LogManager.Log($"Initializing {nameof(ExaminerSequenceConfigurationRunner)}...");
UpdateProgress("Updating Database", "Initializing update sequence...");
ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner(
Path.Combine(_newPackageTempFolder, "Update Scripts", "config.xml"),
Path.Combine(_newPackageTempFolder, "Update Scripts"),
update_response.DataSource,
localDataSource,
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.");
}
//Updating firmware
if (_machineProvider.Machine.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);
var handler = await op.UpgradeFirmware(stream);
handler.Failed += (_, ex) =>
{
stream.Dispose();
result.SetException(ex);
};
handler.Completed += (_, __) =>
{
UpdateProgress("Updating Firmware", "Firmware update completed successfully.");
stream.Dispose();
result.SetResult(new MachineUpdateResult()
{
UpdatePackagePath = _newPackageTempFolder,
});
};
handler.Canceled += (_, __) =>
{
stream.Dispose();
result.SetException(new Exception("The operation has been canceled."));
};
handler.Progress += (_, e) =>
{
UpdateProgress("Updating Firmware", e.Message, false, e.Current, e.Total);
};
}
else
{
result.SetResult(new MachineUpdateResult()
{
UpdatePackagePath = _newPackageTempFolder,
});
}
}
catch (Exception ex)
{
LogManager.Log(ex, "An error occurred in machine update.");
result.SetException(ex);
}
return await result.Task;
}
///
/// Checks if any update are available for the specified machine serial number.
///
/// The serial number.
/// The machine service address.
///
public Task CheckForUpdate(string serialNumber)
{
return Task.Factory.StartNew(() =>
{
var machineServiceAddress = SettingsManager.Default.GetOrCreate().GetMachineServiceAddress();
LogManager.Log($"Connecting to machine service on {machineServiceAddress}...");
Login(serialNumber).Wait();
LogManager.Log($"Checking if updates available...");
CheckForUpdateRequest request = new CheckForUpdateRequest();
request.SerialNumber = serialNumber;
request.Version = _app_manager.Version.ToString();
CheckForUpdateResponse update_response = null;
update_response = _client.CheckForUpdates(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, String serialNumber)
{
return Task.Factory.StartNew(() =>
{
LogManager.Log("Starting database update...");
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."));
}
UpdateDBResponse update_response = dbCompareResult.UpdateDBResponse;
var localDataSource = SettingsManager.Default.GetOrCreate().DataSource;
LogManager.Log($"Updating database '{update_response.DataSource.ToString()}' => '{localDataSource.ToString()}'...");
UpdateProgress("Updating Database", "Initializing update sequence...");
ExaminerSequenceConfiguration config_sequence = ExaminerSequenceConfiguration.FromFile(config_file);
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);
if (item.RequiresSerialNumber)
{
builder.SetMachineSerialNumber(serialNumber);
}
builder.Synchronize();
var config = builder.Build();
ExaminerProcess process = new ExaminerProcess(config, ExaminerProcessType.Data);
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(String.Format("OverrideData script has terminated with exit code '{0}'.", result.ExitCode)));
}
LogManager.Log("Script executed successfully.");
}
catch (Exception ex)
{
throw LogManager.Log(ex, "Setup manager error while trying to update the database.");
}
}
UpdateProgress("Updating Database", "Database synchronization completed successfully.");
LogManager.Log("Update completed successfully.");
});
}
///
/// 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)
{
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");
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(serialNumber).Wait();
UpdateDBRequest request = new UpdateDBRequest();
request.SerialNumber = serialNumber;
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(serialNumber);
}
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)
{
throw LogManager.Log(ex, "Update manager error while trying to compare the database.");
}
}
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.
///
/// Name of the file.
///
public Task UpdateFromTUP(string fileName)
{
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,
};
});
}
///
/// Gets the update package file information.
///
/// The file path.
///
public Task GetUpdatePackageFileInfo(string filePath)
{
return Task.Factory.StartNew(() =>
{
UpdatePackageFile file = new UpdatePackageFile();
var tempFolder = TemporaryManager.CreateFolder();
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);
}
FileVersionInfo info = FileVersionInfo.GetVersionInfo(Path.Combine(tempFolder, "Tango.PPC.UI.exe"));
file.Version = Version.Parse(info.ProductVersion);
tempFolder.Delete();
return file;
});
}
#endregion
#region Protected Methods
protected virtual void UpdateProgress(String name, String message = "", bool isIntermediate = true, double progress = 0, double total = 0)
{
InvokeUINow(() =>
{
Status = new MachineUpdateProgress()
{
Name = name,
Message = message,
IsIntermediate = isIntermediate,
Progress = progress,
Total = total,
};
Progress?.Invoke(this, Status);
});
UIHelper.DoEvents();
}
#endregion
}
}