diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs
index 15213165d..252d3b85a 100644
--- a/src/Artemis.Core/Constants.cs
+++ b/src/Artemis.Core/Constants.cs
@@ -48,6 +48,10 @@ public static class Constants
/// The full path to the Artemis logs folder
///
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
+ ///
+ /// The full path to the Artemis logs folder
+ ///
+ public static readonly string UpdatingFolder = Path.Combine(DataFolder, "updating");
///
/// The full path to the Artemis plugins folder
diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs
index e5590b568..db6e373a0 100644
--- a/src/Artemis.Core/Utilities/Utilities.cs
+++ b/src/Artemis.Core/Utilities/Utilities.cs
@@ -21,6 +21,7 @@ public static class Utilities
CreateAccessibleDirectory(Constants.DataFolder);
CreateAccessibleDirectory(Constants.PluginsFolder);
CreateAccessibleDirectory(Constants.LayoutsFolder);
+ CreateAccessibleDirectory(Constants.UpdatingFolder);
}
///
diff --git a/src/Artemis.Storage/Entities/General/ReleaseEntity.cs b/src/Artemis.Storage/Entities/General/ReleaseEntity.cs
index 3d10ed71b..7c517ff79 100644
--- a/src/Artemis.Storage/Entities/General/ReleaseEntity.cs
+++ b/src/Artemis.Storage/Entities/General/ReleaseEntity.cs
@@ -7,15 +7,5 @@ public class ReleaseEntity
public Guid Id { get; set; }
public string Version { get; set; }
- public string ReleaseId { get; set; }
- public ReleaseEntityStatus Status { get; set; }
public DateTimeOffset? InstalledAt { get; set; }
-}
-
-public enum ReleaseEntityStatus
-{
- Queued,
- Installed,
- Historical,
- Unknown
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Repositories/ReleaseRepository.cs b/src/Artemis.Storage/Repositories/ReleaseRepository.cs
index 0c2d466dd..3516c6c80 100644
--- a/src/Artemis.Storage/Repositories/ReleaseRepository.cs
+++ b/src/Artemis.Storage/Repositories/ReleaseRepository.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using Artemis.Storage.Entities.General;
using Artemis.Storage.Repositories.Interfaces;
using LiteDB;
@@ -14,63 +13,25 @@ public class ReleaseRepository : IReleaseRepository
{
_repository = repository;
_repository.Database.GetCollection().EnsureIndex(s => s.Version, true);
- _repository.Database.GetCollection().EnsureIndex(s => s.Status);
}
- public ReleaseEntity GetQueuedVersion()
+ public void SaveVersionInstallDate(string version)
{
- return _repository.Query().Where(r => r.Status == ReleaseEntityStatus.Queued).FirstOrDefault();
- }
+ ReleaseEntity release = _repository.Query().Where(r => r.Version == version).FirstOrDefault();
+ if (release != null)
+ return;
- public ReleaseEntity GetInstalledVersion()
- {
- return _repository.Query().Where(r => r.Status == ReleaseEntityStatus.Installed).FirstOrDefault();
+ _repository.Insert(new ReleaseEntity {Version = version, InstalledAt = DateTimeOffset.UtcNow});
}
public ReleaseEntity GetPreviousInstalledVersion()
{
- return _repository.Query().Where(r => r.Status == ReleaseEntityStatus.Historical).OrderByDescending(r => r.InstalledAt).FirstOrDefault();
- }
-
- public void QueueInstallation(string version, string releaseId)
- {
- // Mark release as queued and add if missing
- ReleaseEntity release = _repository.Query().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().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.Historical).ToList();
- foreach (ReleaseEntity oldRelease in oldReleases)
- oldRelease.Status = ReleaseEntityStatus.Historical;
- _repository.Update(oldReleases);
- }
-
- public void DequeueInstallation()
- {
- // Mark all queued releases as unknown, until FinishInstallation is called we don't know the status
- List queuedReleases = _repository.Query().Where(r => r.Status == ReleaseEntityStatus.Queued).ToList();
- foreach (ReleaseEntity queuedRelease in queuedReleases)
- queuedRelease.Status = ReleaseEntityStatus.Unknown;
- _repository.Update(queuedReleases);
+ return _repository.Query().OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault();
}
}
public interface IReleaseRepository : IRepository
{
- ReleaseEntity GetQueuedVersion();
- ReleaseEntity GetInstalledVersion();
+ void SaveVersionInstallDate(string version);
ReleaseEntity GetPreviousInstalledVersion();
- void QueueInstallation(string version, string releaseId);
- void FinishInstallation(string version);
- void DequeueInstallation();
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs
index b2a8db3df..9444327c6 100644
--- a/src/Artemis.UI.Windows/App.axaml.cs
+++ b/src/Artemis.UI.Windows/App.axaml.cs
@@ -43,9 +43,9 @@ public class App : Application
{
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
return;
-
- ArtemisBootstrapper.Initialize();
+
_applicationStateManager = new ApplicationStateManager(_container!, desktop.Args);
+ ArtemisBootstrapper.Initialize();
RegisterProviders(_container!);
}
diff --git a/src/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Artemis.UI.Windows/ApplicationStateManager.cs
index 1a7d8147e..05435e5ac 100644
--- a/src/Artemis.UI.Windows/ApplicationStateManager.cs
+++ b/src/Artemis.UI.Windows/ApplicationStateManager.cs
@@ -99,8 +99,8 @@ public class ApplicationStateManager
argsList.Add("--autorun");
// Retain startup arguments after update by providing them to the script
- string script = $"\"{Path.Combine(Constants.DataFolder, "updating", "pending", "scripts", "update.ps1")}\"";
- string source = $"-sourceDirectory \"{Path.Combine(Constants.DataFolder, "updating", "pending")}\"";
+ string script = $"\"{Path.Combine(Constants.UpdatingFolder, "installing", "scripts", "update.ps1")}\"";
+ string source = $"-sourceDirectory \"{Path.Combine(Constants.UpdatingFolder, "installing")}\"";
string destination = $"-destinationDirectory \"{Constants.ApplicationFolder}\"";
string args = argsList.Any() ? $"-artemisArgs \"{string.Join(',', argsList)}\"" : "";
diff --git a/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs b/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs
index 024844bd6..3b434360d 100644
--- a/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs
+++ b/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs
@@ -9,6 +9,7 @@ using Artemis.UI.Screens.Settings;
using Artemis.UI.Services.Updating;
using Artemis.UI.Shared.Services.MainWindow;
using Avalonia.Threading;
+using DryIoc.ImTools;
using Microsoft.Toolkit.Uwp.Notifications;
using ReactiveUI;
@@ -16,7 +17,7 @@ namespace Artemis.UI.Windows.Providers;
public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
{
- private readonly Func _getReleaseInstaller;
+ private readonly Func _getReleaseInstaller;
private readonly Func _getSettingsViewModel;
private readonly IMainWindowService _mainWindowService;
private readonly IUpdateService _updateService;
@@ -25,7 +26,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
public WindowsUpdateNotificationProvider(IMainWindowService mainWindowService,
IUpdateService updateService,
Func getSettingsViewModel,
- Func getReleaseInstaller)
+ Func getReleaseInstaller)
{
_mainWindowService = mainWindowService;
_updateService = updateService;
@@ -37,7 +38,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e)
{
ToastArguments args = ToastArguments.Parse(e.Argument);
- string releaseId = args.Get("releaseId");
+ Guid releaseId = Guid.Parse(args.Get("releaseId"));
string releaseVersion = args.Get("releaseVersion");
string action = "view-changes";
if (args.Contains("action"))
@@ -53,7 +54,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
_updateService.RestartForUpdate(false);
}
- public void ShowNotification(string releaseId, string releaseVersion)
+ public void ShowNotification(Guid releaseId, string releaseVersion)
{
GetBuilderForRelease(releaseId, releaseVersion)
.AddText("Update available")
@@ -62,10 +63,10 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
.SetContent("Install")
.AddArgument("action", "install").SetAfterActivationBehavior(ToastAfterActivationBehavior.PendingUpdate))
.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(() =>
{
@@ -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);
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"))
.Show(t =>
{
- t.Tag = releaseId;
+ t.Tag = releaseId.ToString();
t.Data = GetDataForInstaller(installer);
});
@@ -127,26 +128,23 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
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)
.AddAudio(new ToastAudio {Silent = true})
.AddText("Update ready")
.AddText($"Artemis version {releaseVersion} is ready to be applied")
.AddButton(new ToastButton().SetContent("Restart Artemis").AddArgument("action", "restart-for-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)
diff --git a/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs b/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs
index 8b3c5fa42..7d3e344aa 100644
--- a/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs
+++ b/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs
@@ -480,7 +480,7 @@ public class ScriptVmFactory : IScriptVmFactory
public interface IReleaseVmFactory : IVmFactory
{
- ReleaseViewModel ReleaseListViewModel(string releaseId, string version, DateTimeOffset createdAt);
+ ReleaseViewModel ReleaseListViewModel(Guid releaseId, string version, DateTimeOffset createdAt);
}
public class ReleaseVmFactory : IReleaseVmFactory
{
@@ -491,7 +491,7 @@ public class ReleaseVmFactory : IReleaseVmFactory
_container = container;
}
- public ReleaseViewModel ReleaseListViewModel(string releaseId, string version, DateTimeOffset createdAt)
+ public ReleaseViewModel ReleaseListViewModel(Guid releaseId, string version, DateTimeOffset createdAt)
{
return _container.Resolve(new object[] { releaseId, version, createdAt });
}
diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs
index 2037d0b1a..373fd22f1 100644
--- a/src/Artemis.UI/Screens/Root/RootViewModel.cs
+++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs
@@ -64,6 +64,9 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
Router.CurrentViewModel.Subscribe(UpdateTitleBarViewModel);
Task.Run(() =>
{
+ if (_updateService.Initialize())
+ return;
+
coreService.Initialize();
registrationService.RegisterBuiltInDataModelDisplays();
registrationService.RegisterBuiltInDataModelInputs();
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs
index c504c4510..8d4c54f1c 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs
@@ -61,7 +61,7 @@ public class ReleasesTabViewModel : ActivatableViewModelBase
public ReadOnlyObservableCollection ReleaseViewModels { get; }
public string Channel { get; }
- public string? PreselectId { get; set; }
+ public Guid? PreselectId { get; set; }
public ReleaseViewModel? SelectedReleaseViewModel
{
diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs b/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs
index 263ff055e..bce5e0181 100644
--- a/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs
@@ -36,7 +36,7 @@ public class ReleaseViewModel : ActivatableViewModelBase
private bool _loading = true;
private bool _retrievedDetails;
- public ReleaseViewModel(string releaseId,
+ public ReleaseViewModel(Guid releaseId,
string version,
DateTimeOffset createdAt,
ILogger logger,
@@ -79,7 +79,7 @@ public class ReleaseViewModel : ActivatableViewModelBase
});
}
- public string ReleaseId { get; }
+ public Guid ReleaseId { get; }
private void ExecuteRestart()
{
@@ -158,7 +158,6 @@ public class ReleaseViewModel : ActivatableViewModelBase
{
InstallationInProgress = true;
await ReleaseInstaller.InstallAsync(_installerCts.Token);
- _updateService.QueueUpdate(Version, ReleaseId);
InstallationFinished = true;
}
catch (Exception e)
diff --git a/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs b/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs
index 9e1268f29..5f77faf60 100644
--- a/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs
+++ b/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs
@@ -1,8 +1,9 @@
+using System;
using System.Threading.Tasks;
namespace Artemis.UI.Services.Updating;
public interface IUpdateNotificationProvider
{
- void ShowNotification(string releaseId, string releaseVersion);
+ void ShowNotification(Guid releaseId, string releaseVersion);
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/Updating/IUpdateService.cs b/src/Artemis.UI/Services/Updating/IUpdateService.cs
index 0da40b1a9..225ecbd97 100644
--- a/src/Artemis.UI/Services/Updating/IUpdateService.cs
+++ b/src/Artemis.UI/Services/Updating/IUpdateService.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading.Tasks;
using Artemis.UI.Services.Interfaces;
using Artemis.WebClient.Updating;
@@ -6,14 +7,47 @@ namespace Artemis.UI.Services.Updating;
public interface IUpdateService : IArtemisUIService
{
+ ///
+ /// Gets the current update channel.
+ ///
string Channel { get; }
+
+ ///
+ /// Gets the version number of the previous release that was installed, if any.
+ ///
string? PreviousVersion { get; }
+
+ ///
+ /// The latest cached release, can be updated by calling .
+ ///
IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; }
+ ///
+ /// Asynchronously caches the latest release.
+ ///
Task CacheLatestRelease();
- Task CheckForUpdate();
- void QueueUpdate(string version, string releaseId);
- ReleaseInstaller GetReleaseInstaller(string releaseId);
+ ///
+ /// Asynchronously checks whether an update is available on the current .
+ ///
+ Task CheckForUpdate();
+
+ ///
+ /// Creates a release installed for a release with the provided ID.
+ ///
+ /// The ID of the release to create the installer for.
+ /// The resulting release installer.
+ ReleaseInstaller GetReleaseInstaller(Guid releaseId);
+
+ ///
+ /// Restarts the application to install a pending update.
+ ///
+ /// A boolean indicating whether to perform a silent install of the update.
void RestartForUpdate(bool silent);
+
+ ///
+ /// Initializes the update service.
+ ///
+ /// A boolean indicating whether a restart will occur to install a pending update.
+ bool Initialize();
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/Updating/InAppUpdateNotificationProvider.cs b/src/Artemis.UI/Services/Updating/InAppUpdateNotificationProvider.cs
index 1a4d066b9..46a06a2ea 100644
--- a/src/Artemis.UI/Services/Updating/InAppUpdateNotificationProvider.cs
+++ b/src/Artemis.UI/Services/Updating/InAppUpdateNotificationProvider.cs
@@ -23,7 +23,7 @@ public class InAppUpdateNotificationProvider : IUpdateNotificationProvider
_getSettingsViewModel = getSettingsViewModel;
}
- private void ShowInAppNotification(string releaseId, string releaseVersion)
+ private void ShowInAppNotification(Guid releaseId, string releaseVersion)
{
_notification?.Invoke();
_notification = _notificationService.CreateNotification()
@@ -35,7 +35,7 @@ public class InAppUpdateNotificationProvider : IUpdateNotificationProvider
.Show();
}
- private void ViewRelease(string releaseId)
+ private void ViewRelease(Guid releaseId)
{
_notification?.Invoke();
@@ -56,7 +56,7 @@ public class InAppUpdateNotificationProvider : IUpdateNotificationProvider
}
///
- public void ShowNotification(string releaseId, string releaseVersion)
+ public void ShowNotification(Guid releaseId, string releaseVersion)
{
if (_mainWindowService.IsMainWindowOpen)
ShowInAppNotification(releaseId, releaseVersion);
diff --git a/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs b/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs
index 78535ac31..04ca4f74f 100644
--- a/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs
+++ b/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs
@@ -22,24 +22,26 @@ namespace Artemis.UI.Services.Updating;
///
public class ReleaseInstaller : CorePropertyChanged
{
- private readonly string _dataFolder;
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
- private readonly string _releaseId;
+ private readonly Guid _releaseId;
private readonly Platform _updatePlatform;
private readonly IUpdatingClient _updatingClient;
private readonly Progress _progress = new();
+
+ private IGetReleaseById_PublishedRelease _release = null!;
+ private IGetReleaseById_PublishedRelease_Artifacts _artifact = null!;
+
private Progress _stepProgress = new();
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;
_logger = logger;
_updatingClient = updatingClient;
_httpClient = httpClient;
- _dataFolder = Path.Combine(Constants.DataFolder, "updating");
if (OperatingSystem.IsWindows())
_updatePlatform = Platform.Windows;
@@ -50,9 +52,6 @@ public class ReleaseInstaller : CorePropertyChanged
else
throw new PlatformNotSupportedException("Cannot auto update on the current platform");
- if (!Directory.Exists(_dataFolder))
- Directory.CreateDirectory(_dataFolder);
-
_progress.ProgressChanged += (_, f) => Progress = f;
}
@@ -64,8 +63,8 @@ public class ReleaseInstaller : CorePropertyChanged
public float Progress
{
- get => _progress1;
- set => SetAndNotify(ref _progress1, value);
+ get => _floatProgress;
+ set => SetAndNotify(ref _floatProgress, value);
}
public async Task InstallAsync(CancellationToken cancellationToken)
@@ -79,24 +78,24 @@ public class ReleaseInstaller : CorePropertyChanged
IOperationResult result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
result.EnsureNoErrors();
- IGetReleaseById_PublishedRelease? release = result.Data?.PublishedRelease;
- if (release == null)
+ _release = result.Data?.PublishedRelease!;
+ if (_release == null)
throw new Exception($"Could not find release with ID {_releaseId}");
- IGetReleaseById_PublishedRelease_Artifacts? artifact = release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform);
- if (artifact == null)
+ _artifact = _release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform)!;
+ if (_artifact == null)
throw new Exception("Found the release but it has no artifact for the current platform");
((IProgress) _progress).Report(10);
// 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)
- await DownloadDelta(artifact, Path.Combine(_dataFolder, $"{release.PreviousRelease}.zip"), cancellationToken);
+ if (_release.PreviousRelease != null && File.Exists(Path.Combine(Constants.UpdatingFolder, $"{_release.PreviousRelease.Version}.zip")) && _artifact.DeltaFileInfo.DownloadSize != 0)
+ await DownloadDelta(Path.Combine(Constants.UpdatingFolder, $"{_release.PreviousRelease.Version}.zip"), cancellationToken);
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%
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
@@ -104,20 +103,20 @@ public class ReleaseInstaller : CorePropertyChanged
Status = "Downloading...";
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;
- 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%
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress) _progress).Report(50f + e * 0.1f);
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))
{
deltaStream.Seek(0, SeekOrigin.Begin);
@@ -132,23 +131,23 @@ public class ReleaseInstaller : CorePropertyChanged
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
- await ValidateArchive(newFileStream, artifact, cancellationToken);
+ await ValidateArchive(newFileStream, cancellationToken);
await Extract(newFileStream, cancellationToken);
}
- private async Task Download(IGetReleaseById_PublishedRelease_Artifacts artifact, CancellationToken cancellationToken)
+ private async Task Download(CancellationToken cancellationToken)
{
// 10 - 60%
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress) _progress).Report(10f + e * 0.5f);
Status = "Downloading...";
- await using FileStream stream = new(Path.Combine(_dataFolder, $"{_releaseId}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
- await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/{artifact.ArtifactId}", stream, _stepProgress, cancellationToken);
+ 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);
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
- await ValidateArchive(stream, artifact, cancellationToken);
+ await ValidateArchive(stream, cancellationToken);
await Extract(stream, cancellationToken);
}
@@ -160,7 +159,7 @@ public class ReleaseInstaller : CorePropertyChanged
Status = "Extracting...";
// Ensure the directory is empty
- string extractDirectory = Path.Combine(_dataFolder, "pending");
+ string extractDirectory = Path.Combine(Constants.UpdatingFolder, "pending");
if (Directory.Exists(extractDirectory))
Directory.Delete(extractDirectory, true);
Directory.CreateDirectory(extractDirectory);
@@ -176,12 +175,12 @@ public class ReleaseInstaller : CorePropertyChanged
_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();
archiveStream.Seek(0, SeekOrigin.Begin);
string hash = BitConverter.ToString(await md5.ComputeHashAsync(archiveStream, cancellationToken)).Replace("-", "");
- if (hash != artifact.FileInfo.Md5Hash)
- throw new ArtemisUIException($"Update file hash mismatch, expected \"{artifact.FileInfo.Md5Hash}\" but got \"{hash}\"");
+ if (hash != _artifact.FileInfo.Md5Hash)
+ throw new ArtemisUIException($"Update file hash mismatch, expected \"{_artifact.FileInfo.Md5Hash}\" but got \"{hash}\"");
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/Updating/UpdateService.cs b/src/Artemis.UI/Services/Updating/UpdateService.cs
index 8e46d20b5..70c53e3f5 100644
--- a/src/Artemis.UI/Services/Updating/UpdateService.cs
+++ b/src/Artemis.UI/Services/Updating/UpdateService.cs
@@ -7,6 +7,7 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.Storage.Entities.General;
using Artemis.Storage.Repositories;
+using Artemis.UI.Exceptions;
using Artemis.UI.Shared.Services.MainWindow;
using Artemis.WebClient.Updating;
using Serilog;
@@ -20,7 +21,7 @@ public class UpdateService : IUpdateService
private const double UPDATE_CHECK_INTERVAL = 3_600_000; // once per hour
private readonly PluginSetting _autoCheck;
private readonly PluginSetting _autoInstall;
- private readonly Func _getReleaseInstaller;
+ private readonly Func _getReleaseInstaller;
private readonly ILogger _logger;
private readonly IReleaseRepository _releaseRepository;
@@ -36,7 +37,7 @@ public class UpdateService : IUpdateService
IUpdatingClient updatingClient,
IReleaseRepository releaseRepository,
Lazy updateNotificationProvider,
- Func getReleaseInstaller)
+ Func getReleaseInstaller)
{
_logger = logger;
_updatingClient = updatingClient;
@@ -66,39 +67,21 @@ public class UpdateService : IUpdateService
Timer timer = new(UPDATE_CHECK_INTERVAL);
timer.Elapsed += HandleAutoUpdateEvent;
timer.Start();
-
- _logger.Information("Update service initialized for {Channel} channel", Channel);
- ProcessReleaseStatus();
}
private void ProcessReleaseStatus()
{
- // If an update is queued, don't bother with anything else
- ReleaseEntity? 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.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);
-
+ string currentVersion = Constants.CurrentVersion;
+ _releaseRepository.SaveVersionInstallDate(currentVersion);
PreviousVersion = _releaseRepository.GetPreviousInstalledVersion()?.Version;
- if (!Directory.Exists(Path.Combine(Constants.DataFolder, "updating")))
+ if (!Directory.Exists(Constants.UpdatingFolder))
return;
// 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")
- continue;
- if (installed != null && Path.GetFileName(file) == $"{installed.ReleaseId}.zip")
+ if (Path.GetExtension(file) != ".zip" || Path.GetFileName(file) == $"{currentVersion}.zip")
continue;
try
@@ -140,8 +123,13 @@ public class UpdateService : IUpdateService
}
}
+ ///
public string Channel { get; }
- public string? PreviousVersion { get; set; }
+
+ ///
+ public string? PreviousVersion { get; private set; }
+
+ ///
public IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; private set; }
///
@@ -158,6 +146,7 @@ public class UpdateService : IUpdateService
}
}
+ ///
public async Task CheckForUpdate()
{
IOperationResult result = await _updatingClient.GetNextRelease.ExecuteAsync(Constants.CurrentVersion, Channel, _updatePlatform);
@@ -183,13 +172,7 @@ public class UpdateService : IUpdateService
}
///
- public void QueueUpdate(string version, string releaseId)
- {
- _releaseRepository.QueueInstallation(version, releaseId);
- }
-
- ///
- public ReleaseInstaller GetReleaseInstaller(string releaseId)
+ public ReleaseInstaller GetReleaseInstaller(Guid releaseId)
{
return _getReleaseInstaller(releaseId);
}
@@ -197,7 +180,33 @@ public class UpdateService : IUpdateService
///
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);
}
+
+ ///
+ 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;
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Updating/Queries/GetReleaseById.graphql b/src/Artemis.WebClient.Updating/Queries/GetReleaseById.graphql
index dff2f5582..e364038ff 100644
--- a/src/Artemis.WebClient.Updating/Queries/GetReleaseById.graphql
+++ b/src/Artemis.WebClient.Updating/Queries/GetReleaseById.graphql
@@ -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) {
branch
commit
version
- previousRelease
+ previousRelease {
+ version
+ }
changelog
artifacts {
platform
diff --git a/src/Artemis.WebClient.Updating/schema.graphql b/src/Artemis.WebClient.Updating/schema.graphql
index 464ffb797..fe855cb6c 100644
--- a/src/Artemis.WebClient.Updating/schema.graphql
+++ b/src/Artemis.WebClient.Updating/schema.graphql
@@ -5,33 +5,6 @@ schema {
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 {
branch: String!
releases: Int!
@@ -41,15 +14,25 @@ type Artifact {
artifactId: Long!
deltaFileInfo: ArtifactFileInfo!
fileInfo: ArtifactFileInfo!
+ id: UUID!
platform: Platform!
}
type ArtifactFileInfo {
downloadSize: Long!
downloads: Long!
+ id: UUID!
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 {
updateReleaseChangelog(input: UpdateReleaseChangelogInput!): UpdateReleaseChangelogPayload!
}
@@ -74,6 +57,7 @@ type PublishedReleasesConnection {
nodes: [Release!]
"Information to aid in pagination."
pageInfo: PageInfo!
+ "Identifies the total count of items in the connection."
totalCount: Int!
}
@@ -90,7 +74,7 @@ type Query {
channels: [ArtemisChannel!]!
nextPublishedRelease(branch: String!, platform: Platform!, version: String): Release
publishedChannels: [String!]!
- publishedRelease(id: String!): Release
+ publishedRelease(id: UUID!): Release
publishedReleases(
"Returns the elements in the list that come after the specified cursor."
after: String,
@@ -103,20 +87,9 @@ type Query {
order: [ReleaseSortInput!],
where: ReleaseFilterInput
): PublishedReleasesConnection
- release(id: String!): Release
+ release(id: UUID!): Release
releaseStatistics(order: [ReleaseStatisticSortInput!], where: ReleaseStatisticFilterInput): [ReleaseStatistic!]!
- releases(
- "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
+ releases(order: [ReleaseSortInput!], skip: Int, take: Int, where: ReleaseFilterInput): ReleasesCollectionSegment
}
type Release {
@@ -125,9 +98,9 @@ type Release {
changelog: String!
commit: String!
createdAt: DateTime!
- id: String!
+ id: UUID!
isDraft: Boolean!
- previousRelease: String
+ previousRelease: Release
version: String!
workflowRunId: Long!
}
@@ -136,30 +109,20 @@ type ReleaseStatistic {
count: Int!
lastReportedUsage: DateTime!
linuxCount: Int!
- oSXCount: Int!
- releaseId: String!
+ osxCount: Int!
+ releaseId: UUID!
windowsCount: Int!
}
-"A connection to a list of items."
-type ReleasesConnection {
- "A list of edges."
- edges: [ReleasesEdge!]
- "A flattened list of the nodes."
- nodes: [Release!]
+"A segment of a collection."
+type ReleasesCollectionSegment {
+ "A flattened list of the items."
+ items: [Release!]
"Information to aid in pagination."
- pageInfo: PageInfo!
+ pageInfo: CollectionSegmentInfo!
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 {
release: Release
}
@@ -167,6 +130,7 @@ type UpdateReleaseChangelogPayload {
enum ApplyPolicy {
AFTER_RESOLVER
BEFORE_RESOLVER
+ VALIDATION
}
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."
scalar Long
+scalar UUID
+
input ArtifactFileInfoFilterInput {
and: [ArtifactFileInfoFilterInput!]
- downloadSize: ComparableInt64OperationFilterInput
- downloads: ComparableInt64OperationFilterInput
+ downloadSize: LongOperationFilterInput
+ downloads: LongOperationFilterInput
+ id: UuidOperationFilterInput
md5Hash: StringOperationFilterInput
or: [ArtifactFileInfoFilterInput!]
}
input ArtifactFilterInput {
and: [ArtifactFilterInput!]
- artifactId: ComparableInt64OperationFilterInput
+ artifactId: LongOperationFilterInput
deltaFileInfo: ArtifactFileInfoFilterInput
fileInfo: ArtifactFileInfoFilterInput
+ id: UuidOperationFilterInput
or: [ArtifactFilterInput!]
platform: PlatformOperationFilterInput
}
@@ -208,51 +176,36 @@ input BooleanOperationFilterInput {
neq: Boolean
}
-input ComparableDateTimeOffsetOperationFilterInput {
+input DateTimeOperationFilterInput {
eq: DateTime
gt: DateTime
gte: DateTime
- in: [DateTime!]
+ in: [DateTime]
lt: DateTime
lte: DateTime
neq: DateTime
ngt: DateTime
ngte: DateTime
- nin: [DateTime!]
+ nin: [DateTime]
nlt: DateTime
nlte: DateTime
}
-input ComparableInt32OperationFilterInput {
+input IntOperationFilterInput {
eq: Int
gt: Int
gte: Int
- in: [Int!]
+ in: [Int]
lt: Int
lte: Int
neq: Int
ngt: Int
ngte: Int
- nin: [Int!]
+ nin: [Int]
nlt: 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 {
all: ArtifactFilterInput
any: Boolean
@@ -260,6 +213,21 @@ input ListFilterInputTypeOfArtifactFilterInput {
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 {
eq: Platform
in: [Platform!]
@@ -273,13 +241,13 @@ input ReleaseFilterInput {
branch: StringOperationFilterInput
changelog: StringOperationFilterInput
commit: StringOperationFilterInput
- createdAt: ComparableDateTimeOffsetOperationFilterInput
- id: StringOperationFilterInput
+ createdAt: DateTimeOperationFilterInput
+ id: UuidOperationFilterInput
isDraft: BooleanOperationFilterInput
or: [ReleaseFilterInput!]
- previousRelease: StringOperationFilterInput
+ previousRelease: ReleaseFilterInput
version: StringOperationFilterInput
- workflowRunId: ComparableInt64OperationFilterInput
+ workflowRunId: LongOperationFilterInput
}
input ReleaseSortInput {
@@ -289,27 +257,27 @@ input ReleaseSortInput {
createdAt: SortEnumType
id: SortEnumType
isDraft: SortEnumType
- previousRelease: SortEnumType
+ previousRelease: ReleaseSortInput
version: SortEnumType
workflowRunId: SortEnumType
}
input ReleaseStatisticFilterInput {
and: [ReleaseStatisticFilterInput!]
- count: ComparableInt32OperationFilterInput
- lastReportedUsage: ComparableDateTimeOffsetOperationFilterInput
- linuxCount: ComparableInt32OperationFilterInput
- oSXCount: ComparableInt32OperationFilterInput
+ count: IntOperationFilterInput
+ lastReportedUsage: DateTimeOperationFilterInput
+ linuxCount: IntOperationFilterInput
or: [ReleaseStatisticFilterInput!]
- releaseId: StringOperationFilterInput
- windowsCount: ComparableInt32OperationFilterInput
+ osxCount: IntOperationFilterInput
+ releaseId: UuidOperationFilterInput
+ windowsCount: IntOperationFilterInput
}
input ReleaseStatisticSortInput {
count: SortEnumType
lastReportedUsage: SortEnumType
linuxCount: SortEnumType
- oSXCount: SortEnumType
+ osxCount: SortEnumType
releaseId: SortEnumType
windowsCount: SortEnumType
}
@@ -331,6 +299,21 @@ input StringOperationFilterInput {
input UpdateReleaseChangelogInput {
changelog: String!
- id: String!
+ id: UUID!
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
+}