aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Publish/MachineStudioPublisher.cs
blob: d55324b32b5795d8c61f17398c7850d6ef49d178 (149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
'n179' href='#n179'>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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
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;

        /// <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.");
                    }

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

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