1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Updating - Initial client code

This commit is contained in:
Robert 2023-02-05 21:48:56 +01:00
parent 3222eae876
commit a5d2249593
12 changed files with 394 additions and 363 deletions

View File

@ -1,31 +0,0 @@
using System.Threading.Tasks;
namespace Artemis.UI.Shared.Providers;
/// <summary>
/// Represents a provider for custom cursors.
/// </summary>
public interface IUpdateProvider
{
/// <summary>
/// Asynchronously checks whether an update is available.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <returns>A task returning <see langword="true" /> if an update is available; otherwise <see langword="false" />.</returns>
Task<bool> CheckForUpdate(string channel);
/// <summary>
/// Applies any available updates.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <param name="silent">Whether or not to update silently.</param>
Task ApplyUpdate(string channel, bool silent);
/// <summary>
/// Offer to install the update to the user.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <param name="windowOpen">A boolean indicating whether the main window is open.</param>
/// <returns>A task returning <see langword="true" /> if the user chose to update; otherwise <see langword="false" />.</returns>
Task OfferUpdate(string channel, bool windowOpen);
}

View File

@ -1,213 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Exceptions;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.MainWindow;
using Artemis.UI.Windows.Models;
using Artemis.UI.Windows.Screens.Update;
using Avalonia.Threading;
using Flurl;
using Flurl.Http;
using Microsoft.Toolkit.Uwp.Notifications;
using Serilog;
using File = System.IO.File;
namespace Artemis.UI.Windows.Providers;
public class UpdateProvider : IUpdateProvider, IDisposable
{
private const string API_URL = "https://dev.azure.com/artemis-rgb/Artemis/_apis/";
private const string INSTALLER_URL = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe";
private readonly ILogger _logger;
private readonly IMainWindowService _mainWindowService;
private readonly IWindowService _windowService;
public UpdateProvider(ILogger logger, IWindowService windowService, IMainWindowService mainWindowService)
{
_logger = logger;
_windowService = windowService;
_mainWindowService = mainWindowService;
ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated;
}
public async Task<DevOpsBuild?> GetBuildInfo(int buildDefinition, string? buildNumber = null)
{
Url request = API_URL.AppendPathSegments("build", "builds")
.SetQueryParam("definitions", buildDefinition)
.SetQueryParam("resultFilter", "succeeded")
.SetQueryParam("branchName", "refs/heads/master")
.SetQueryParam("$top", 1)
.SetQueryParam("api-version", "6.1-preview.6");
if (buildNumber != null)
request = request.SetQueryParam("buildNumber", buildNumber);
try
{
DevOpsBuilds result = await request.GetJsonAsync<DevOpsBuilds>();
try
{
return result.Builds.FirstOrDefault();
}
catch (Exception e)
{
_logger.Warning(e, "GetBuildInfo: Failed to retrieve build info JSON");
throw;
}
}
catch (FlurlHttpException e)
{
_logger.Warning("GetBuildInfo: Getting build info, request returned {StatusCode}", e.StatusCode);
throw;
}
}
public async Task<GitHubDifference> GetBuildDifferences(DevOpsBuild a, DevOpsBuild b)
{
return await "https://api.github.com"
.AppendPathSegments("repos", "Artemis-RGB", "Artemis", "compare")
.AppendPathSegment(a.SourceVersion + "..." + b.SourceVersion)
.WithHeader("User-Agent", "Artemis 2")
.WithHeader("Accept", "application/vnd.github.v3+json")
.GetJsonAsync<GitHubDifference>();
}
private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e)
{
ToastArguments args = ToastArguments.Parse(e.Argument);
string channel = args.Get("channel");
string action = "view-changes";
if (args.Contains("action"))
action = args.Get("action");
if (action == "install")
await RunInstaller(channel, true);
else if (action == "view-changes")
await Dispatcher.UIThread.InvokeAsync(async () =>
{
_mainWindowService.OpenMainWindow();
await OfferUpdate(channel, true);
});
}
private async Task RunInstaller(string channel, bool silent)
{
_logger.Information("ApplyUpdate: Applying update");
// Ensure the installer is up-to-date, get installer build info
DevOpsBuild? buildInfo = await GetBuildInfo(6);
string installerPath = Path.Combine(Constants.DataFolder, "installer", "Artemis.Installer.exe");
// Always update installer if it is missing ^^
if (!File.Exists(installerPath))
{
await UpdateInstaller();
}
// Compare the creation date of the installer with the build date and update if needed
else
{
if (buildInfo != null && File.GetLastWriteTime(installerPath) < buildInfo.FinishTime)
await UpdateInstaller();
}
_logger.Information("ApplyUpdate: Running installer at {InstallerPath}", installerPath);
try
{
Process.Start(new ProcessStartInfo(installerPath, "-autoupdate")
{
UseShellExecute = true,
Verb = "runas"
});
}
catch (Win32Exception e)
{
if (e.NativeErrorCode == 0x4c7)
_logger.Warning("ApplyUpdate: Operation was cancelled, user likely clicked No in UAC dialog");
else
throw;
}
}
private async Task UpdateInstaller()
{
string installerDirectory = Path.Combine(Constants.DataFolder, "installer");
string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe");
_logger.Information("UpdateInstaller: Downloading installer from {DownloadUrl}", INSTALLER_URL);
using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(INSTALLER_URL);
if (!httpResponseMessage.IsSuccessStatusCode)
throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}");
_logger.Information("UpdateInstaller: Writing installer file to {InstallerPath}", installerPath);
if (File.Exists(installerPath))
File.Delete(installerPath);
Core.Utilities.CreateAccessibleDirectory(installerDirectory);
await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None);
await httpResponseMessage.Content.CopyToAsync(fs);
}
private void ShowDesktopNotification(string channel)
{
new ToastContentBuilder()
.AddArgument("channel", channel)
.AddText("An update is available")
.AddButton(new ToastButton().SetContent("Install").AddArgument("action", "install").SetBackgroundActivation())
.AddButton(new ToastButton().SetContent("View changes").AddArgument("action", "view-changes"))
.Show();
}
/// <inheritdoc />
public void Dispose()
{
ToastNotificationManagerCompat.OnActivated -= ToastNotificationManagerCompatOnOnActivated;
ToastNotificationManagerCompat.Uninstall();
}
/// <inheritdoc />
public async Task<bool> CheckForUpdate(string channel)
{
DevOpsBuild? buildInfo = await GetBuildInfo(1);
if (buildInfo == null)
return false;
double buildNumber = double.Parse(buildInfo.BuildNumber, CultureInfo.InvariantCulture);
string buildNumberDisplay = buildNumber.ToString(CultureInfo.InvariantCulture);
_logger.Information("Latest build is {BuildNumber}, we're running {LocalBuildNumber}", buildNumberDisplay, Constants.BuildInfo.BuildNumberDisplay);
return buildNumber > Constants.BuildInfo.BuildNumber;
}
/// <inheritdoc />
public async Task ApplyUpdate(string channel, bool silent)
{
await RunInstaller(channel, silent);
}
/// <inheritdoc />
public async Task OfferUpdate(string channel, bool windowOpen)
{
if (windowOpen)
{
bool update = await _windowService.ShowDialogAsync<UpdateDialogViewModel, bool>(channel);
if (update)
await RunInstaller(channel, false);
}
else
{
ShowDesktopNotification(channel);
}
}
}

