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
}
}