using System; using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Services.Updating; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; using Artemis.WebClient.Updating; using ReactiveUI; using Serilog; using StrawberryShake; namespace Artemis.UI.Screens.Settings.Updating; public class ReleaseDetailsViewModel : RoutableScreen { private readonly ObservableAsPropertyHelper _fileSize; private readonly ILogger _logger; private readonly INotificationService _notificationService; private readonly IUpdateService _updateService; private readonly IUpdatingClient _updatingClient; private bool _installationAvailable; private bool _installationFinished; private bool _installationInProgress; private CancellationTokenSource? _installerCts; private bool _loading = true; private IGetReleaseById_PublishedRelease? _release; private ReleaseInstaller? _releaseInstaller; public ReleaseDetailsViewModel(ILogger logger, IUpdatingClient updatingClient, INotificationService notificationService, IUpdateService updateService) { _logger = logger; _updatingClient = updatingClient; _notificationService = notificationService; _updateService = updateService; Platform updatePlatform; if (OperatingSystem.IsWindows()) updatePlatform = Platform.Windows; else if (OperatingSystem.IsLinux()) updatePlatform = Platform.Linux; else if (OperatingSystem.IsMacOS()) updatePlatform = Platform.Osx; else throw new PlatformNotSupportedException("Cannot auto update on the current platform"); Install = ReactiveCommand.CreateFromTask(ExecuteInstall); Restart = ReactiveCommand.Create(ExecuteRestart); CancelInstall = ReactiveCommand.Create(() => _installerCts?.Cancel()); _fileSize = this.WhenAnyValue(vm => vm.Release) .Select(release => release?.Artifacts.FirstOrDefault(a => a.Platform == updatePlatform)?.FileInfo.DownloadSize ?? 0) .ToProperty(this, vm => vm.FileSize); this.WhenActivated(d => Disposable.Create(_installerCts, cts => cts?.Cancel()).DisposeWith(d)); } public ReactiveCommand Restart { get; } public ReactiveCommand Install { get; } public ReactiveCommand CancelInstall { get; } public long FileSize => _fileSize.Value; public IGetReleaseById_PublishedRelease? Release { get => _release; set => RaiseAndSetIfChanged(ref _release, value); } public ReleaseInstaller? ReleaseInstaller { get => _releaseInstaller; set => RaiseAndSetIfChanged(ref _releaseInstaller, value); } public bool Loading { get => _loading; private set => RaiseAndSetIfChanged(ref _loading, value); } public bool InstallationAvailable { get => _installationAvailable; set => RaiseAndSetIfChanged(ref _installationAvailable, value); } public bool InstallationInProgress { get => _installationInProgress; set => RaiseAndSetIfChanged(ref _installationInProgress, value); } public bool InstallationFinished { get => _installationFinished; set => RaiseAndSetIfChanged(ref _installationFinished, value); } public void NavigateToSource() { if (Release != null) Utilities.OpenUrl($"https://github.com/Artemis-RGB/Artemis/commit/{Release.Commit}"); } /// public override async Task OnNavigating(ReleaseDetailsViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { // There's no point in running anything but the latest version of the current channel. // Perhaps later that won't be true anymore, then we could consider allowing to install // older versions with compatible database versions. InstallationAvailable = _updateService.CachedLatestRelease?.Id == parameters.ReleaseId; await RetrieveDetails(parameters.ReleaseId, cancellationToken); ReleaseInstaller = _updateService.GetReleaseInstaller(parameters.ReleaseId); } private void ExecuteRestart() { _updateService.RestartForUpdate(false); } private async Task ExecuteInstall(CancellationToken cancellationToken) { if (ReleaseInstaller == null) return; _installerCts = new CancellationTokenSource(); try { InstallationInProgress = true; await ReleaseInstaller.InstallAsync(_installerCts.Token); InstallationFinished = true; } catch (Exception e) { if (_installerCts.IsCancellationRequested) return; _logger.Warning(e, "Failed to install update through UI"); _notificationService.CreateNotification() .WithTitle("Failed to install update") .WithMessage(e.Message) .WithSeverity(NotificationSeverity.Warning) .Show(); } finally { InstallationInProgress = false; } } private async Task RetrieveDetails(Guid releaseId, CancellationToken cancellationToken) { try { Loading = true; IOperationResult result = await _updatingClient.GetReleaseById.ExecuteAsync(releaseId, cancellationToken); IGetReleaseById_PublishedRelease? release = result.Data?.PublishedRelease; if (release == null) return; Release = release; } catch (TaskCanceledException) { // ignored } catch (Exception e) { _logger.Warning(e, "Failed to retrieve release details"); _notificationService.CreateNotification() .WithTitle("Failed to retrieve details") .WithMessage(e.Message) .WithSeverity(NotificationSeverity.Warning) .Show(); } finally { Loading = false; } } } public class ReleaseDetailsViewModelParameters { public Guid ReleaseId { get; set; } }