View File

@ -30,6 +30,7 @@
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.1.10" />
<PackageReference Include="Octopus.Octodiff" Version="2.0.100" />
<PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
<PackageReference Include="RGB.NET.Core" Version="1.0.0" />

View File

@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Artemis.UI.Extensions
{
public static class HttpClientProgressExtensions
{
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<float>? progress, CancellationToken cancellationToken)
{
using HttpResponseMessage response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
long? contentLength = response.Content.Headers.ContentLength;
await using Stream download = await response.Content.ReadAsStreamAsync(cancellationToken);
// no progress... no contentLength... very sad
if (progress is null || !contentLength.HasValue)
{
await download.CopyToAsync(destination, cancellationToken);
return;
}
// Such progress and contentLength much reporting Wow!
Progress<long> progressWrapper = new(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
}
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress, CancellationToken cancellationToken)
{
if (bufferSize < 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
if (source is null)
throw new ArgumentNullException(nameof(source));
if (!source.CanRead)
throw new InvalidOperationException($"'{nameof(source)}' is not readable.");
if (destination == null)
throw new ArgumentNullException(nameof(destination));
if (!destination.CanWrite)
throw new InvalidOperationException($"'{nameof(destination)}' is not writable.");
byte[] buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
}
}

View File

@ -0,0 +1,14 @@
using Artemis.Core;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Settings.Updating;
public class UpdateInstallationViewModel : DialogViewModelBase<bool>
{
private readonly string _nextReleaseId;
public UpdateInstallationViewModel(string nextReleaseId)
{
_nextReleaseId = nextReleaseId;
}
}

View File

@ -1,113 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services.MainWindow;
using Avalonia.Threading;
using DryIoc;
using Serilog;
namespace Artemis.UI.Services;
public class UpdateService : IUpdateService
{
private const double UPDATE_CHECK_INTERVAL = 3_600_000; // once per hour
private readonly PluginSetting<bool> _autoUpdate;
private readonly PluginSetting<bool> _checkForUpdates;
private readonly ILogger _logger;
private readonly IMainWindowService _mainWindowService;
private readonly IUpdateProvider? _updateProvider;
public UpdateService(ILogger logger, IContainer container, ISettingsService settingsService, IMainWindowService mainWindowService)
{
_logger = logger;
_mainWindowService = mainWindowService;
if (!Constants.BuildInfo.IsLocalBuild)
_updateProvider = container.Resolve<IUpdateProvider>(IfUnresolved.ReturnDefault);
_checkForUpdates = settingsService.GetSetting("UI.CheckForUpdates", true);
_autoUpdate = settingsService.GetSetting("UI.AutoUpdate", false);
_checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged;
_mainWindowService.MainWindowOpened += WindowServiceOnMainWindowOpened;
Timer timer = new(UPDATE_CHECK_INTERVAL);
timer.Elapsed += TimerOnElapsed;
timer.Start();
}
private async void TimerOnElapsed(object? sender, ElapsedEventArgs e)
{
await AutoUpdate();
}
private async void CheckForUpdatesOnSettingChanged(object? sender, EventArgs e)
{
// Run an auto-update as soon as the setting gets changed to enabled
if (_checkForUpdates.Value)
await AutoUpdate();
}
private async void WindowServiceOnMainWindowOpened(object? sender, EventArgs e)
{
await AutoUpdate();
}
private async Task AutoUpdate()
{
if (_updateProvider == null || !_checkForUpdates.Value || SuspendAutoUpdate)
return;
try
{
bool updateAvailable = await _updateProvider.CheckForUpdate("master");
if (!updateAvailable)
return;
// Only offer it once per session
SuspendAutoUpdate = true;
// If the window is open show the changelog, don't auto-update while the user is busy
if (_mainWindowService.IsMainWindowOpen)
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
// Call OpenMainWindow anyway to focus the main window
_mainWindowService.OpenMainWindow();
await _updateProvider.OfferUpdate("master", true);
});
return;
}
// If the window is closed but auto-update is enabled, update silently
if (_autoUpdate.Value)
await _updateProvider.ApplyUpdate("master", true);
// If auto-update is disabled the update provider can show a notification and handle the rest
else
await _updateProvider.OfferUpdate("master", false);
}
catch (Exception e)
{
_logger.Warning(e, "Auto update failed");
}
}
public bool SuspendAutoUpdate { get; set; }
public bool UpdatingSupported => _updateProvider != null;
public async Task ManualUpdate()
{
if (_updateProvider == null || !_mainWindowService.IsMainWindowOpen)
return;
bool updateAvailable = await _updateProvider.CheckForUpdate("master");
if (!updateAvailable)
return;
await _updateProvider.OfferUpdate("master", true);
}
}

