using CommandLine; 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.Core; using Tango.Core.Cryptography; using Tango.Core.Helpers; using Tango.Git; using Tango.MachineStudio.Common.Web; using Tango.Transport.Web; using Tango.Web; namespace Tango.MachineStudio.Common.Publish { public class MachineStudioPublisher : ExtendedObject { private MachineStudioWebClient _client; /// /// Occurs on publish progress. /// public event EventHandler PublishProgress; 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 MachineStudioPublisher() { _client = new MachineStudioWebClient(); Options = new PublishOptions(); } /// /// Initializes a new instance of the class. /// /// The publish options. public MachineStudioPublisher(PublishOptions publishOptions) : this() { Options = publishOptions; } /// /// Gets the latest version. /// /// public async Task GetRemoteVersion() { _client.Environment = Options.Environment; var response = await _client.GetLatestVersion(new LatestVersionRequest()); return response.Version; } /// /// 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() { return FileVersionInfo.GetVersionInfo(GetMachineStudioExecutablePath()).ProductVersion; } /// /// Gets the machine studio executable path. /// /// public String GetMachineStudioExecutablePath() { String appPath = Path.Combine(Options.GetApplicationPath(), "Tango.MachineStudio.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() { Email = Options.Email, Password = Options.Password, Version = GetLocalVersion(), }).Result; }); } /// /// Publish a machine studio version using the specified . /// /// public Task Publish() { _client.Environment = Options.Environment; String appPath = GetMachineStudioExecutablePath(); String folder = Options.GetApplicationPath(); if (!File.Exists(appPath)) { throw new FileNotFoundException($"Could not locate the machine studio 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); } } String tempFile = TemporaryManager.CreateFile(); return Task.Factory.StartNew(() => { 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()}..."); String remote_version = GetRemoteVersion().Result; String local_version = GetLocalVersion(); 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}'."); } OnPublishProgress(0, 100, $"Requesting version upload..."); var response = _client.UploadVersion(new UploadVersionRequest() { Version = local_version, Comments = Options.Comments, WithInstaller = !String.IsNullOrWhiteSpace(Options.InstallerProject), }).Result; 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 = $"Machine Studio Installer_v{Version.Parse(local_version).ToString(3)}.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 packaging..."); using (ZipFile zip = new ZipFile()) { foreach (var directory in Directory.GetDirectories(folder)) { zip.AddDirectory(directory, $"/{Path.GetFileName(directory)}"); } var files = Directory.GetFiles(folder, "*.*", SearchOption.TopDirectoryOnly).ToList(); foreach (var file in files) { 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(0, 100, $"Starting version upload..."); using (StorageBlobUploader uploader = new StorageBlobUploader(response.BlobAddress, tempFile)) { uploader.Progress += (x, e) => { 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().Result; 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 = $"Machine_Studio_v{tagVersion}"; 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); } }); } /// /// 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, }); } } }