From 5d01665d6e29d34b37e357349ca6471c4c4437b7 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 4 Mar 2023 16:30:41 +0100 Subject: [PATCH] Added update history --- src/Artemis.Core/Constants.cs | 2 +- .../Entities/General/ReleaseEntity.cs | 19 +++++ .../Repositories/ReleaseRepository.cs | 76 +++++++++++++++++++ .../Properties/launchSettings.json | 2 +- .../WindowsUpdateNotificationProvider.cs | 2 +- .../Settings/Tabs/GeneralTabViewModel.cs | 26 +++++-- .../Settings/Tabs/ReleasesTabView.axaml | 34 +++++---- .../Settings/Updating/ReleaseView.axaml | 3 +- .../Settings/Updating/ReleaseViewModel.cs | 31 ++++---- .../Services/Updating/IUpdateService.cs | 3 +- .../Services/Updating/UpdateService.cs | 71 +++++++++-------- 11 files changed, 195 insertions(+), 74 deletions(-) create mode 100644 src/Artemis.Storage/Entities/General/ReleaseEntity.cs create mode 100644 src/Artemis.Storage/Repositories/ReleaseRepository.cs diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 6c31f21ba..15213165d 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -70,7 +70,7 @@ public static class Constants /// public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute()!.InformationalVersion != "1.0.0" ? CoreAssembly.GetCustomAttribute()!.InformationalVersion - : "1.2023.0212.2-feature-gh-actions"; + : "local"; /// /// The plugin info used by core components of Artemis diff --git a/src/Artemis.Storage/Entities/General/ReleaseEntity.cs b/src/Artemis.Storage/Entities/General/ReleaseEntity.cs new file mode 100644 index 000000000..afae86286 --- /dev/null +++ b/src/Artemis.Storage/Entities/General/ReleaseEntity.cs @@ -0,0 +1,19 @@ +using System; + +namespace Artemis.Storage.Entities.General; + +public class ReleaseEntity +{ + public Guid Id { get; set; } + + public string Version { get; set; } + public ReleaseEntityStatus Status { get; set; } + public DateTimeOffset? InstalledAt { get; set; } +} + +public enum ReleaseEntityStatus +{ + Queued, + Installed, + Historical +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ReleaseRepository.cs b/src/Artemis.Storage/Repositories/ReleaseRepository.cs new file mode 100644 index 000000000..d052f550d --- /dev/null +++ b/src/Artemis.Storage/Repositories/ReleaseRepository.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Storage.Entities.General; +using Artemis.Storage.Repositories.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Repositories; + +public class ReleaseRepository : IReleaseRepository +{ + private readonly LiteRepository _repository; + + public ReleaseRepository(LiteRepository repository) + { + _repository = repository; + _repository.Database.GetCollection().EnsureIndex(s => s.Version, true); + _repository.Database.GetCollection().EnsureIndex(s => s.Status); + } + + public string GetQueuedVersion() + { + return _repository.Query().Where(r => r.Status == ReleaseEntityStatus.Queued).FirstOrDefault()?.Version; + } + + public string GetInstalledVersion() + { + return _repository.Query().Where(r => r.Status == ReleaseEntityStatus.Installed).FirstOrDefault()?.Version; + } + + public string GetPreviousInstalledVersion() + { + return _repository.Query().Where(r => r.Status == ReleaseEntityStatus.Historical).OrderByDescending(r => r.InstalledAt).FirstOrDefault()?.Version; + } + + public void QueueInstallation(string version) + { + // Mark release as queued and add if missing + ReleaseEntity release = _repository.Query().Where(r => r.Version == version).FirstOrDefault() ?? new ReleaseEntity {Version = version}; + release.Status = ReleaseEntityStatus.Queued; + _repository.Upsert(release); + } + + public void FinishInstallation(string version) + { + // Mark release as installed and add if missing + ReleaseEntity release = _repository.Query().Where(r => r.Version == version).FirstOrDefault() ?? new ReleaseEntity {Version = version}; + release.Status = ReleaseEntityStatus.Installed; + release.InstalledAt = DateTimeOffset.UtcNow; + _repository.Upsert(release); + + // Mark other releases as historical + List oldReleases = _repository.Query().Where(r => r.Version != version && r.Status == ReleaseEntityStatus.Installed).ToList(); + if (!oldReleases.Any()) + return; + + foreach (ReleaseEntity oldRelease in oldReleases) + oldRelease.Status = ReleaseEntityStatus.Historical; + _repository.Update(oldReleases); + } + + public void DequeueInstallation() + { + _repository.DeleteMany(r => r.Status == ReleaseEntityStatus.Queued); + } +} + +public interface IReleaseRepository : IRepository +{ + string GetQueuedVersion(); + string GetInstalledVersion(); + string GetPreviousInstalledVersion(); + void QueueInstallation(string version); + void FinishInstallation(string version); + void DequeueInstallation(); +} \ No newline at end of file diff --git a/src/Artemis.UI.Windows/Properties/launchSettings.json b/src/Artemis.UI.Windows/Properties/launchSettings.json index 31322f128..62ce0eaf5 100644 --- a/src/Artemis.UI.Windows/Properties/launchSettings.json +++ b/src/Artemis.UI.Windows/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Artemis.UI.Windows": { "commandName": "Project", - "commandLineArgs": "--force-elevation --disable-forced-shutdown --pcmr --channel=feature/lawn-mowing" + "commandLineArgs": "--force-elevation --disable-forced-shutdown --pcmr --channel=feature/gh-actions" } } } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs b/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs index a73bc2f42..fa9ea76fa 100644 --- a/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs +++ b/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs @@ -128,7 +128,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider } // Queue an update in case the user interrupts the process after everything has been prepared - _updateService.QueueUpdate(); + _updateService.QueueUpdate(releaseVersion); GetBuilderForRelease(releaseId, releaseVersion) .AddAudio(new ToastAudio {Silent = true}) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs index 9d3aa0d1b..b4db42619 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs @@ -16,6 +16,7 @@ using Artemis.UI.Services.Updating; using Artemis.UI.Shared; using Artemis.UI.Shared.Providers; using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.Builders; using Avalonia.Threading; using DryIoc; using DynamicData; @@ -162,14 +163,25 @@ public class GeneralTabViewModel : ActivatableViewModelBase private async Task ExecuteCheckForUpdate(CancellationToken cancellationToken) { - // If an update was available a popup was shown, no need to continue - if (await _updateService.CheckForUpdate()) - return; + try + { + // If an update was available a popup was shown, no need to continue + if (await _updateService.CheckForUpdate()) + return; - _notificationService.CreateNotification() - .WithTitle("No update available") - .WithMessage("You are running the latest version in your current channel") - .Show(); + _notificationService.CreateNotification() + .WithTitle("No update available") + .WithMessage("You are running the latest version in your current channel") + .Show(); + } + catch (Exception e) + { + _notificationService.CreateNotification() + .WithTitle("Failed to check for update") + .WithMessage(e.Message) + .WithSeverity(NotificationSeverity.Warning) + .Show(); + } } private async Task ExecuteShowSetupWizard() diff --git a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml index 44d257cd2..aee70e179 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml @@ -9,6 +9,14 @@ mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400" x:Class="Artemis.UI.Screens.Settings.ReleasesTabView" x:DataType="settings:ReleasesTabViewModel"> + + + Loading releases... @@ -16,9 +24,10 @@ - + + Learn more about channels on the wiki @@ -31,20 +40,15 @@ - - + + - + + - + diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseView.axaml b/src/Artemis.UI/Screens/Settings/Updating/ReleaseView.axaml index 91e1a0a4c..b1f10bdab 100644 --- a/src/Artemis.UI/Screens/Settings/Updating/ReleaseView.axaml +++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseView.axaml @@ -2,7 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings" xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating" xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" xmlns:mdc="clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia" @@ -55,7 +54,7 @@ - + diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs b/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs index 7d4d34f65..bf959adbb 100644 --- a/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs @@ -25,11 +25,10 @@ public class ReleaseViewModel : ActivatableViewModelBase private readonly IUpdateService _updateService; private readonly Platform _updatePlatform; private readonly IUpdatingClient _updatingClient; - private readonly IWindowService _windowService; private CancellationTokenSource? _installerCts; - private string _changelog = string.Empty; - private string _commit = string.Empty; - private string _shortCommit = string.Empty; + private string? _changelog; + private string? _commit; + private string? _shortCommit; private long _fileSize; private bool _installationAvailable; private bool _installationFinished; @@ -43,14 +42,12 @@ public class ReleaseViewModel : ActivatableViewModelBase ILogger logger, IUpdatingClient updatingClient, INotificationService notificationService, - IUpdateService updateService, - IWindowService windowService) + IUpdateService updateService) { _logger = logger; _updatingClient = updatingClient; _notificationService = notificationService; _updateService = updateService; - _windowService = windowService; if (OperatingSystem.IsWindows()) _updatePlatform = Platform.Windows; @@ -97,19 +94,19 @@ public class ReleaseViewModel : ActivatableViewModelBase public DateTimeOffset CreatedAt { get; } public ReleaseInstaller ReleaseInstaller { get; } - public string Changelog + public string? Changelog { get => _changelog; set => RaiseAndSetIfChanged(ref _changelog, value); } - public string Commit + public string? Commit { get => _commit; set => RaiseAndSetIfChanged(ref _commit, value); } - public string ShortCommit + public string? ShortCommit { get => _shortCommit; set => RaiseAndSetIfChanged(ref _shortCommit, value); @@ -146,7 +143,9 @@ public class ReleaseViewModel : ActivatableViewModelBase } public bool IsCurrentVersion => Version == Constants.CurrentVersion; - + public bool IsPreviousVersion => Version == _updateService.PreviousVersion; + public bool ShowStatusIndicator => IsCurrentVersion || IsPreviousVersion; + public void NavigateToSource() { Utilities.OpenUrl($"https://github.com/Artemis-RGB/Artemis/commit/{Commit}"); @@ -159,14 +158,20 @@ public class ReleaseViewModel : ActivatableViewModelBase { InstallationInProgress = true; await ReleaseInstaller.InstallAsync(_installerCts.Token); - _updateService.QueueUpdate(); + _updateService.QueueUpdate(Version); InstallationFinished = true; } catch (Exception e) { if (_installerCts.IsCancellationRequested) return; - _windowService.ShowExceptionDialog("Failed to install update", e); + + _logger.Warning(e, "Failed to install update through UI"); + _notificationService.CreateNotification() + .WithTitle("Failed to install update") + .WithMessage(e.Message) + .WithSeverity(NotificationSeverity.Warning) + .Show(); } finally { diff --git a/src/Artemis.UI/Services/Updating/IUpdateService.cs b/src/Artemis.UI/Services/Updating/IUpdateService.cs index 519222c24..6b2571600 100644 --- a/src/Artemis.UI/Services/Updating/IUpdateService.cs +++ b/src/Artemis.UI/Services/Updating/IUpdateService.cs @@ -7,11 +7,12 @@ namespace Artemis.UI.Services.Updating; public interface IUpdateService : IArtemisUIService { string Channel { get; } + string? PreviousVersion { get; } IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; } Task CacheLatestRelease(); Task CheckForUpdate(); - void QueueUpdate(); + void QueueUpdate(string version); ReleaseInstaller GetReleaseInstaller(string releaseId); void RestartForUpdate(bool silent); diff --git a/src/Artemis.UI/Services/Updating/UpdateService.cs b/src/Artemis.UI/Services/Updating/UpdateService.cs index d858c5d79..d2cf9bef9 100644 --- a/src/Artemis.UI/Services/Updating/UpdateService.cs +++ b/src/Artemis.UI/Services/Updating/UpdateService.cs @@ -4,8 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; -using Artemis.Storage.Entities.General; -using Artemis.Storage.Repositories.Interfaces; +using Artemis.Storage.Repositories; using Artemis.UI.Shared.Services.MainWindow; using Artemis.WebClient.Updating; using Serilog; @@ -22,10 +21,9 @@ public class UpdateService : IUpdateService private readonly Platform _updatePlatform; private readonly ILogger _logger; - private readonly IMainWindowService _mainWindowService; - private readonly IQueuedActionRepository _queuedActionRepository; - private readonly Lazy _updateNotificationProvider; private readonly IUpdatingClient _updatingClient; + private readonly IReleaseRepository _releaseRepository; + private readonly Lazy _updateNotificationProvider; private readonly Func _getReleaseInstaller; private bool _suspendAutoCheck; @@ -33,15 +31,14 @@ public class UpdateService : IUpdateService public UpdateService(ILogger logger, ISettingsService settingsService, IMainWindowService mainWindowService, - IQueuedActionRepository queuedActionRepository, IUpdatingClient updatingClient, + IReleaseRepository releaseRepository, Lazy updateNotificationProvider, Func getReleaseInstaller) { _logger = logger; - _mainWindowService = mainWindowService; - _queuedActionRepository = queuedActionRepository; _updatingClient = updatingClient; + _releaseRepository = releaseRepository; _updateNotificationProvider = updateNotificationProvider; _getReleaseInstaller = getReleaseInstaller; @@ -59,33 +56,41 @@ public class UpdateService : IUpdateService _updatePlatform = Platform.Osx; else throw new PlatformNotSupportedException("Cannot auto update on the current platform"); - + _autoCheck = settingsService.GetSetting("UI.Updating.AutoCheck", true); _autoInstall = settingsService.GetSetting("UI.Updating.AutoInstall", false); _autoCheck.SettingChanged += HandleAutoUpdateEvent; - _mainWindowService.MainWindowOpened += HandleAutoUpdateEvent; + mainWindowService.MainWindowOpened += HandleAutoUpdateEvent; Timer timer = new(UPDATE_CHECK_INTERVAL); timer.Elapsed += HandleAutoUpdateEvent; timer.Start(); - InstallQueuedUpdate(); - _logger.Information("Update service initialized for {Channel} channel", Channel); + ProcessReleaseStatus(); } public string Channel { get; } + public string? PreviousVersion { get; set; } public IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; private set; } - private void InstallQueuedUpdate() + private void ProcessReleaseStatus() { - if (!_queuedActionRepository.IsTypeQueued("InstallUpdate")) + // If an update is queued, don't bother with anything else + string? queued = _releaseRepository.GetQueuedVersion(); + if (queued != null) + { + // Remove the queued installation, in case something goes wrong then at least we don't end up in a loop + _logger.Information("Installing queued version {Version}", queued); + RestartForUpdate(true); return; + } + + // If a different version was installed, mark it as such + string? installed = _releaseRepository.GetInstalledVersion(); + if (installed != Constants.CurrentVersion) + _releaseRepository.FinishInstallation(Constants.CurrentVersion); - // Remove the queued action, in case something goes wrong then at least we don't end up in a loop - _queuedActionRepository.ClearByType("InstallUpdate"); - - _logger.Information("Installing queued update"); - Utilities.ApplyUpdate(false); + PreviousVersion = _releaseRepository.GetPreviousInstalledVersion(); } private void ShowUpdateNotification(IGetNextRelease_NextPublishedRelease release) @@ -111,15 +116,22 @@ public class UpdateService : IUpdateService } catch (Exception ex) { - _logger.Warning(ex, "Auto update failed"); + _logger.Warning(ex, "Auto update-check failed"); } } /// public async Task CacheLatestRelease() { - IOperationResult result = await _updatingClient.GetNextRelease.ExecuteAsync(Constants.CurrentVersion, Channel, _updatePlatform); - CachedLatestRelease = result.Data?.NextPublishedRelease; + try + { + IOperationResult result = await _updatingClient.GetNextRelease.ExecuteAsync(Constants.CurrentVersion, Channel, _updatePlatform); + CachedLatestRelease = result.Data?.NextPublishedRelease; + } + catch (Exception e) + { + _logger.Warning(e, "Failed to cache latest release"); + } } public async Task CheckForUpdate() @@ -147,18 +159,11 @@ public class UpdateService : IUpdateService } /// - public void QueueUpdate() + public void QueueUpdate(string version) { - if (!_queuedActionRepository.IsTypeQueued("InstallUpdate")) - _queuedActionRepository.Add(new QueuedActionEntity {Type = "InstallUpdate"}); + _releaseRepository.QueueInstallation(version); } - - /// - public void DequeueUpdate() - { - _queuedActionRepository.ClearByType("InstallUpdate"); - } - + /// public ReleaseInstaller GetReleaseInstaller(string releaseId) { @@ -168,7 +173,7 @@ public class UpdateService : IUpdateService /// public void RestartForUpdate(bool silent) { - DequeueUpdate(); + _releaseRepository.DequeueInstallation(); Utilities.ApplyUpdate(silent); } } \ No newline at end of file