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, }); } } }