aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Publish/MachineStudioPublisher.cs
generated by cgit v1.3.1 (git 2.54.0) at 2026-07-29 11:59:30 +0000
href='#n175'>175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
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.MachineStudio.Common.Web;
using Tango.Transport.Web;
using Tango.Web;

namespace Tango.MachineStudio.Common.Publish
{
    public class MachineStudioPublisher : ExtendedObject
    {
        private MachineStudioWebClient _client;

        /// <summary>
        /// Occurs on publish progress.
        /// </summary>
        public event EventHandler<PublishProgressEventArgs> PublishProgress;

        private PublishOptions _options;
        /// <summary>
        /// Gets or sets the publish options.
        /// </summary>
        public PublishOptions Options
        {
            get { return _options; }
            set { _options = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MachineStudioPublisher"/> class.
        /// </summary>
        public MachineStudioPublisher()
        {
            _client = new MachineStudioWebClient();
            Options = new PublishOptions();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MachineStudioPublisher"/> class.
        /// </summary>
        /// <param name="publishOptions">The publish options.</param>
        public MachineStudioPublisher(PublishOptions publishOptions) : this()
        {
            Options = publishOptions;
        }

        /// <summary>
        /// Gets the latest version.
        /// </summary>
        /// <returns></returns>
        public async Task<String> GetRemoteVersion()
        {
            _client.Environment = Options.Environment;
            var response = await _client.GetLatestVersion(new LatestVersionRequest());
            return response.Version;
        }

        /// <summary>
        /// Gets the latest version.
        /// </summary>
        /// <returns></returns>
        public async Task<String> GetRemoteVersion(DeploymentSlot environment)
        {
            _client.Environment = environment;
            var response = await _client.GetLatestVersion(new LatestVersionRequest());
            return response.Version;
        }

        /// <summary>
        /// Gets the latest version.
        /// </summary>
        /// <returns></returns>
        public String GetLocalVersion()
        {
            return FileVersionInfo.GetVersionInfo(GetMachineStudioExecutablePath()).ProductVersion;
        }

        /// <summary>
        /// Gets the machine studio executable path.
        /// </summary>
        /// <returns></returns>
        public String GetMachineStudioExecutablePath()
        {
            String appPath = Path.Combine(Options.GetApplicationPath(), "Tango.MachineStudio.UI.exe");
            return appPath;
        }

        /// <summary>
        /// Login to machine service and returns an access token.
        /// </summary>
        /// <returns></returns>
        private Task Login()
        {
            return Task.Factory.StartNew(() =>
            {
                return _client.Login(new LoginRequest()
                {
                    Email = Options.Email,
                    Password = Options.Password,
                    Version = GetLocalVersion(),
                }).Result;
            });
        }

        /// <summary>
        /// Publish a machine studio version using the specified <see cref="Options"/>.
        /// </summary>
        /// <returns></returns>
        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))
                {
                    throw new DirectoryNotFoundException($"Installer output folder does not exists '{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.");
                    }

                    OnPublishProgress(0, 0, "Version published successfully.");
                }
                catch (Exception ex)
                {
                    OnPublishProgress(0, 100, $"Failed: {ex.Message}");
                    throw ex;
                }
                finally
                {
                    PathHelper.TryDeleteFile(tempFile);
                }
            });
        }

        /// <summary>
        /// Raises the publish progress event.
        /// </summary>
        /// <param name="progress">The progress.</param>
        /// <param name="total">The total.</param>
        /// <param name="message">The message.</param>
        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,
            });
        }
    }
}