using FluentFTP;
using Ionic.Zip;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tango.Core.Commands;
using Tango.Core.DI;
using Tango.Core.Helpers;
using Tango.Core.IO;
using Tango.Logging;
using Tango.MachineStudio.Common.Authentication;
using Tango.MachineStudio.Common.Navigation;
using Tango.MachineStudio.Common.Notifications;
using Tango.MachineStudio.Common.StudioApplication;
using Tango.MachineStudio.Common.Web;
using Tango.SharedUI;
using Tango.MachineStudio.UI.Messages;
using Tango.Settings;
using Tango.MachineStudio.Common;
using Tango.Transport.Web;
using Tango.MachineStudio.Common.Web;
namespace Tango.MachineStudio.UI.ViewModels
{
public enum UpdateStatus
{
None,
CheckingForUpdate,
UpToDate,
UpdateAvailable,
Downloading,
Updating,
UpdateCompleted,
Error,
RollingBack,
RollbackCompleted,
RollbackError,
}
public class UpdateViewVM : ViewModel
{
private String _appPath = AppDomain.CurrentDomain.BaseDirectory;
private LogManager logManager = LogManager.Default;
private INotificationProvider _notification;
private INavigationManager _navigation;
private IStudioApplicationManager _application;
private IAuthenticationProvider _authentication;
private CheckForUpdatesResponse _updateInfo;
private TemporaryFolder _newPackageTempFolder;
private TemporaryFolder _previousPackageTempFolder;
private bool _forcedUpdate;
public bool ForcedUpdate
{
get { return _forcedUpdate; }
set { _forcedUpdate = value; RaisePropertyChangedAuto(); }
}
private UpdateStatus _status;
public UpdateStatus Status
{
get { return _status; }
set { _status = value; RaisePropertyChangedAuto(); }
}
private String _latestVersion;
public String LatestVersion
{
get { return _latestVersion; }
set { _latestVersion = value; RaisePropertyChangedAuto(); }
}
private String _updateComments;
///
/// Gets or sets the latest version comments.
///
public String UpdateComments
{
get { return _updateComments; }
set { _updateComments = value; RaisePropertyChangedAuto(); }
}
private double _downloadProgress;
public double DownloadProgress
{
get { return _downloadProgress; }
set { _downloadProgress = value; RaisePropertyChangedAuto(); }
}
private double _rollbackProgress;
public double RollbackProgress
{
get { return _rollbackProgress; }
set { _rollbackProgress = value; RaisePropertyChangedAuto(); }
}
private double _updateProgress;
public double UpdateProgress
{
get { return _updateProgress; }
set { _updateProgress = value; RaisePropertyChangedAuto(); }
}
private String _currentUpdateFile;
public String CurrentUpdateFile
{
get { return _currentUpdateFile; }
set { _currentUpdateFile = value; RaisePropertyChanged(nameof(CurrentUpdateFile)); }
}
private bool _isRollbackAvailable;
public bool IsRollbackAvailable
{
get { return _isRollbackAvailable; }
set { _isRollbackAvailable = value; RaisePropertyChangedAuto(); }
}
public RelayCommand UpdateCommand { get; set; }
public RelayCommand BackCommand { get; set; }
public RelayCommand RestartCommand { get; set; }
public RelayCommand TryAgainCommand { get; set; }
public RelayCommand RollbackCommand { get; set; }
public RelayCommand TryRollbackAgainCommand { get; set; }
public UpdateViewVM(INotificationProvider notification, IAuthenticationProvider authentication, INavigationManager navigation, IStudioApplicationManager application)
{
_notification = notification;
_navigation = navigation;
_application = application;
_authentication = authentication;
LatestVersion = "1.0.0.2";
Status = UpdateStatus.CheckingForUpdate;
UpdateCommand = new RelayCommand(StartUpdate, () => Status == UpdateStatus.UpdateAvailable);
BackCommand = new RelayCommand(BackToApplication, () => Status != UpdateStatus.Updating && !ForcedUpdate);
RestartCommand = new RelayCommand(RestartApplication, () => Status == UpdateStatus.UpdateCompleted || Status == UpdateStatus.RollbackCompleted);
TryAgainCommand = new RelayCommand(TryAgain, () => Status == UpdateStatus.Error);
RollbackCommand = new RelayCommand(Rollback, () => Status != UpdateStatus.RollingBack && !ForcedUpdate);
TryRollbackAgainCommand = new RelayCommand(TryRollbackAgain, () => Status == UpdateStatus.RollbackError);
IsRollbackAvailable = File.Exists(GetRollbackFile());
TangoMessenger.Default.Register(HandleForcedUpdateMessage);
TangoMessenger.Default.Register(HandleChangeVersionMessage);
}
private async void HandleChangeVersionMessage(ChangeVersionMessage msg)
{
ForcedUpdate = true;
InvalidateRelayCommands();
Status = UpdateStatus.CheckingForUpdate;
var client = new MachineStudioWebService();
DownloadLatestVersionResponse response = await client.DownloadLatestVersion(new DownloadLatestVersionRequest()
{
AccessToken = _authentication.AccessToken,
});
_updateInfo = new CheckForUpdatesResponse();
_updateInfo.BlobAddress = response.BlobAddress;
_updateInfo.Version = response.Version;
LatestVersion = _updateInfo.Version;
StartUpdate();
}
private void HandleForcedUpdateMessage(ForcedUpdateMessage msg)
{
ForcedUpdate = true;
InvalidateRelayCommands();
_updateInfo = msg.UpdateResponse;
Status = UpdateStatus.UpdateAvailable;
LatestVersion = _updateInfo.Version;
UpdateComments = _updateInfo.Comments;
StartUpdate();
}
public void OnNavigatedInto()
{
if (!ForcedUpdate)
{
CheckForUpdates();
}
}
private void CheckForUpdates()
{
Status = UpdateStatus.CheckingForUpdate;
var settings = SettingsManager.Default.GetOrCreate();
Task.Factory.StartNew(() =>
{
try
{
Thread.Sleep(2000);
var client = new MachineStudioWebService();
CheckForUpdatesResponse response = client.CheckForUpdates(new CheckForUpdatesRequest()
{
AccessToken = _authentication.AccessToken,
Version = _application.Version.ToString(),
}).Result;
if (response.IsUpdateAvailable)
{
_updateInfo = response;
Status = UpdateStatus.UpdateAvailable;
LatestVersion = response.Version;
UpdateComments = response.Comments;
}
else
{
Status = UpdateStatus.UpToDate;
}
}
catch (Exception ex)
{
logManager.Log(ex, "Error while checking for version update!");
Status = UpdateStatus.Error;
}
});
}
private void BackToApplication()
{
if (Status == UpdateStatus.Downloading)
{
if (!_notification.ShowQuestion("This will abort all update operations. Are you sure?"))
{
return;
}
}
_navigation.NavigateTo(NavigationView.MainView);
Status = UpdateStatus.None;
}
private void StartUpdate()
{
DownloadProgress = 0;
UpdateProgress = 0;
Status = UpdateStatus.Downloading;
Task.Factory.StartNew(() =>
{
var tempFile = TemporaryManager.CreateFile(".zip");
try
{
logManager.Log("Creating temporary file " + tempFile);
using (StorageBlobDownloader downloader = new StorageBlobDownloader(_updateInfo.BlobAddress, tempFile.Path))
{
downloader.Progress += (x, e) =>
{
InvokeUINow(() =>
{
DownloadProgress = ((double)e.Current / (double)e.Total) * 100d;
});
};
downloader.Download().Wait();
}
Status = UpdateStatus.Updating;
_newPackageTempFolder = TemporaryManager.CreateFolder();
_newPackageTempFolder.Persist = true;
using (ZipFile zip = ZipFile.Read(tempFile.Path))
{
int currentEntry = 0;
zip.ExtractProgress += (x, args) =>
{
if (args.EventType == ZipProgressEventType.Extracting_AfterExtractEntry)
{
logManager.Log("Extracting " + Path.GetFileName(args.CurrentEntry.FileName));
UpdateProgress = ((double)(currentEntry++) / (double)zip.Entries.Count) * 100d;
}
};
foreach (ZipEntry entry in zip)
{
Thread.Sleep(10);
string newPath = Path.Combine(_newPackageTempFolder.Path, entry.FileName);
try
{
if (entry.IsDirectory)
{
Directory.CreateDirectory(newPath);
}
else
{
CurrentUpdateFile = Path.GetFileName(entry.FileName);
entry.Extract(_newPackageTempFolder.Path, ExtractExistingFileAction.OverwriteSilently);
}
}
catch
{
logManager.Log("Could not extract file " + entry.FileName);
}
}
}
try
{
LogManager.Log("Backing up current version...");
CurrentUpdateFile = "Backing up current version...";
String rollbackFolder = GetRollbackFolder();
Directory.CreateDirectory(rollbackFolder);
String backFile = GetRollbackFile();
if (File.Exists(backFile))
{
File.Delete(backFile);
}
using (ZipFile backZip = new ZipFile(backFile))
{
int currentEntry = 0;
backZip.SaveProgress += (_, e) =>
{
UpdateProgress = ((double)(currentEntry++) / (double)backZip.Entries.Count) * 100d;
};
backZip.Password = "Aa123456";
backZip.AddDirectory(_appPath);
backZip.Save();
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Could not construct rollback.");
_notification.ShowWarning("Update center has failed to construct a rollback point for the current version. Version rollback will not be available.");
}
TangoIOC.Default.GetInstance().DisableCheckForUpdates = true;
Status = UpdateStatus.UpdateCompleted;
}
catch (Exception ex)
{
logManager.Log(ex, "Error while extracting update package.");
Status = UpdateStatus.Error;
}
finally
{
tempFile.Delete();
}
});
}
private void TryAgain()
{
CheckForUpdates();
}
private void RestartApplication()
{
try
{
Process p = new Process();
if (Status == UpdateStatus.UpdateCompleted)
{
p.StartInfo.FileName = _newPackageTempFolder + "\\Tango.MachineStudio.Updater.exe";
}
else if (Status == UpdateStatus.RollbackCompleted)
{
p.StartInfo.FileName = _previousPackageTempFolder + "\\Tango.MachineStudio.Updater.exe";
}
p.StartInfo.UseShellExecute = true;
p.StartInfo.Arguments = _appPath;
p.Start();
}
catch (Exception ex)
{
if (ex.Message == "The operation was canceled by the user")
{
_notification.ShowWarning("It seems like you refused to invoke our update utility. This prevents Machine Studio from completing the update process!");
return;
}
}
Environment.Exit(0);
}
private void Rollback()
{
if (_notification.ShowQuestion("Are you sure you want to restore the previous version?"))
{
Status = UpdateStatus.RollingBack;
try
{
Task.Factory.StartNew(() =>
{
_previousPackageTempFolder = TemporaryManager.CreateFolder();
_previousPackageTempFolder.Persist = true;
using (ZipFile zip = new ZipFile(GetRollbackFile()))
{
zip.Password = "Aa123456";
int currentEntry = 0;
zip.ExtractProgress += (x, args) =>
{
if (args.EventType == ZipProgressEventType.Extracting_AfterExtractEntry)
{
logManager.Log("Extracting " + Path.GetFileName(args.CurrentEntry.FileName));
RollbackProgress = ((double)(currentEntry++) / (double)zip.Entries.Count) * 100d;
}
};
foreach (ZipEntry entry in zip)
{
Thread.Sleep(10);
string newPath = Path.Combine(_previousPackageTempFolder.Path, entry.FileName);
try
{
if (entry.IsDirectory)
{
Directory.CreateDirectory(newPath);
}
else
{
entry.Extract(_previousPackageTempFolder.Path, ExtractExistingFileAction.OverwriteSilently);
}
}
catch
{
logManager.Log("Could not extract file " + entry.FileName);
}
}
}
File.Delete(GetRollbackFile());
Status = UpdateStatus.RollbackCompleted;
});
}
catch (Exception ex)
{
Status = UpdateStatus.Error;
LogManager.Log(ex, "Error while trying to restore version.");
_notification.ShowError("An error occurred while trying to restore the previous version.");
}
}
}
private void TryRollbackAgain()
{
CheckForUpdates();
}
private String GetRollbackFolder()
{
String rollbackFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Rollback");
return rollbackFolder;
}
private String GetRollbackFile()
{
String backFile = Path.Combine(GetRollbackFolder(), Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName) + ".rollback");
return backFile;
}
protected override void RaisePropertyChangedAuto([CallerMemberName] string caller = null)
{
base.RaisePropertyChangedAuto(caller);
InvalidateRelayCommands();
}
}
}