mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Simplify release install process
Fix install on startup
This commit is contained in:
parent
09a2f769c3
commit
4a8845e578
@ -48,6 +48,10 @@ public static class Constants
|
|||||||
/// The full path to the Artemis logs folder
|
/// The full path to the Artemis logs folder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
|
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
|
||||||
|
/// <summary>
|
||||||
|
/// The full path to the Artemis logs folder
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string UpdatingFolder = Path.Combine(DataFolder, "updating");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The full path to the Artemis plugins folder
|
/// The full path to the Artemis plugins folder
|
||||||
|
|||||||
@ -21,6 +21,7 @@ public static class Utilities
|
|||||||
CreateAccessibleDirectory(Constants.DataFolder);
|
CreateAccessibleDirectory(Constants.DataFolder);
|
||||||
CreateAccessibleDirectory(Constants.PluginsFolder);
|
CreateAccessibleDirectory(Constants.PluginsFolder);
|
||||||
CreateAccessibleDirectory(Constants.LayoutsFolder);
|
CreateAccessibleDirectory(Constants.LayoutsFolder);
|
||||||
|
CreateAccessibleDirectory(Constants.UpdatingFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -7,15 +7,5 @@ public class ReleaseEntity
|
|||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
public string ReleaseId { get; set; }
|
|
||||||
public ReleaseEntityStatus Status { get; set; }
|
|
||||||
public DateTimeOffset? InstalledAt { get; set; }
|
public DateTimeOffset? InstalledAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ReleaseEntityStatus
|
|
||||||
{
|
|
||||||
Queued,
|
|
||||||
Installed,
|
|
||||||
Historical,
|
|
||||||
Unknown
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Artemis.Storage.Entities.General;
|
using Artemis.Storage.Entities.General;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
@ -14,63 +13,25 @@ public class ReleaseRepository : IReleaseRepository
|
|||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_repository.Database.GetCollection<ReleaseEntity>().EnsureIndex(s => s.Version, true);
|
_repository.Database.GetCollection<ReleaseEntity>().EnsureIndex(s => s.Version, true);
|
||||||
_repository.Database.GetCollection<ReleaseEntity>().EnsureIndex(s => s.Status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReleaseEntity GetQueuedVersion()
|
public void SaveVersionInstallDate(string version)
|
||||||
{
|
{
|
||||||
return _repository.Query<ReleaseEntity>().Where(r => r.Status == ReleaseEntityStatus.Queued).FirstOrDefault();
|
ReleaseEntity release = _repository.Query<ReleaseEntity>().Where(r => r.Version == version).FirstOrDefault();
|
||||||
}
|
if (release != null)
|
||||||
|
return;
|
||||||
|
|
||||||
public ReleaseEntity GetInstalledVersion()
|
_repository.Insert(new ReleaseEntity {Version = version, InstalledAt = DateTimeOffset.UtcNow});
|
||||||
{
|
|
||||||
return _repository.Query<ReleaseEntity>().Where(r => r.Status == ReleaseEntityStatus.Installed).FirstOrDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReleaseEntity GetPreviousInstalledVersion()
|
public ReleaseEntity GetPreviousInstalledVersion()
|
||||||
{
|
{
|
||||||
return _repository.Query<ReleaseEntity>().Where(r => r.Status == ReleaseEntityStatus.Historical).OrderByDescending(r => r.InstalledAt).FirstOrDefault();
|
return _repository.Query<ReleaseEntity>().OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault();
|
||||||
}
|
|
||||||
|
|
||||||
public void QueueInstallation(string version, string releaseId)
|
|
||||||
{
|
|
||||||
// Mark release as queued and add if missing
|
|
||||||
ReleaseEntity release = _repository.Query<ReleaseEntity>().Where(r => r.Version == version).FirstOrDefault() ?? new ReleaseEntity {Version = version, ReleaseId = releaseId};
|
|
||||||
release.Status = ReleaseEntityStatus.Queued;
|
|
||||||
_repository.Upsert(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FinishInstallation(string version)
|
|
||||||
{
|
|
||||||
// Mark release as installed and add if missing
|
|
||||||
ReleaseEntity release = _repository.Query<ReleaseEntity>().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<ReleaseEntity> oldReleases = _repository.Query<ReleaseEntity>().Where(r => r.Version != version && r.Status != ReleaseEntityStatus.Historical).ToList();
|
|
||||||
foreach (ReleaseEntity oldRelease in oldReleases)
|
|
||||||
oldRelease.Status = ReleaseEntityStatus.Historical;
|
|
||||||
_repository.Update<ReleaseEntity>(oldReleases);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DequeueInstallation()
|
|
||||||
{
|
|
||||||
// Mark all queued releases as unknown, until FinishInstallation is called we don't know the status
|
|
||||||
List<ReleaseEntity> queuedReleases = _repository.Query<ReleaseEntity>().Where(r => r.Status == ReleaseEntityStatus.Queued).ToList();
|
|
||||||
foreach (ReleaseEntity queuedRelease in queuedReleases)
|
|
||||||
queuedRelease.Status = ReleaseEntityStatus.Unknown;
|
|
||||||
_repository.Update<ReleaseEntity>(queuedReleases);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IReleaseRepository : IRepository
|
public interface IReleaseRepository : IRepository
|
||||||
{
|
{
|
||||||
ReleaseEntity GetQueuedVersion();
|
void SaveVersionInstallDate(string version);
|
||||||
ReleaseEntity GetInstalledVersion();
|
|
||||||
ReleaseEntity GetPreviousInstalledVersion();
|
ReleaseEntity GetPreviousInstalledVersion();
|
||||||
void QueueInstallation(string version, string releaseId);
|
|
||||||
void FinishInstallation(string version);
|
|
||||||
void DequeueInstallation();
|
|
||||||
}
|
}
|
||||||
@ -44,8 +44,8 @@ public class App : Application
|
|||||||
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
|
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ArtemisBootstrapper.Initialize();
|
|
||||||
_applicationStateManager = new ApplicationStateManager(_container!, desktop.Args);
|
_applicationStateManager = new ApplicationStateManager(_container!, desktop.Args);
|
||||||
|
ArtemisBootstrapper.Initialize();
|
||||||
RegisterProviders(_container!);
|
RegisterProviders(_container!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -99,8 +99,8 @@ public class ApplicationStateManager
|
|||||||
argsList.Add("--autorun");
|
argsList.Add("--autorun");
|
||||||
|
|
||||||
// Retain startup arguments after update by providing them to the script
|
// Retain startup arguments after update by providing them to the script
|
||||||
string script = $"\"{Path.Combine(Constants.DataFolder, "updating", "pending", "scripts", "update.ps1")}\"";
|
string script = $"\"{Path.Combine(Constants.UpdatingFolder, "installing", "scripts", "update.ps1")}\"";
|
||||||
string source = $"-sourceDirectory \"{Path.Combine(Constants.DataFolder, "updating", "pending")}\"";
|
string source = $"-sourceDirectory \"{Path.Combine(Constants.UpdatingFolder, "installing")}\"";
|
||||||
string destination = $"-destinationDirectory \"{Constants.ApplicationFolder}\"";
|
string destination = $"-destinationDirectory \"{Constants.ApplicationFolder}\"";
|
||||||
string args = argsList.Any() ? $"-artemisArgs \"{string.Join(',', argsList)}\"" : "";
|
string args = argsList.Any() ? $"-artemisArgs \"{string.Join(',', argsList)}\"" : "";
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ using Artemis.UI.Screens.Settings;
|
|||||||
using Artemis.UI.Services.Updating;
|
using Artemis.UI.Services.Updating;
|
||||||
using Artemis.UI.Shared.Services.MainWindow;
|
using Artemis.UI.Shared.Services.MainWindow;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using DryIoc.ImTools;
|
||||||
using Microsoft.Toolkit.Uwp.Notifications;
|
using Microsoft.Toolkit.Uwp.Notifications;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ namespace Artemis.UI.Windows.Providers;
|
|||||||
|
|
||||||
public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
||||||
{
|
{
|
||||||
private readonly Func<string, ReleaseInstaller> _getReleaseInstaller;
|
private readonly Func<Guid, ReleaseInstaller> _getReleaseInstaller;
|
||||||
private readonly Func<IScreen, SettingsViewModel> _getSettingsViewModel;
|
private readonly Func<IScreen, SettingsViewModel> _getSettingsViewModel;
|
||||||
private readonly IMainWindowService _mainWindowService;
|
private readonly IMainWindowService _mainWindowService;
|
||||||
private readonly IUpdateService _updateService;
|
private readonly IUpdateService _updateService;
|
||||||
@ -25,7 +26,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
public WindowsUpdateNotificationProvider(IMainWindowService mainWindowService,
|
public WindowsUpdateNotificationProvider(IMainWindowService mainWindowService,
|
||||||
IUpdateService updateService,
|
IUpdateService updateService,
|
||||||
Func<IScreen, SettingsViewModel> getSettingsViewModel,
|
Func<IScreen, SettingsViewModel> getSettingsViewModel,
|
||||||
Func<string, ReleaseInstaller> getReleaseInstaller)
|
Func<Guid, ReleaseInstaller> getReleaseInstaller)
|
||||||
{
|
{
|
||||||
_mainWindowService = mainWindowService;
|
_mainWindowService = mainWindowService;
|
||||||
_updateService = updateService;
|
_updateService = updateService;
|
||||||
@ -37,7 +38,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e)
|
private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e)
|
||||||
{
|
{
|
||||||
ToastArguments args = ToastArguments.Parse(e.Argument);
|
ToastArguments args = ToastArguments.Parse(e.Argument);
|
||||||
string releaseId = args.Get("releaseId");
|
Guid releaseId = Guid.Parse(args.Get("releaseId"));
|
||||||
string releaseVersion = args.Get("releaseVersion");
|
string releaseVersion = args.Get("releaseVersion");
|
||||||
string action = "view-changes";
|
string action = "view-changes";
|
||||||
if (args.Contains("action"))
|
if (args.Contains("action"))
|
||||||
@ -53,7 +54,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
_updateService.RestartForUpdate(false);
|
_updateService.RestartForUpdate(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowNotification(string releaseId, string releaseVersion)
|
public void ShowNotification(Guid releaseId, string releaseVersion)
|
||||||
{
|
{
|
||||||
GetBuilderForRelease(releaseId, releaseVersion)
|
GetBuilderForRelease(releaseId, releaseVersion)
|
||||||
.AddText("Update available")
|
.AddText("Update available")
|
||||||
@ -62,10 +63,10 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
.SetContent("Install")
|
.SetContent("Install")
|
||||||
.AddArgument("action", "install").SetAfterActivationBehavior(ToastAfterActivationBehavior.PendingUpdate))
|
.AddArgument("action", "install").SetAfterActivationBehavior(ToastAfterActivationBehavior.PendingUpdate))
|
||||||
.AddButton(new ToastButton().SetContent("View changes").AddArgument("action", "view-changes"))
|
.AddButton(new ToastButton().SetContent("View changes").AddArgument("action", "view-changes"))
|
||||||
.Show(t => t.Tag = releaseId);
|
.Show(t => t.Tag = releaseId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ViewRelease(string releaseId)
|
private void ViewRelease(Guid releaseId)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
@ -87,7 +88,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InstallRelease(string releaseId, string releaseVersion)
|
private async Task InstallRelease(Guid releaseId, string releaseVersion)
|
||||||
{
|
{
|
||||||
ReleaseInstaller installer = _getReleaseInstaller(releaseId);
|
ReleaseInstaller installer = _getReleaseInstaller(releaseId);
|
||||||
void InstallerOnPropertyChanged(object? sender, PropertyChangedEventArgs e) => UpdateInstallProgress(releaseId, installer);
|
void InstallerOnPropertyChanged(object? sender, PropertyChangedEventArgs e) => UpdateInstallProgress(releaseId, installer);
|
||||||
@ -104,7 +105,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
.AddButton(new ToastButton().SetContent("Cancel").AddArgument("action", "cancel"))
|
.AddButton(new ToastButton().SetContent("Cancel").AddArgument("action", "cancel"))
|
||||||
.Show(t =>
|
.Show(t =>
|
||||||
{
|
{
|
||||||
t.Tag = releaseId;
|
t.Tag = releaseId.ToString();
|
||||||
t.Data = GetDataForInstaller(installer);
|
t.Data = GetDataForInstaller(installer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -127,26 +128,23 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
installer.PropertyChanged -= InstallerOnPropertyChanged;
|
installer.PropertyChanged -= InstallerOnPropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue an update in case the user interrupts the process after everything has been prepared
|
|
||||||
_updateService.QueueUpdate(releaseVersion, releaseId);
|
|
||||||
|
|
||||||
GetBuilderForRelease(releaseId, releaseVersion)
|
GetBuilderForRelease(releaseId, releaseVersion)
|
||||||
.AddAudio(new ToastAudio {Silent = true})
|
.AddAudio(new ToastAudio {Silent = true})
|
||||||
.AddText("Update ready")
|
.AddText("Update ready")
|
||||||
.AddText($"Artemis version {releaseVersion} is ready to be applied")
|
.AddText($"Artemis version {releaseVersion} is ready to be applied")
|
||||||
.AddButton(new ToastButton().SetContent("Restart Artemis").AddArgument("action", "restart-for-update"))
|
.AddButton(new ToastButton().SetContent("Restart Artemis").AddArgument("action", "restart-for-update"))
|
||||||
.AddButton(new ToastButton().SetContent("Later").AddArgument("action", "postpone-update"))
|
.AddButton(new ToastButton().SetContent("Later").AddArgument("action", "postpone-update"))
|
||||||
.Show(t => t.Tag = releaseId);
|
.Show(t => t.Tag = releaseId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateInstallProgress(string releaseId, ReleaseInstaller installer)
|
private void UpdateInstallProgress(Guid releaseId, ReleaseInstaller installer)
|
||||||
{
|
{
|
||||||
ToastNotificationManagerCompat.CreateToastNotifier().Update(GetDataForInstaller(installer), releaseId);
|
ToastNotificationManagerCompat.CreateToastNotifier().Update(GetDataForInstaller(installer), releaseId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ToastContentBuilder GetBuilderForRelease(string releaseId, string releaseVersion)
|
private ToastContentBuilder GetBuilderForRelease(Guid releaseId, string releaseVersion)
|
||||||
{
|
{
|
||||||
return new ToastContentBuilder().AddArgument("releaseId", releaseId).AddArgument("releaseVersion", releaseVersion);
|
return new ToastContentBuilder().AddArgument("releaseId", releaseId.ToString()).AddArgument("releaseVersion", releaseVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationData GetDataForInstaller(ReleaseInstaller installer)
|
private NotificationData GetDataForInstaller(ReleaseInstaller installer)
|
||||||
|
|||||||
@ -480,7 +480,7 @@ public class ScriptVmFactory : IScriptVmFactory
|
|||||||
|
|
||||||
public interface IReleaseVmFactory : IVmFactory
|
public interface IReleaseVmFactory : IVmFactory
|
||||||
{
|
{
|
||||||
ReleaseViewModel ReleaseListViewModel(string releaseId, string version, DateTimeOffset createdAt);
|
ReleaseViewModel ReleaseListViewModel(Guid releaseId, string version, DateTimeOffset createdAt);
|
||||||
}
|
}
|
||||||
public class ReleaseVmFactory : IReleaseVmFactory
|
public class ReleaseVmFactory : IReleaseVmFactory
|
||||||
{
|
{
|
||||||
@ -491,7 +491,7 @@ public class ReleaseVmFactory : IReleaseVmFactory
|
|||||||
_container = container;
|
_container = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReleaseViewModel ReleaseListViewModel(string releaseId, string version, DateTimeOffset createdAt)
|
public ReleaseViewModel ReleaseListViewModel(Guid releaseId, string version, DateTimeOffset createdAt)
|
||||||
{
|
{
|
||||||
return _container.Resolve<ReleaseViewModel>(new object[] { releaseId, version, createdAt });
|
return _container.Resolve<ReleaseViewModel>(new object[] { releaseId, version, createdAt });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,9 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
|
|||||||
Router.CurrentViewModel.Subscribe(UpdateTitleBarViewModel);
|
Router.CurrentViewModel.Subscribe(UpdateTitleBarViewModel);
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
if (_updateService.Initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
coreService.Initialize();
|
coreService.Initialize();
|
||||||
registrationService.RegisterBuiltInDataModelDisplays();
|
registrationService.RegisterBuiltInDataModelDisplays();
|
||||||
registrationService.RegisterBuiltInDataModelInputs();
|
registrationService.RegisterBuiltInDataModelInputs();
|
||||||
|
|||||||
@ -61,7 +61,7 @@ public class ReleasesTabViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
public ReadOnlyObservableCollection<ReleaseViewModel> ReleaseViewModels { get; }
|
public ReadOnlyObservableCollection<ReleaseViewModel> ReleaseViewModels { get; }
|
||||||
public string Channel { get; }
|
public string Channel { get; }
|
||||||
public string? PreselectId { get; set; }
|
public Guid? PreselectId { get; set; }
|
||||||
|
|
||||||
public ReleaseViewModel? SelectedReleaseViewModel
|
public ReleaseViewModel? SelectedReleaseViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@ -36,7 +36,7 @@ public class ReleaseViewModel : ActivatableViewModelBase
|
|||||||
private bool _loading = true;
|
private bool _loading = true;
|
||||||
private bool _retrievedDetails;
|
private bool _retrievedDetails;
|
||||||
|
|
||||||
public ReleaseViewModel(string releaseId,
|
public ReleaseViewModel(Guid releaseId,
|
||||||
string version,
|
string version,
|
||||||
DateTimeOffset createdAt,
|
DateTimeOffset createdAt,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
@ -79,7 +79,7 @@ public class ReleaseViewModel : ActivatableViewModelBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ReleaseId { get; }
|
public Guid ReleaseId { get; }
|
||||||
|
|
||||||
private void ExecuteRestart()
|
private void ExecuteRestart()
|
||||||
{
|
{
|
||||||
@ -158,7 +158,6 @@ public class ReleaseViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
InstallationInProgress = true;
|
InstallationInProgress = true;
|
||||||
await ReleaseInstaller.InstallAsync(_installerCts.Token);
|
await ReleaseInstaller.InstallAsync(_installerCts.Token);
|
||||||
_updateService.QueueUpdate(Version, ReleaseId);
|
|
||||||
InstallationFinished = true;
|
InstallationFinished = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Artemis.UI.Services.Updating;
|
namespace Artemis.UI.Services.Updating;
|
||||||
|
|
||||||
public interface IUpdateNotificationProvider
|
public interface IUpdateNotificationProvider
|
||||||
{
|
{
|
||||||
void ShowNotification(string releaseId, string releaseVersion);
|
void ShowNotification(Guid releaseId, string releaseVersion);
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.WebClient.Updating;
|
using Artemis.WebClient.Updating;
|
||||||
@ -6,14 +7,47 @@ namespace Artemis.UI.Services.Updating;
|
|||||||
|
|
||||||
public interface IUpdateService : IArtemisUIService
|
public interface IUpdateService : IArtemisUIService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current update channel.
|
||||||
|
/// </summary>
|
||||||
string Channel { get; }
|
string Channel { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the version number of the previous release that was installed, if any.
|
||||||
|
/// </summary>
|
||||||
string? PreviousVersion { get; }
|
string? PreviousVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The latest cached release, can be updated by calling <see cref="CachedLatestRelease" />.
|
||||||
|
/// </summary>
|
||||||
IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; }
|
IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously caches the latest release.
|
||||||
|
/// </summary>
|
||||||
Task CacheLatestRelease();
|
Task CacheLatestRelease();
|
||||||
Task<bool> CheckForUpdate();
|
|
||||||
void QueueUpdate(string version, string releaseId);
|
|
||||||
|
|
||||||
ReleaseInstaller GetReleaseInstaller(string releaseId);
|
/// <summary>
|
||||||
|
/// Asynchronously checks whether an update is available on the current <see cref="Channel" />.
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> CheckForUpdate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a release installed for a release with the provided ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="releaseId">The ID of the release to create the installer for.</param>
|
||||||
|
/// <returns>The resulting release installer.</returns>
|
||||||
|
ReleaseInstaller GetReleaseInstaller(Guid releaseId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restarts the application to install a pending update.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="silent">A boolean indicating whether to perform a silent install of the update.</param>
|
||||||
void RestartForUpdate(bool silent);
|
void RestartForUpdate(bool silent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the update service.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A boolean indicating whether a restart will occur to install a pending update.</returns>
|
||||||
|
bool Initialize();
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ public class InAppUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
_getSettingsViewModel = getSettingsViewModel;
|
_getSettingsViewModel = getSettingsViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowInAppNotification(string releaseId, string releaseVersion)
|
private void ShowInAppNotification(Guid releaseId, string releaseVersion)
|
||||||
{
|
{
|
||||||
_notification?.Invoke();
|
_notification?.Invoke();
|
||||||
_notification = _notificationService.CreateNotification()
|
_notification = _notificationService.CreateNotification()
|
||||||
@ -35,7 +35,7 @@ public class InAppUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
.Show();
|
.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ViewRelease(string releaseId)
|
private void ViewRelease(Guid releaseId)
|
||||||
{
|
{
|
||||||
_notification?.Invoke();
|
_notification?.Invoke();
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class InAppUpdateNotificationProvider : IUpdateNotificationProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ShowNotification(string releaseId, string releaseVersion)
|
public void ShowNotification(Guid releaseId, string releaseVersion)
|
||||||
{
|
{
|
||||||
if (_mainWindowService.IsMainWindowOpen)
|
if (_mainWindowService.IsMainWindowOpen)
|
||||||
ShowInAppNotification(releaseId, releaseVersion);
|
ShowInAppNotification(releaseId, releaseVersion);
|
||||||
|
|||||||
@ -22,24 +22,26 @@ namespace Artemis.UI.Services.Updating;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ReleaseInstaller : CorePropertyChanged
|
public class ReleaseInstaller : CorePropertyChanged
|
||||||
{
|
{
|
||||||
private readonly string _dataFolder;
|
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly string _releaseId;
|
private readonly Guid _releaseId;
|
||||||
private readonly Platform _updatePlatform;
|
private readonly Platform _updatePlatform;
|
||||||
private readonly IUpdatingClient _updatingClient;
|
private readonly IUpdatingClient _updatingClient;
|
||||||
private readonly Progress<float> _progress = new();
|
private readonly Progress<float> _progress = new();
|
||||||
|
|
||||||
|
private IGetReleaseById_PublishedRelease _release = null!;
|
||||||
|
private IGetReleaseById_PublishedRelease_Artifacts _artifact = null!;
|
||||||
|
|
||||||
private Progress<float> _stepProgress = new();
|
private Progress<float> _stepProgress = new();
|
||||||
private string _status = string.Empty;
|
private string _status = string.Empty;
|
||||||
private float _progress1;
|
private float _floatProgress;
|
||||||
|
|
||||||
public ReleaseInstaller(string releaseId, ILogger logger, IUpdatingClient updatingClient, HttpClient httpClient)
|
public ReleaseInstaller(Guid releaseId, ILogger logger, IUpdatingClient updatingClient, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_releaseId = releaseId;
|
_releaseId = releaseId;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_updatingClient = updatingClient;
|
_updatingClient = updatingClient;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_dataFolder = Path.Combine(Constants.DataFolder, "updating");
|
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
_updatePlatform = Platform.Windows;
|
_updatePlatform = Platform.Windows;
|
||||||
@ -50,9 +52,6 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
else
|
else
|
||||||
throw new PlatformNotSupportedException("Cannot auto update on the current platform");
|
throw new PlatformNotSupportedException("Cannot auto update on the current platform");
|
||||||
|
|
||||||
if (!Directory.Exists(_dataFolder))
|
|
||||||
Directory.CreateDirectory(_dataFolder);
|
|
||||||
|
|
||||||
_progress.ProgressChanged += (_, f) => Progress = f;
|
_progress.ProgressChanged += (_, f) => Progress = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,8 +63,8 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
|
|
||||||
public float Progress
|
public float Progress
|
||||||
{
|
{
|
||||||
get => _progress1;
|
get => _floatProgress;
|
||||||
set => SetAndNotify(ref _progress1, value);
|
set => SetAndNotify(ref _floatProgress, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InstallAsync(CancellationToken cancellationToken)
|
public async Task InstallAsync(CancellationToken cancellationToken)
|
||||||
@ -79,24 +78,24 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
|
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
|
||||||
result.EnsureNoErrors();
|
result.EnsureNoErrors();
|
||||||
|
|
||||||
IGetReleaseById_PublishedRelease? release = result.Data?.PublishedRelease;
|
_release = result.Data?.PublishedRelease!;
|
||||||
if (release == null)
|
if (_release == null)
|
||||||
throw new Exception($"Could not find release with ID {_releaseId}");
|
throw new Exception($"Could not find release with ID {_releaseId}");
|
||||||
|
|
||||||
IGetReleaseById_PublishedRelease_Artifacts? artifact = release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform);
|
_artifact = _release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform)!;
|
||||||
if (artifact == null)
|
if (_artifact == null)
|
||||||
throw new Exception("Found the release but it has no artifact for the current platform");
|
throw new Exception("Found the release but it has no artifact for the current platform");
|
||||||
|
|
||||||
((IProgress<float>) _progress).Report(10);
|
((IProgress<float>) _progress).Report(10);
|
||||||
|
|
||||||
// Determine whether the last update matches our local version, then we can download the delta
|
// Determine whether the last update matches our local version, then we can download the delta
|
||||||
if (release.PreviousRelease != null && File.Exists(Path.Combine(_dataFolder, $"{release.PreviousRelease}.zip")) && artifact.DeltaFileInfo.DownloadSize != 0)
|
if (_release.PreviousRelease != null && File.Exists(Path.Combine(Constants.UpdatingFolder, $"{_release.PreviousRelease.Version}.zip")) && _artifact.DeltaFileInfo.DownloadSize != 0)
|
||||||
await DownloadDelta(artifact, Path.Combine(_dataFolder, $"{release.PreviousRelease}.zip"), cancellationToken);
|
await DownloadDelta(Path.Combine(Constants.UpdatingFolder, $"{_release.PreviousRelease.Version}.zip"), cancellationToken);
|
||||||
else
|
else
|
||||||
await Download(artifact, cancellationToken);
|
await Download(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadDelta(IGetReleaseById_PublishedRelease_Artifacts artifact, string previousRelease, CancellationToken cancellationToken)
|
private async Task DownloadDelta(string previousRelease, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 10 - 50%
|
// 10 - 50%
|
||||||
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
@ -104,20 +103,20 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
|
|
||||||
Status = "Downloading...";
|
Status = "Downloading...";
|
||||||
await using MemoryStream stream = new();
|
await using MemoryStream stream = new();
|
||||||
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/{artifact.ArtifactId}/delta", stream, _stepProgress, cancellationToken);
|
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/{_artifact.ArtifactId}/delta", stream, _stepProgress, cancellationToken);
|
||||||
|
|
||||||
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
await PatchDelta(stream, previousRelease, artifact, cancellationToken);
|
await PatchDelta(stream, previousRelease, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PatchDelta(Stream deltaStream, string previousRelease, IGetReleaseById_PublishedRelease_Artifacts artifact, CancellationToken cancellationToken)
|
private async Task PatchDelta(Stream deltaStream, string previousRelease, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 50 - 60%
|
// 50 - 60%
|
||||||
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(50f + e * 0.1f);
|
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(50f + e * 0.1f);
|
||||||
|
|
||||||
Status = "Patching...";
|
Status = "Patching...";
|
||||||
await using FileStream newFileStream = new(Path.Combine(_dataFolder, $"{_releaseId}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
await using FileStream newFileStream = new(Path.Combine(Constants.UpdatingFolder, $"{_release.Version}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
await using (FileStream baseStream = File.OpenRead(previousRelease))
|
await using (FileStream baseStream = File.OpenRead(previousRelease))
|
||||||
{
|
{
|
||||||
deltaStream.Seek(0, SeekOrigin.Begin);
|
deltaStream.Seek(0, SeekOrigin.Begin);
|
||||||
@ -132,23 +131,23 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
|
|
||||||
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
|
|
||||||
await ValidateArchive(newFileStream, artifact, cancellationToken);
|
await ValidateArchive(newFileStream, cancellationToken);
|
||||||
await Extract(newFileStream, cancellationToken);
|
await Extract(newFileStream, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Download(IGetReleaseById_PublishedRelease_Artifacts artifact, CancellationToken cancellationToken)
|
private async Task Download(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 10 - 60%
|
// 10 - 60%
|
||||||
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(10f + e * 0.5f);
|
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(10f + e * 0.5f);
|
||||||
|
|
||||||
Status = "Downloading...";
|
Status = "Downloading...";
|
||||||
await using FileStream stream = new(Path.Combine(_dataFolder, $"{_releaseId}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
await using FileStream stream = new(Path.Combine(Constants.UpdatingFolder, $"{_release.Version}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/{artifact.ArtifactId}", stream, _stepProgress, cancellationToken);
|
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/{_artifact.ArtifactId}", stream, _stepProgress, cancellationToken);
|
||||||
|
|
||||||
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
|
|
||||||
await ValidateArchive(stream, artifact, cancellationToken);
|
await ValidateArchive(stream, cancellationToken);
|
||||||
await Extract(stream, cancellationToken);
|
await Extract(stream, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +159,7 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
|
|
||||||
Status = "Extracting...";
|
Status = "Extracting...";
|
||||||
// Ensure the directory is empty
|
// Ensure the directory is empty
|
||||||
string extractDirectory = Path.Combine(_dataFolder, "pending");
|
string extractDirectory = Path.Combine(Constants.UpdatingFolder, "pending");
|
||||||
if (Directory.Exists(extractDirectory))
|
if (Directory.Exists(extractDirectory))
|
||||||
Directory.Delete(extractDirectory, true);
|
Directory.Delete(extractDirectory, true);
|
||||||
Directory.CreateDirectory(extractDirectory);
|
Directory.CreateDirectory(extractDirectory);
|
||||||
@ -176,12 +175,12 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidateArchive(Stream archiveStream, IGetReleaseById_PublishedRelease_Artifacts artifact, CancellationToken cancellationToken)
|
private async Task ValidateArchive(Stream archiveStream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using MD5 md5 = MD5.Create();
|
using MD5 md5 = MD5.Create();
|
||||||
archiveStream.Seek(0, SeekOrigin.Begin);
|
archiveStream.Seek(0, SeekOrigin.Begin);
|
||||||
string hash = BitConverter.ToString(await md5.ComputeHashAsync(archiveStream, cancellationToken)).Replace("-", "");
|
string hash = BitConverter.ToString(await md5.ComputeHashAsync(archiveStream, cancellationToken)).Replace("-", "");
|
||||||
if (hash != artifact.FileInfo.Md5Hash)
|
if (hash != _artifact.FileInfo.Md5Hash)
|
||||||
throw new ArtemisUIException($"Update file hash mismatch, expected \"{artifact.FileInfo.Md5Hash}\" but got \"{hash}\"");
|
throw new ArtemisUIException($"Update file hash mismatch, expected \"{_artifact.FileInfo.Md5Hash}\" but got \"{hash}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,6 +7,7 @@ using Artemis.Core;
|
|||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.Storage.Entities.General;
|
using Artemis.Storage.Entities.General;
|
||||||
using Artemis.Storage.Repositories;
|
using Artemis.Storage.Repositories;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
using Artemis.UI.Shared.Services.MainWindow;
|
using Artemis.UI.Shared.Services.MainWindow;
|
||||||
using Artemis.WebClient.Updating;
|
using Artemis.WebClient.Updating;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@ -20,7 +21,7 @@ public class UpdateService : IUpdateService
|
|||||||
private const double UPDATE_CHECK_INTERVAL = 3_600_000; // once per hour
|
private const double UPDATE_CHECK_INTERVAL = 3_600_000; // once per hour
|
||||||
private readonly PluginSetting<bool> _autoCheck;
|
private readonly PluginSetting<bool> _autoCheck;
|
||||||
private readonly PluginSetting<bool> _autoInstall;
|
private readonly PluginSetting<bool> _autoInstall;
|
||||||
private readonly Func<string, ReleaseInstaller> _getReleaseInstaller;
|
private readonly Func<Guid, ReleaseInstaller> _getReleaseInstaller;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IReleaseRepository _releaseRepository;
|
private readonly IReleaseRepository _releaseRepository;
|
||||||
@ -36,7 +37,7 @@ public class UpdateService : IUpdateService
|
|||||||
IUpdatingClient updatingClient,
|
IUpdatingClient updatingClient,
|
||||||
IReleaseRepository releaseRepository,
|
IReleaseRepository releaseRepository,
|
||||||
Lazy<IUpdateNotificationProvider> updateNotificationProvider,
|
Lazy<IUpdateNotificationProvider> updateNotificationProvider,
|
||||||
Func<string, ReleaseInstaller> getReleaseInstaller)
|
Func<Guid, ReleaseInstaller> getReleaseInstaller)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_updatingClient = updatingClient;
|
_updatingClient = updatingClient;
|
||||||
@ -66,39 +67,21 @@ public class UpdateService : IUpdateService
|
|||||||
Timer timer = new(UPDATE_CHECK_INTERVAL);
|
Timer timer = new(UPDATE_CHECK_INTERVAL);
|
||||||
timer.Elapsed += HandleAutoUpdateEvent;
|
timer.Elapsed += HandleAutoUpdateEvent;
|
||||||
timer.Start();
|
timer.Start();
|
||||||
|
|
||||||
_logger.Information("Update service initialized for {Channel} channel", Channel);
|
|
||||||
ProcessReleaseStatus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessReleaseStatus()
|
private void ProcessReleaseStatus()
|
||||||
{
|
{
|
||||||
// If an update is queued, don't bother with anything else
|
string currentVersion = Constants.CurrentVersion;
|
||||||
ReleaseEntity? queued = _releaseRepository.GetQueuedVersion();
|
_releaseRepository.SaveVersionInstallDate(currentVersion);
|
||||||
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.Version);
|
|
||||||
RestartForUpdate(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a different version was installed, mark it as such
|
|
||||||
ReleaseEntity? installed = _releaseRepository.GetInstalledVersion();
|
|
||||||
if (installed?.Version != Constants.CurrentVersion)
|
|
||||||
_releaseRepository.FinishInstallation(Constants.CurrentVersion);
|
|
||||||
|
|
||||||
PreviousVersion = _releaseRepository.GetPreviousInstalledVersion()?.Version;
|
PreviousVersion = _releaseRepository.GetPreviousInstalledVersion()?.Version;
|
||||||
|
|
||||||
if (!Directory.Exists(Path.Combine(Constants.DataFolder, "updating")))
|
if (!Directory.Exists(Constants.UpdatingFolder))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Clean up the update folder, leaving only the last ZIP
|
// Clean up the update folder, leaving only the last ZIP
|
||||||
foreach (string file in Directory.GetFiles(Path.Combine(Constants.DataFolder, "updating")))
|
foreach (string file in Directory.GetFiles(Constants.UpdatingFolder))
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(file) != ".zip")
|
if (Path.GetExtension(file) != ".zip" || Path.GetFileName(file) == $"{currentVersion}.zip")
|
||||||
continue;
|
|
||||||
if (installed != null && Path.GetFileName(file) == $"{installed.ReleaseId}.zip")
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -140,8 +123,13 @@ public class UpdateService : IUpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Channel { get; }
|
public string Channel { get; }
|
||||||
public string? PreviousVersion { get; set; }
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string? PreviousVersion { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; private set; }
|
public IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -158,6 +146,7 @@ public class UpdateService : IUpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<bool> CheckForUpdate()
|
public async Task<bool> CheckForUpdate()
|
||||||
{
|
{
|
||||||
IOperationResult<IGetNextReleaseResult> result = await _updatingClient.GetNextRelease.ExecuteAsync(Constants.CurrentVersion, Channel, _updatePlatform);
|
IOperationResult<IGetNextReleaseResult> result = await _updatingClient.GetNextRelease.ExecuteAsync(Constants.CurrentVersion, Channel, _updatePlatform);
|
||||||
@ -183,13 +172,7 @@ public class UpdateService : IUpdateService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void QueueUpdate(string version, string releaseId)
|
public ReleaseInstaller GetReleaseInstaller(Guid releaseId)
|
||||||
{
|
|
||||||
_releaseRepository.QueueInstallation(version, releaseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ReleaseInstaller GetReleaseInstaller(string releaseId)
|
|
||||||
{
|
{
|
||||||
return _getReleaseInstaller(releaseId);
|
return _getReleaseInstaller(releaseId);
|
||||||
}
|
}
|
||||||
@ -197,7 +180,33 @@ public class UpdateService : IUpdateService
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void RestartForUpdate(bool silent)
|
public void RestartForUpdate(bool silent)
|
||||||
{
|
{
|
||||||
_releaseRepository.DequeueInstallation();
|
if (!Directory.Exists(Path.Combine(Constants.UpdatingFolder, "pending")))
|
||||||
|
throw new ArtemisUIException("Cannot install update, none is pending.");
|
||||||
|
|
||||||
|
Directory.Move(Path.Combine(Constants.UpdatingFolder, "pending"), Path.Combine(Constants.UpdatingFolder, "installing"));
|
||||||
Utilities.ApplyUpdate(silent);
|
Utilities.ApplyUpdate(silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Initialize()
|
||||||
|
{
|
||||||
|
// There should never be an installing folder
|
||||||
|
if (Directory.Exists(Path.Combine(Constants.UpdatingFolder, "installing")))
|
||||||
|
{
|
||||||
|
_logger.Warning("Cleaning up leftover installing folder, did an update go wrong?");
|
||||||
|
Directory.Delete(Path.Combine(Constants.UpdatingFolder, "installing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an update is pending, don't bother with anything else
|
||||||
|
if (Directory.Exists(Path.Combine(Constants.UpdatingFolder, "pending")))
|
||||||
|
{
|
||||||
|
_logger.Information("Installing pending update");
|
||||||
|
RestartForUpdate(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessReleaseStatus();
|
||||||
|
_logger.Information("Update service initialized for {Channel} channel", Channel);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -22,12 +22,14 @@ query GetReleases($branch: String!, $platform: Platform!, $take: Int!, $after: S
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
query GetReleaseById($id: String!) {
|
query GetReleaseById($id: UUID!) {
|
||||||
publishedRelease(id: $id) {
|
publishedRelease(id: $id) {
|
||||||
branch
|
branch
|
||||||
commit
|
commit
|
||||||
version
|
version
|
||||||
previousRelease
|
previousRelease {
|
||||||
|
version
|
||||||
|
}
|
||||||
changelog
|
changelog
|
||||||
artifacts {
|
artifacts {
|
||||||
platform
|
platform
|
||||||
|
|||||||
@ -5,33 +5,6 @@ schema {
|
|||||||
mutation: Mutation
|
mutation: Mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
|
|
||||||
directive @defer(
|
|
||||||
"Deferred when true."
|
|
||||||
if: Boolean,
|
|
||||||
"If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to."
|
|
||||||
label: String
|
|
||||||
) on FRAGMENT_SPREAD | INLINE_FRAGMENT
|
|
||||||
|
|
||||||
"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`."
|
|
||||||
directive @stream(
|
|
||||||
"Streamed when true."
|
|
||||||
if: Boolean,
|
|
||||||
"The initial elements that shall be send down to the consumer."
|
|
||||||
initialCount: Int! = 0,
|
|
||||||
"If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to."
|
|
||||||
label: String
|
|
||||||
) on FIELD
|
|
||||||
|
|
||||||
directive @authorize(
|
|
||||||
"Defines when when the resolver shall be executed.By default the resolver is executed after the policy has determined that the current user is allowed to access the field."
|
|
||||||
apply: ApplyPolicy! = BEFORE_RESOLVER,
|
|
||||||
"The name of the authorization policy that determines access to the annotated resource."
|
|
||||||
policy: String,
|
|
||||||
"Roles that are allowed to access the annotated resource."
|
|
||||||
roles: [String!]
|
|
||||||
) on SCHEMA | OBJECT | FIELD_DEFINITION
|
|
||||||
|
|
||||||
type ArtemisChannel {
|
type ArtemisChannel {
|
||||||
branch: String!
|
branch: String!
|
||||||
releases: Int!
|
releases: Int!
|
||||||
@ -41,15 +14,25 @@ type Artifact {
|
|||||||
artifactId: Long!
|
artifactId: Long!
|
||||||
deltaFileInfo: ArtifactFileInfo!
|
deltaFileInfo: ArtifactFileInfo!
|
||||||
fileInfo: ArtifactFileInfo!
|
fileInfo: ArtifactFileInfo!
|
||||||
|
id: UUID!
|
||||||
platform: Platform!
|
platform: Platform!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArtifactFileInfo {
|
type ArtifactFileInfo {
|
||||||
downloadSize: Long!
|
downloadSize: Long!
|
||||||
downloads: Long!
|
downloads: Long!
|
||||||
|
id: UUID!
|
||||||
md5Hash: String
|
md5Hash: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Information about the offset pagination."
|
||||||
|
type CollectionSegmentInfo {
|
||||||
|
"Indicates whether more items exist following the set defined by the clients arguments."
|
||||||
|
hasNextPage: Boolean!
|
||||||
|
"Indicates whether more items exist prior the set defined by the clients arguments."
|
||||||
|
hasPreviousPage: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
updateReleaseChangelog(input: UpdateReleaseChangelogInput!): UpdateReleaseChangelogPayload!
|
updateReleaseChangelog(input: UpdateReleaseChangelogInput!): UpdateReleaseChangelogPayload!
|
||||||
}
|
}
|
||||||
@ -74,6 +57,7 @@ type PublishedReleasesConnection {
|
|||||||
nodes: [Release!]
|
nodes: [Release!]
|
||||||
"Information to aid in pagination."
|
"Information to aid in pagination."
|
||||||
pageInfo: PageInfo!
|
pageInfo: PageInfo!
|
||||||
|
"Identifies the total count of items in the connection."
|
||||||
totalCount: Int!
|
totalCount: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +74,7 @@ type Query {
|
|||||||
channels: [ArtemisChannel!]!
|
channels: [ArtemisChannel!]!
|
||||||
nextPublishedRelease(branch: String!, platform: Platform!, version: String): Release
|
nextPublishedRelease(branch: String!, platform: Platform!, version: String): Release
|
||||||
publishedChannels: [String!]!
|
publishedChannels: [String!]!
|
||||||
publishedRelease(id: String!): Release
|
publishedRelease(id: UUID!): Release
|
||||||
publishedReleases(
|
publishedReleases(
|
||||||
"Returns the elements in the list that come after the specified cursor."
|
"Returns the elements in the list that come after the specified cursor."
|
||||||
after: String,
|
after: String,
|
||||||
@ -103,20 +87,9 @@ type Query {
|
|||||||
order: [ReleaseSortInput!],
|
order: [ReleaseSortInput!],
|
||||||
where: ReleaseFilterInput
|
where: ReleaseFilterInput
|
||||||
): PublishedReleasesConnection
|
): PublishedReleasesConnection
|
||||||
release(id: String!): Release
|
release(id: UUID!): Release
|
||||||
releaseStatistics(order: [ReleaseStatisticSortInput!], where: ReleaseStatisticFilterInput): [ReleaseStatistic!]!
|
releaseStatistics(order: [ReleaseStatisticSortInput!], where: ReleaseStatisticFilterInput): [ReleaseStatistic!]!
|
||||||
releases(
|
releases(order: [ReleaseSortInput!], skip: Int, take: Int, where: ReleaseFilterInput): ReleasesCollectionSegment
|
||||||
"Returns the elements in the list that come after the specified cursor."
|
|
||||||
after: String,
|
|
||||||
"Returns the elements in the list that come before the specified cursor."
|
|
||||||
before: String,
|
|
||||||
"Returns the first _n_ elements from the list."
|
|
||||||
first: Int,
|
|
||||||
"Returns the last _n_ elements from the list."
|
|
||||||
last: Int,
|
|
||||||
order: [ReleaseSortInput!],
|
|
||||||
where: ReleaseFilterInput
|
|
||||||
): ReleasesConnection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Release {
|
type Release {
|
||||||
@ -125,9 +98,9 @@ type Release {
|
|||||||
changelog: String!
|
changelog: String!
|
||||||
commit: String!
|
commit: String!
|
||||||
createdAt: DateTime!
|
createdAt: DateTime!
|
||||||
id: String!
|
id: UUID!
|
||||||
isDraft: Boolean!
|
isDraft: Boolean!
|
||||||
previousRelease: String
|
previousRelease: Release
|
||||||
version: String!
|
version: String!
|
||||||
workflowRunId: Long!
|
workflowRunId: Long!
|
||||||
}
|
}
|
||||||
@ -136,30 +109,20 @@ type ReleaseStatistic {
|
|||||||
count: Int!
|
count: Int!
|
||||||
lastReportedUsage: DateTime!
|
lastReportedUsage: DateTime!
|
||||||
linuxCount: Int!
|
linuxCount: Int!
|
||||||
oSXCount: Int!
|
osxCount: Int!
|
||||||
releaseId: String!
|
releaseId: UUID!
|
||||||
windowsCount: Int!
|
windowsCount: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
"A connection to a list of items."
|
"A segment of a collection."
|
||||||
type ReleasesConnection {
|
type ReleasesCollectionSegment {
|
||||||
"A list of edges."
|
"A flattened list of the items."
|
||||||
edges: [ReleasesEdge!]
|
items: [Release!]
|
||||||
"A flattened list of the nodes."
|
|
||||||
nodes: [Release!]
|
|
||||||
"Information to aid in pagination."
|
"Information to aid in pagination."
|
||||||
pageInfo: PageInfo!
|
pageInfo: CollectionSegmentInfo!
|
||||||
totalCount: Int!
|
totalCount: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
"An edge in a connection."
|
|
||||||
type ReleasesEdge {
|
|
||||||
"A cursor for use in pagination."
|
|
||||||
cursor: String!
|
|
||||||
"The item at the end of the edge."
|
|
||||||
node: Release!
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateReleaseChangelogPayload {
|
type UpdateReleaseChangelogPayload {
|
||||||
release: Release
|
release: Release
|
||||||
}
|
}
|
||||||
@ -167,6 +130,7 @@ type UpdateReleaseChangelogPayload {
|
|||||||
enum ApplyPolicy {
|
enum ApplyPolicy {
|
||||||
AFTER_RESOLVER
|
AFTER_RESOLVER
|
||||||
BEFORE_RESOLVER
|
BEFORE_RESOLVER
|
||||||
|
VALIDATION
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Platform {
|
enum Platform {
|
||||||
@ -186,19 +150,23 @@ scalar DateTime
|
|||||||
"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1."
|
"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1."
|
||||||
scalar Long
|
scalar Long
|
||||||
|
|
||||||
|
scalar UUID
|
||||||
|
|
||||||
input ArtifactFileInfoFilterInput {
|
input ArtifactFileInfoFilterInput {
|
||||||
and: [ArtifactFileInfoFilterInput!]
|
and: [ArtifactFileInfoFilterInput!]
|
||||||
downloadSize: ComparableInt64OperationFilterInput
|
downloadSize: LongOperationFilterInput
|
||||||
downloads: ComparableInt64OperationFilterInput
|
downloads: LongOperationFilterInput
|
||||||
|
id: UuidOperationFilterInput
|
||||||
md5Hash: StringOperationFilterInput
|
md5Hash: StringOperationFilterInput
|
||||||
or: [ArtifactFileInfoFilterInput!]
|
or: [ArtifactFileInfoFilterInput!]
|
||||||
}
|
}
|
||||||
|
|
||||||
input ArtifactFilterInput {
|
input ArtifactFilterInput {
|
||||||
and: [ArtifactFilterInput!]
|
and: [ArtifactFilterInput!]
|
||||||
artifactId: ComparableInt64OperationFilterInput
|
artifactId: LongOperationFilterInput
|
||||||
deltaFileInfo: ArtifactFileInfoFilterInput
|
deltaFileInfo: ArtifactFileInfoFilterInput
|
||||||
fileInfo: ArtifactFileInfoFilterInput
|
fileInfo: ArtifactFileInfoFilterInput
|
||||||
|
id: UuidOperationFilterInput
|
||||||
or: [ArtifactFilterInput!]
|
or: [ArtifactFilterInput!]
|
||||||
platform: PlatformOperationFilterInput
|
platform: PlatformOperationFilterInput
|
||||||
}
|
}
|
||||||
@ -208,51 +176,36 @@ input BooleanOperationFilterInput {
|
|||||||
neq: Boolean
|
neq: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input ComparableDateTimeOffsetOperationFilterInput {
|
input DateTimeOperationFilterInput {
|
||||||
eq: DateTime
|
eq: DateTime
|
||||||
gt: DateTime
|
gt: DateTime
|
||||||
gte: DateTime
|
gte: DateTime
|
||||||
in: [DateTime!]
|
in: [DateTime]
|
||||||
lt: DateTime
|
lt: DateTime
|
||||||
lte: DateTime
|
lte: DateTime
|
||||||
neq: DateTime
|
neq: DateTime
|
||||||
ngt: DateTime
|
ngt: DateTime
|
||||||
ngte: DateTime
|
ngte: DateTime
|
||||||
nin: [DateTime!]
|
nin: [DateTime]
|
||||||
nlt: DateTime
|
nlt: DateTime
|
||||||
nlte: DateTime
|
nlte: DateTime
|
||||||
}
|
}
|
||||||
|
|
||||||
input ComparableInt32OperationFilterInput {
|
input IntOperationFilterInput {
|
||||||
eq: Int
|
eq: Int
|
||||||
gt: Int
|
gt: Int
|
||||||
gte: Int
|
gte: Int
|
||||||
in: [Int!]
|
in: [Int]
|
||||||
lt: Int
|
lt: Int
|
||||||
lte: Int
|
lte: Int
|
||||||
neq: Int
|
neq: Int
|
||||||
ngt: Int
|
ngt: Int
|
||||||
ngte: Int
|
ngte: Int
|
||||||
nin: [Int!]
|
nin: [Int]
|
||||||
nlt: Int
|
nlt: Int
|
||||||
nlte: Int
|
nlte: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
input ComparableInt64OperationFilterInput {
|
|
||||||
eq: Long
|
|
||||||
gt: Long
|
|
||||||
gte: Long
|
|
||||||
in: [Long!]
|
|
||||||
lt: Long
|
|
||||||
lte: Long
|
|
||||||
neq: Long
|
|
||||||
ngt: Long
|
|
||||||
ngte: Long
|
|
||||||
nin: [Long!]
|
|
||||||
nlt: Long
|
|
||||||
nlte: Long
|
|
||||||
}
|
|
||||||
|
|
||||||
input ListFilterInputTypeOfArtifactFilterInput {
|
input ListFilterInputTypeOfArtifactFilterInput {
|
||||||
all: ArtifactFilterInput
|
all: ArtifactFilterInput
|
||||||
any: Boolean
|
any: Boolean
|
||||||
@ -260,6 +213,21 @@ input ListFilterInputTypeOfArtifactFilterInput {
|
|||||||
some: ArtifactFilterInput
|
some: ArtifactFilterInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input LongOperationFilterInput {
|
||||||
|
eq: Long
|
||||||
|
gt: Long
|
||||||
|
gte: Long
|
||||||
|
in: [Long]
|
||||||
|
lt: Long
|
||||||
|
lte: Long
|
||||||
|
neq: Long
|
||||||
|
ngt: Long
|
||||||
|
ngte: Long
|
||||||
|
nin: [Long]
|
||||||
|
nlt: Long
|
||||||
|
nlte: Long
|
||||||
|
}
|
||||||
|
|
||||||
input PlatformOperationFilterInput {
|
input PlatformOperationFilterInput {
|
||||||
eq: Platform
|
eq: Platform
|
||||||
in: [Platform!]
|
in: [Platform!]
|
||||||
@ -273,13 +241,13 @@ input ReleaseFilterInput {
|
|||||||
branch: StringOperationFilterInput
|
branch: StringOperationFilterInput
|
||||||
changelog: StringOperationFilterInput
|
changelog: StringOperationFilterInput
|
||||||
commit: StringOperationFilterInput
|
commit: StringOperationFilterInput
|
||||||
createdAt: ComparableDateTimeOffsetOperationFilterInput
|
createdAt: DateTimeOperationFilterInput
|
||||||
id: StringOperationFilterInput
|
id: UuidOperationFilterInput
|
||||||
isDraft: BooleanOperationFilterInput
|
isDraft: BooleanOperationFilterInput
|
||||||
or: [ReleaseFilterInput!]
|
or: [ReleaseFilterInput!]
|
||||||
previousRelease: StringOperationFilterInput
|
previousRelease: ReleaseFilterInput
|
||||||
version: StringOperationFilterInput
|
version: StringOperationFilterInput
|
||||||
workflowRunId: ComparableInt64OperationFilterInput
|
workflowRunId: LongOperationFilterInput
|
||||||
}
|
}
|
||||||
|
|
||||||
input ReleaseSortInput {
|
input ReleaseSortInput {
|
||||||
@ -289,27 +257,27 @@ input ReleaseSortInput {
|
|||||||
createdAt: SortEnumType
|
createdAt: SortEnumType
|
||||||
id: SortEnumType
|
id: SortEnumType
|
||||||
isDraft: SortEnumType
|
isDraft: SortEnumType
|
||||||
previousRelease: SortEnumType
|
previousRelease: ReleaseSortInput
|
||||||
version: SortEnumType
|
version: SortEnumType
|
||||||
workflowRunId: SortEnumType
|
workflowRunId: SortEnumType
|
||||||
}
|
}
|
||||||
|
|
||||||
input ReleaseStatisticFilterInput {
|
input ReleaseStatisticFilterInput {
|
||||||
and: [ReleaseStatisticFilterInput!]
|
and: [ReleaseStatisticFilterInput!]
|
||||||
count: ComparableInt32OperationFilterInput
|
count: IntOperationFilterInput
|
||||||
lastReportedUsage: ComparableDateTimeOffsetOperationFilterInput
|
lastReportedUsage: DateTimeOperationFilterInput
|
||||||
linuxCount: ComparableInt32OperationFilterInput
|
linuxCount: IntOperationFilterInput
|
||||||
oSXCount: ComparableInt32OperationFilterInput
|
|
||||||
or: [ReleaseStatisticFilterInput!]
|
or: [ReleaseStatisticFilterInput!]
|
||||||
releaseId: StringOperationFilterInput
|
osxCount: IntOperationFilterInput
|
||||||
windowsCount: ComparableInt32OperationFilterInput
|
releaseId: UuidOperationFilterInput
|
||||||
|
windowsCount: IntOperationFilterInput
|
||||||
}
|
}
|
||||||
|
|
||||||
input ReleaseStatisticSortInput {
|
input ReleaseStatisticSortInput {
|
||||||
count: SortEnumType
|
count: SortEnumType
|
||||||
lastReportedUsage: SortEnumType
|
lastReportedUsage: SortEnumType
|
||||||
linuxCount: SortEnumType
|
linuxCount: SortEnumType
|
||||||
oSXCount: SortEnumType
|
osxCount: SortEnumType
|
||||||
releaseId: SortEnumType
|
releaseId: SortEnumType
|
||||||
windowsCount: SortEnumType
|
windowsCount: SortEnumType
|
||||||
}
|
}
|
||||||
@ -331,6 +299,21 @@ input StringOperationFilterInput {
|
|||||||
|
|
||||||
input UpdateReleaseChangelogInput {
|
input UpdateReleaseChangelogInput {
|
||||||
changelog: String!
|
changelog: String!
|
||||||
id: String!
|
id: UUID!
|
||||||
isDraft: Boolean!
|
isDraft: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input UuidOperationFilterInput {
|
||||||
|
eq: UUID
|
||||||
|
gt: UUID
|
||||||
|
gte: UUID
|
||||||
|
in: [UUID]
|
||||||
|
lt: UUID
|
||||||
|
lte: UUID
|
||||||
|
neq: UUID
|
||||||
|
ngt: UUID
|
||||||
|
ngte: UUID
|
||||||
|
nin: [UUID]
|
||||||
|
nlt: UUID
|
||||||
|
nlte: UUID
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user