using Ionic.Zip;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tango.AdvancedInstaller;
using Tango.BL.Enumerations;
using Tango.Core;
using Tango.Core.Helpers;
using Tango.Git;
using Tango.PMR.FirmwareUpgrade;
using Tango.PPC.Common.Web;
using Tango.SQLExaminer;
using Tango.Transport.Web;
using Tango.Web;
namespace Tango.PPC.Common.Publish
{
public class PPCPublisher : ExtendedObject
{
private PPCWebClient _client;
///
/// Occurs on publish progress.
///
public event EventHandler PublishProgress;
public MachineTypes MachineType { get; set; }
private PublishOptions _options;
///
/// Gets or sets the publish options.
///
public PublishOptions Options
{
get { return _options; }
set { _options = value; RaisePropertyChangedAuto(); }
}
///
/// Initializes a new instance of the class.
///
public PPCPublisher()
{
_client = new PPCWebClient();
Options = new PublishOptions();
}
///
/// Initializes a new instance of the class.
///
/// The publish options.
public PPCPublisher(PublishOptions publishOptions) : this()
{
Options = publishOptions;
}
///
/// Gets the latest version.
///
///
public async Task GetRemoteVersion(String machineVersionGuid)
{
_client.Environment = Options.Environment;
var response = await _client.GetLatestVersion(new LatestVersionRequest() { MachineVersionGuid = machineVersionGuid, Tag = Options.Tag });
return response;
}
///
/// Gets the latest version.
///
///
public async Task GetRemoteVersion(DeploymentSlot environment)
{
_client.Environment = environment;
var response = await _client.GetLatestVersion(new LatestVersionRequest());
return response.Version;
}
///
/// Gets the latest version.
///
///
public String GetLocalVersion()
{
if (File.Exists(GetPPCExecutablePath()))
{
return FileVersionInfo.GetVersionInfo(GetPPCExecutablePath()).ProductVersion;
}
else
{
return "0.0.0.0";
}
}
///
/// Gets the PPC executable path.
///
///
public String GetPPCExecutablePath()
{
String appPath = Path.Combine(Options.GetApplicationPath(), "Tango.PPC.UI.exe");
return appPath;
}
///
/// Login to machine service and returns an access token.
///
///
private Task Login()
{
return Task.Factory.StartNew(() =>
{
return _client.Login(new LoginRequest()
{
Mode = LoginMode.User,
Email = Options.Email,
Password = Options.Password,
}).Result;
});
}
///
/// Publish a PPC version using the specified .
///
///
public Task Publish()
{
_client.Environment = Options.Environment;
String appPath = GetPPCExecutablePath();
String folder = Options.GetApplicationPath();
if (!File.Exists(appPath))
{
throw new FileNotFoundException($"Could not locate the PPC executable at {appPath}.");
}
if (!String.IsNullOrWhiteSpace(Options.InstallerProject))
{
if (!File.Exists(Options.InstallerProject))
{
throw new FileNotFoundException($"Installer project not found at '{Options.InstallerProject}'.");
}
if (!Directory.Exists(Options.InstallerOutputFolder))
{
Directory.CreateDirectory(Options.InstallerOutputFolder);
}
}
return Task.Factory.StartNew(() =>
{
String tempFile = TemporaryManager.CreateFile(".zip");
try
{
OnPublishProgress(0, 100, $"Logging in to machine service at {Options.Environment.ToAddress()}...");
Login().Wait();
OnPublishProgress(0, 100, $"Fetching remote version from {Options.Environment.ToAddress()}...");
var r = GetRemoteVersion(Options.MachineVersionGuid).Result;
String remote_version = r.Version;
String remote_firmware_version = r.FirmwareVersion;
String local_version = GetLocalVersion();
String local_firmware_version = GetLocalFirmwareVersion(Options.TfpPath);
OnPublishProgress(0, 100, $"Remote version: {remote_version}");
OnPublishProgress(0, 100, $"Local version: {local_version}");
if (Version.Parse(local_version) <= Version.Parse(remote_version))
{
throw new InvalidOperationException($"The local version '{local_version}' is not greater than the remote version '{remote_version}'.");
}
if (Version.Parse(local_firmware_version) < Version.Parse(remote_firmware_version))
{
throw new InvalidOperationException($"The local firmware version '{local_firmware_version}' is not greater than the remote version '{remote_firmware_version}'.");
}
OnPublishProgress(0, 100, $"Requesting version upload...");
var response = _client.UploadVersion(new UploadVersionRequest()
{
Comments = Options.Comments,
Version = local_version,
MachineVersionGuid = Options.MachineVersionGuid,
WithInstaller = !String.IsNullOrWhiteSpace(Options.InstallerProject),
FirmwareVersion = GetVersionInfoFromTFP(Options.TfpPath).FileDescriptors.SingleOrDefault(x => x.Destination == VersionFileDestination.Mcu).Version,
Tag = Options.Tag
}).Result;
CreateTupPackage(tempFile).Wait();
if (!String.IsNullOrWhiteSpace(Options.InstallerProject))
{
String output_folder = Options.InstallerOutputFolder;
OnPublishProgress(0, 100, $"Building installer project...");
InstallerBuilder installerBuilder = new InstallerBuilder(Options.InstallerProject);
String installer_name = $"{MachineType.ToDescription()} Installer_v{Version.Parse(local_version).ToString(3) + (Options.Tag.IsNotNullOrEmpty() ? $" [{Options.Tag}]" : String.Empty)}.exe";
String output_file = Path.Combine(output_folder, installer_name);
installerBuilder.Build(local_version, output_file).Wait();
Thread.Sleep(5000);
OnPublishProgress(0, 100, $"Uploading installer '{installer_name}'...");
using (StorageBlobUploader uploader = new StorageBlobUploader(response.InstallerBlobAddress, output_file))
{
uploader.Progress += (x, e) =>
{
OnPublishProgress(e.Current, e.Total, $"Uploading installer {(((double)e.Current / (double)e.Total) * 100d).ToString("0.0")}%...", true);
};
uploader.Upload().Wait();
}
}
OnPublishProgress(0, 100, $"Starting version upload...");
using (StorageBlobUploader uploader = new StorageBlobUploader(response.BlobAddress, tempFile))
{
uploader.Progress += (x, e) =>
{
InvokeUINow(() =>
{
OnPublishProgress(e.Current, e.Total, $"Uploading version {(((double)e.Current / (double)e.Total) * 100d).ToString("0.0")}%...", true);
});
};
uploader.Upload().Wait();
}
OnPublishProgress(100, 100, $"Finalizing version upload...");
_client.NotifyVersionUploadCompleted(new UploadCompletedRequest()
{
Token = response.Token,
}).Wait();
remote_version = GetRemoteVersion(Options.MachineVersionGuid).Result.Version;
local_version = GetLocalVersion();
OnPublishProgress(0, 0, $"Remote version: {remote_version}");
OnPublishProgress(0, 0, $"Local version: {local_version}");
if (remote_version != local_version)
{
throw new InvalidOperationException("The remote version does not seems to have been updated.");
}
if (Options.CreateTag)
{
String repoPath = Path.GetFullPath("../../../../../");
String tagVersion = System.Version.Parse(GetLocalVersion()).ToString(3);
String tagName = $"{MachineType.ToShortName()}_v{tagVersion}";
if (Options.Tag.IsNotNullOrEmpty())
{
tagName += $"_[{Options.Tag.Replace(" ", "")}]";
}
using (GitRepositoryManager git = new GitRepositoryManager(repoPath, Options.Email, Options.PersonalAccessToken))
{
OnPublishProgress(0, 100, "Checking repository changes...");
int changes = git.GetChanges().Count;
if (changes > 0)
{
if (Options.AutoCommitAndPush)
{
OnPublishProgress(0, 100, "Committing repository changes...");
git.Commit(tagName);
}
else
{
throw new InvalidOperationException($"There are {changes} uncommitted changes on the repository. Please commit and push all changes before creating the Tag");
}
}
OnPublishProgress(0, 100, "Checking outgoing commits...");
int commits = git.GetOutgoingCommits().Count;
//if (commits > 0)
//{
if (Options.AutoCommitAndPush)
{
OnPublishProgress(0, 100, "Pushing repository changes...");
git.Sync();
}
else
{
throw new InvalidOperationException($"There are {commits} outgoing commits on the repository. Please push all commits before creating the Tag");
}
//}
git.Progress += (x, e) =>
{
OnPublishProgress(e.Progress.Value, e.Progress.Maximum, $"Pushing Tag '{tagName}'...");
};
OnPublishProgress(0, 100, $"Creating Tag '{tagName}'...");
git.CreatePushTag(tagName, Options.Comments, "Roy Ben Shabat");
}
}
OnPublishProgress(0, 0, "Version published successfully.");
}
catch (Exception ex)
{
OnPublishProgress(0, 100, $"Failed: {ex.Message}");
throw ex;
}
finally
{
PathHelper.TryDeleteFile(tempFile);
}
});
}
///
/// Creates a Tango Update Package (.tup) file.
///
/// The file path.
///
public Task CreateTupPackage(String filePath)
{
return Task.Factory.StartNew(() =>
{
try
{
OnPublishProgress(0, 100, "Generating .tup file...");
String appPath = GetPPCExecutablePath();
String folder = Options.GetApplicationPath();
if (!File.Exists(appPath))
{
throw new FileNotFoundException($"Could not locate the PPC executable at {appPath}.");
}
if (!File.Exists(Options.TfpPath))
{
throw new FileNotFoundException($"Could not locate TFP file at {Options.TfpPath}.");
}
var tempFile = filePath;
using (ZipFile zip = new ZipFile())
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
if (Options.BuildConfig != "Debug")
{
zip.AddFile(Options.TfpPath, "/").FileName = "firmware_package.tfp";
}
PublishInfo versionInfo = new PublishInfo();
versionInfo.ApplicationVersion = GetLocalVersion();
versionInfo.Comments = Options.Comments;
versionInfo.Firmware = GetVersionInfoFromTFP(Options.TfpPath);
versionInfo.Tag = Options.Tag;
//Validate the package.
versionInfo.Firmware.Validate();
var versionInfoFile = TemporaryManager.CreateImaginaryFile();
File.WriteAllText(versionInfoFile, versionInfo.ToJson());
zip.AddFile(versionInfoFile, "/").FileName = "version.json";
String provision_dir = "Provision Scripts";
zip.AddDirectoryByName(provision_dir);
ExaminerSequenceConfiguration provision_config = new ExaminerSequenceConfiguration();
OnPublishProgress(0, 100, "Processing provisioning scripts...");
foreach (var item in Options.Synchronization.ProvisionSequenceItems)
{
OnPublishProgress(0, 100, $"Processing provisioning script '{item.Name}'...");
provision_config.Items.Add(new ExaminerSequenceItem()
{
Direction = item.Direction,
FileName = item.FileName,
Index = item.Index,
Name = item.Name,
Type = item.Type,
RequiresSerialNumber = item.RequiresSerialNumber
});
zip.AddFile(item.FilePath, provision_dir);
}
String provision_config_file = TemporaryManager.CreateFile(".zip");
provision_config.ToFile(provision_config_file);
var cf = zip.AddFile(provision_config_file, provision_dir);
cf.FileName = provision_dir + "\\config.xml";
String update_dir = "Update Scripts";
zip.AddDirectoryByName(update_dir);
ExaminerSequenceConfiguration update_config = new ExaminerSequenceConfiguration();
OnPublishProgress(0, 100, "Processing update scripts...");
foreach (var item in Options.Synchronization.UpdateSequenceItems)
{
OnPublishProgress(0, 100, $"Processing update script '{item.Name}'...");
update_config.Items.Add(new ExaminerSequenceItem()
{
Direction = item.Direction,
FileName = item.FileName,
Index = item.Index,
Name = item.Name,
Type = item.Type,
RequiresSerialNumber = item.RequiresSerialNumber
});
zip.AddFile(item.FilePath, update_dir);
}
String update_config_file = TemporaryManager.CreateFile(".zip");
update_config.ToFile(update_config_file);
var cuf = zip.AddFile(update_config_file, update_dir);
cuf.FileName = update_dir + "\\config.xml";
String packagesFolder = folder + "\\" + "Packages";
Directory.CreateDirectory(packagesFolder);
zip.AddDirectory(packagesFolder, "/Packages");
foreach (var file in Directory.GetFiles(folder, "*.*", SearchOption.TopDirectoryOnly))
{
zip.AddFile(file, "/");
}
zip.SaveProgress += (x, e) =>
{
if (e.EventType == ZipProgressEventType.Saving_BeforeWriteEntry)
{
OnPublishProgress(e.EntriesSaved + 1, e.EntriesTotal, $"Compressing files {(((double)(e.EntriesSaved + 1) / (double)e.EntriesTotal) * 100d).ToString("0.0")}%...", true);
}
};
zip.ParallelDeflateThreshold = -1;
zip.Save(tempFile);
OnPublishProgress(100, 100, "TUP file generated successfully.");
}
}
catch (Exception ex)
{
OnPublishProgress(0, 100, $"Failed: {ex.Message}");
throw ex;
}
});
}
///
/// Gets the version information from a TFP file.
///
/// The TFP file.
///
private VersionPackageDescriptor GetVersionInfoFromTFP(String tfpFile)
{
using (ZipFile zip = ZipFile.Read(tfpFile))
{
var reader = zip.Entries.SingleOrDefault(x => x.FileName == "package.cfg").OpenReader();
return VersionPackageDescriptor.Parser.ParseFrom(reader);
}
}
///
/// Gets the MCU version from the specified TFP file.
///
/// The TFP file.
///
public String GetLocalFirmwareVersion(String tfpFile)
{
return GetVersionInfoFromTFP(tfpFile).GetMcuVersion().ToString();
}
///
/// Raises the publish progress event.
///
/// The progress.
/// The total.
/// The message.
protected virtual void OnPublishProgress(double progress, double total, String message, bool singleLine = false)
{
PublishProgress?.Invoke(this, new PublishProgressEventArgs()
{
Progress = progress,
Total = total,
Message = message,
SingleLineRecommended = singleLine,
});
}
}
}