1
0
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:
Robert 2023-03-06 21:58:44 +01:00
parent 09a2f769c3
commit 4a8845e578
18 changed files with 237 additions and 253 deletions

View File

@ -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

View File

@ -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>

View File

@ -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
}

View File

@ -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();
} }

View File

@ -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!);
} }

View File

@ -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)}\"" : "";

View File

@ -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)

View File

@ -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 });
} }

View File

@ -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();

View File

@ -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
{ {

View File

@ -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)

View File

@ -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);
} }

View File

@ -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();
} }

View File

@ -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);

View File

@ -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}\"");
} }
} }

View File

@ -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;
}
} }

View File

@ -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

View File

@ -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
}