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