View File

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace Artemis.UI.Services.Updating;
public interface IUpdateNotificationProvider
{
Task ShowNotification(string releaseId);
}

View File

@ -12,7 +12,7 @@ public interface IUpdateService : IArtemisUIService
/// <summary>
/// Gets or sets a boolean indicating whether auto-updating is suspended.
/// </summary>
bool SuspendAutoUpdate { get; set; }
bool SuspendAutoCheck { get; set; }
/// <summary>
/// Manually checks for updates and offers to install it if found.

View File

@ -0,0 +1,142 @@
using System;
using System.Drawing.Drawing2D;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Extensions;
using Artemis.WebClient.Updating;
using NoStringEvaluating.Functions.Math;
using Octodiff.Core;
using Octodiff.Diagnostics;
using Serilog;
using StrawberryShake;
namespace Artemis.UI.Services.Updating;
/// <summary>
/// Represents the installation process of a release
/// </summary>
public class ReleaseInstaller
{
private readonly string _releaseId;
private readonly ILogger _logger;
private readonly IUpdatingClient _updatingClient;
private readonly HttpClient _httpClient;
private readonly Platform _updatePlatform;
private readonly string _dataFolder;
public IProgress<float> OverallProgress { get; } = new Progress<float>();
public IProgress<float> StepProgress { get; } = new Progress<float>();
public ReleaseInstaller(string 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;
if (OperatingSystem.IsLinux())
_updatePlatform = Platform.Linux;
else if (OperatingSystem.IsMacOS())
_updatePlatform = Platform.Osx;
else
throw new PlatformNotSupportedException("Cannot auto update on the current platform");
if (!Directory.Exists(_dataFolder))
Directory.CreateDirectory(_dataFolder);
}
public async Task InstallAsync(CancellationToken cancellationToken)
{
OverallProgress.Report(0);
_logger.Information("Retrieving details for release {ReleaseId}", _releaseId);
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
result.EnsureNoErrors();
IGetReleaseById_Release? release = result.Data?.Release;
if (release == null)
throw new Exception($"Could not find release with ID {_releaseId}");
IGetReleaseById_Release_Artifacts? 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");
OverallProgress.Report(0.1f);
// 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);
else
await Download(artifact, cancellationToken);
}
private async Task DownloadDelta(IGetReleaseById_Release_Artifacts artifact, string previousRelease, CancellationToken cancellationToken)
{
await using MemoryStream stream = new();
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/download/{artifact.ArtifactId}/delta", stream, StepProgress, cancellationToken);
OverallProgress.Report(0.33f);
await PatchDelta(stream, previousRelease, cancellationToken);
}
private async Task PatchDelta(MemoryStream deltaStream, string previousRelease, CancellationToken cancellationToken)
{
await using FileStream baseStream = File.OpenRead(previousRelease);
await using FileStream newFileStream = new(Path.Combine(_dataFolder, $"{_releaseId}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
deltaStream.Seek(0, SeekOrigin.Begin);
DeltaApplier deltaApplier = new();
deltaApplier.Apply(baseStream, new BinaryDeltaReader(deltaStream, new DeltaApplierProgressReporter(StepProgress)), newFileStream);
cancellationToken.ThrowIfCancellationRequested();
OverallProgress.Report(0.66f);
await Extract(newFileStream, cancellationToken);
}
private async Task Download(IGetReleaseById_Release_Artifacts artifact, CancellationToken cancellationToken)
{
await using MemoryStream stream = new();
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/download/{artifact.ArtifactId}", stream, StepProgress, cancellationToken);
OverallProgress.Report(0.5f);
}
private async Task Extract(FileStream archiveStream, CancellationToken cancellationToken)
{
// Ensure the directory is empty
string extractDirectory = Path.Combine(_dataFolder, "pending");
if (Directory.Exists(extractDirectory))
Directory.Delete(extractDirectory, true);
Directory.CreateDirectory(extractDirectory);
archiveStream.Seek(0, SeekOrigin.Begin);
using ZipArchive archive = new(archiveStream);
archive.ExtractToDirectory(extractDirectory);
OverallProgress.Report(1);
}
}
internal class DeltaApplierProgressReporter : IProgressReporter
{
private readonly IProgress<float> _stepProgress;
public DeltaApplierProgressReporter(IProgress<float> stepProgress)
{
_stepProgress = stepProgress;
}
public void ReportProgress(string operation, long currentPosition, long total)
{
_stepProgress.Report((float) currentPosition / total);
}
}

View File

@ -0,0 +1,6 @@
namespace Artemis.UI.Services.Updating;
public class SimpleUpdateNotificationProvider : IUpdateNotificationProvider
{
}

View File

@ -0,0 +1,147 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.Settings.Updating;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Services.Updating;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.MainWindow;
using Artemis.WebClient.Updating;
using Avalonia.Threading;
using DryIoc;
using Serilog;
using StrawberryShake;
using Timer = System.Timers.Timer;
namespace Artemis.UI.Services;
public class UpdateService : IUpdateService
{
private const double UPDATE_CHECK_INTERVAL = 3_600_000; // once per hour
private readonly ILogger _logger;
private readonly IMainWindowService _mainWindowService;
private readonly IWindowService _windowService;
private readonly IUpdatingClient _updatingClient;
private readonly Lazy<IUpdateNotificationProvider> _updateNotificationProvider;
private readonly Func<string, ReleaseInstaller> _getReleaseInstaller;
private readonly Platform _updatePlatform;
private readonly PluginSetting<string> _channel;
private readonly PluginSetting<bool> _autoCheck;
private readonly PluginSetting<bool> _autoInstall;
private bool _suspendAutoCheck;
public UpdateService(ILogger logger,
ISettingsService settingsService,
IMainWindowService mainWindowService,
IWindowService windowService,
IUpdatingClient updatingClient,
Lazy<IUpdateNotificationProvider> updateNotificationProvider,
Func<string, ReleaseInstaller> getReleaseInstaller)
{
_logger = logger;
_mainWindowService = mainWindowService;
_windowService = windowService;
_updatingClient = updatingClient;
_updateNotificationProvider = updateNotificationProvider;
_getReleaseInstaller = getReleaseInstaller;
if (OperatingSystem.IsWindows())
_updatePlatform = Platform.Windows;
if (OperatingSystem.IsLinux())
_updatePlatform = Platform.Linux;
else if (OperatingSystem.IsMacOS())
_updatePlatform = Platform.Osx;
else
throw new PlatformNotSupportedException("Cannot auto update on the current platform");
_channel = settingsService.GetSetting("UI.Updating.Channel", "master");
_autoCheck = settingsService.GetSetting("UI.Updating.AutoCheck", true);
_autoInstall = settingsService.GetSetting("UI.Updating.AutoInstall", false);
_autoCheck.SettingChanged += HandleAutoUpdateEvent;
_mainWindowService.MainWindowOpened += HandleAutoUpdateEvent;
Timer timer = new(UPDATE_CHECK_INTERVAL);
timer.Elapsed += HandleAutoUpdateEvent;
timer.Start();
}
public async Task<bool> CheckForUpdate()
{
string? currentVersion = AssemblyProductVersion;
if (currentVersion == null)
return false;
IOperationResult<IGetNextReleaseResult> result = await _updatingClient.GetNextRelease.ExecuteAsync(currentVersion, _channel.Value, _updatePlatform);
result.EnsureNoErrors();
// No update was found
if (result.Data?.NextRelease == null)
return false;
// Only offer it once per session
_suspendAutoCheck = true;
// If the window is open show the changelog, don't auto-update while the user is busy
if (_mainWindowService.IsMainWindowOpen)
await ShowUpdateDialog(result.Data.NextRelease.Id);
else if (!_autoInstall.Value)
await ShowUpdateNotification(result.Data.NextRelease.Id);
else
await AutoInstallUpdate(result.Data.NextRelease.Id);
return true;
}
private async Task ShowUpdateDialog(string nextReleaseId)
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
// Main window is probably already open but this will bring it into focus
_mainWindowService.OpenMainWindow();
await _windowService.ShowDialogAsync<UpdateInstallationViewModel>(nextReleaseId);
});
}
private async Task ShowUpdateNotification(string nextReleaseId)
{
await _updateNotificationProvider.Value.ShowNotification(nextReleaseId);
}
private async Task AutoInstallUpdate(string nextReleaseId)
{
ReleaseInstaller installer = _getReleaseInstaller(nextReleaseId);
await installer.InstallAsync(CancellationToken.None);
}
private async void HandleAutoUpdateEvent(object? sender, EventArgs e)
{
if (!_autoCheck.Value || _suspendAutoCheck)
return;
try
{
await CheckForUpdate();
}
catch (Exception ex)
{
_logger.Warning(ex, "Auto update failed");
}
}
private static string? AssemblyProductVersion
{
get
{
object[] attributes = typeof(UpdateService).Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
return attributes.Length == 0 ? null : ((AssemblyInformationalVersionAttribute) attributes[0]).InformationalVersion;
}
}
}

View File

@ -1,16 +1,30 @@
query GetReleaseById {
release(id: "63b71dd69a5bb32a0a81a410") {
query GetReleaseById($id: String!) {
release(id: $id) {
branch
commit
version
previousRelease
artifacts {
platform
artifactId
fileInfo {
md5Hash
downloadSize
downloads
...fileInfo
}
deltaFileInfo {
...fileInfo
}
}
}
}
fragment fileInfo on ArtifactFileInfo {
md5Hash
downloadSize
}
query GetNextRelease($currentVersion: String!, $branch: String!, $platform: Platform!) {
nextRelease(version: $currentVersion, branch: $branch, platform: $platform) {
id
version
}
}