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.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.Settings;
using Tango.SharedUI.Helpers;
using Tango.SQLExaminer;
using Tango.Transport.Web;
namespace Tango.PPC.Common.MachineSetup
{
///
/// Represents the PPC machine setup manager.
///
///
///
public class MachineSetupManager : ExtendedObject, IMachineSetupManager
{
#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 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, string machineServiceAddress)
{
TaskCompletionSource result = new TaskCompletionSource();
try
{
LogManager.Log($"Starting machine setup for serial number {serialNumber}...");
IMachineOperator op = null;
var demoMode = SettingsManager.Default.GetOrCreate().DemoMode;
if (!demoMode)
{
//Connecting to machine...
LogManager.Log("Initiating machine connection...");
UpdateProgress("Connecting to machine", "Connecting...");
op = await DefaultMachineProvider.CreateMinimalMachineOperator((msg) =>
{
UpdateProgress("Connecting to machine", msg);
});
}
else
{
LogManager.Log("Application in demo mode. Skipping machine connection...");
}
//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}...");
MachineSetupRequest request = new MachineSetupRequest();
request.SerialNumber = serialNumber;
MachineSetupResponse setup_response = null;
try
{
using (var http = new ProtoWebClient())
{
setup_response = await http.Post(machineServiceAddress + "/api/Synchronization/MachineSetup", request);
}
}
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()}");
//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);
await Task.Factory.StartNew(() =>
{
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 {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);
}
});
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...");
String db_name = "Tango";
String localAddress = SettingsManager.Default.GetOrCreate().DataSource.Address;
String remote_address = setup_response.DbAddress;
LogManager.Log($"Synchronizing database '{remote_address}\\{db_name}' => '{localAddress}\\{db_name}'...");
UpdateProgress("Updating Database", "Connecting to local database...");
LogManager.Log($"Connecting to local database at {localAddress}...");
DbManager db = DbManager.FromAddress(localAddress);
LogManager.Log($"Ensuring {db_name} database exists on the local machine...");
if (!db.Exists(db_name))
{
UpdateProgress("Updating Database", "Creating new database...");
LogManager.Log("Database does not exist. Creating new database...");
db.Create(db_name);
}
else
{
LogManager.Log("Database exists.");
}
db.Dispose();
LogManager.Log("Initializing database manager...");
db = DbManager.FromAddressAndName(localAddress, db_name);
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"),
new ExaminerSequenceDataSource()
{
Address = remote_address,
DataBaseName = db_name,
IntegratedSecurity = false,
UserName = setup_response.DbUserName,
Password = setup_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()}...");
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 (!demoMode)
{
//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);
handler.Failed += (_, ex) =>
{
stream.Dispose();
result.SetException(ex);
};
handler.Completed += (_, __) =>
{
UpdateProgress("Updating Firmware", "Firmware update completed successfully.");
stream.Dispose();
result.SetResult(new MachineSetupResult()
{
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
{
LogManager.Log("Application in demo mode. Skipping firmware upgrade...");
result.SetResult(new MachineSetupResult()
{
UpdatePackagePath = _newPackageTempFolder,
});
}
}
catch (Exception ex)
{
LogManager.Log(ex, "An error occurred in machine setup.");
result.SetException(ex);
}
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)
{
InvokeUINow(() =>
{
Status = new MachineSetupProgress()
{
Name = name,
Message = message,
IsIntermediate = isIntermediate,
Progress = progress,
Total = total,
};
Progress?.Invoke(this, Status);
});
UIHelper.DoEvents();
}
#endregion
}
}