diff --git a/src/Artemis.Core/Events/UpdateEventArgs.cs b/src/Artemis.Core/Events/UpdateEventArgs.cs
new file mode 100644
index 000000000..d513bae5e
--- /dev/null
+++ b/src/Artemis.Core/Events/UpdateEventArgs.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+
+namespace Artemis.Core;
+
+///
+/// Provides data about application update events
+///
+public class UpdateEventArgs : EventArgs
+{
+ internal UpdateEventArgs(bool silent)
+ {
+ Silent = silent;
+ }
+
+ ///
+ /// Gets a boolean indicating whether to silently update or not.
+ ///
+ public bool Silent { get; }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index 6de473234..e313ca59d 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -84,7 +84,20 @@ internal class PluginManagementService : IPluginManagementService
foreach (FileInfo zipFile in builtInPluginDirectory.EnumerateFiles("*.zip"))
{
- // Find the metadata file in the zip
+ try
+ {
+ ExtractBuiltInPlugin(zipFile, pluginDirectory);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Failed to copy built-in plugin from {ZipFile}", zipFile.FullName);
+ }
+ }
+ }
+
+ private void ExtractBuiltInPlugin(FileInfo zipFile, DirectoryInfo pluginDirectory)
+ {
+ // Find the metadata file in the zip
using ZipArchive archive = ZipFile.OpenRead(zipFile.FullName);
ZipArchiveEntry? metaDataFileEntry = archive.GetEntry("plugin.json");
if (metaDataFileEntry == null)
@@ -135,7 +148,6 @@ internal class PluginManagementService : IPluginManagementService
}
}
}
- }
}
#endregion
diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs
index bbb38c4be..e5590b568 100644
--- a/src/Artemis.Core/Utilities/Utilities.cs
+++ b/src/Artemis.Core/Utilities/Utilities.cs
@@ -50,6 +50,15 @@ public static class Utilities
OnRestartRequested(new RestartEventArgs(elevate, delay, extraArgs.ToList()));
}
+ ///
+ /// Applies a pending update
+ ///
+ /// A boolean indicating whether to silently update or not.
+ public static void ApplyUpdate(bool silent)
+ {
+ OnUpdateRequested(new UpdateEventArgs(silent));
+ }
+
///
/// Opens the provided URL in the default web browser
///
@@ -96,11 +105,16 @@ public static class Utilities
/// Occurs when the core has requested an application shutdown
///
public static event EventHandler? ShutdownRequested;
-
+
///
/// Occurs when the core has requested an application restart
///
public static event EventHandler? RestartRequested;
+
+ ///
+ /// Occurs when the core has requested a pending application update to be applied
+ ///
+ public static event EventHandler? UpdateRequested;
///
/// Opens the provided folder in the user's file explorer
@@ -136,6 +150,11 @@ public static class Utilities
{
ShutdownRequested?.Invoke(null, EventArgs.Empty);
}
+
+ private static void OnUpdateRequested(UpdateEventArgs e)
+ {
+ UpdateRequested?.Invoke(null, e);
+ }
#region Scaling
diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs
index 87bc97b76..78fcc3643 100644
--- a/src/Artemis.Storage/StorageManager.cs
+++ b/src/Artemis.Storage/StorageManager.cs
@@ -30,7 +30,7 @@ public static class StorageManager
{
FileSystemInfo newest = files.OrderByDescending(fi => fi.CreationTime).First();
FileSystemInfo oldest = files.OrderBy(fi => fi.CreationTime).First();
- if (DateTime.Now - newest.CreationTime < TimeSpan.FromMinutes(10))
+ if (DateTime.Now - newest.CreationTime < TimeSpan.FromHours(12))
return;
oldest.Delete();
diff --git a/src/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Artemis.UI.Windows/ApplicationStateManager.cs
index a14b50248..2eb3ce47f 100644
--- a/src/Artemis.UI.Windows/ApplicationStateManager.cs
+++ b/src/Artemis.UI.Windows/ApplicationStateManager.cs
@@ -17,7 +17,7 @@ namespace Artemis.UI.Windows;
public class ApplicationStateManager
{
private const int SM_SHUTTINGDOWN = 0x2000;
-
+
public ApplicationStateManager(IContainer container, string[] startupArguments)
{
StartupArguments = startupArguments;
@@ -25,6 +25,7 @@ public class ApplicationStateManager
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
+ Core.Utilities.UpdateRequested += UtilitiesOnUpdateRequested;
// On Windows shutdown dispose the IOC container just so device providers get a chance to clean up
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
@@ -91,6 +92,33 @@ public class ApplicationStateManager
Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
}
+ private void UtilitiesOnUpdateRequested(object? sender, UpdateEventArgs e)
+ {
+ List argsList = new(StartupArguments);
+ if (e.Silent)
+ 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 destination = $"-destinationDirectory \"{Constants.ApplicationFolder}\"";
+ string args = argsList.Any() ? $"-artemisArgs \"{string.Join(',', argsList)}\"" : "";
+
+ // Run the PowerShell script included in the new version, that way any changes made to the script are used
+ ProcessStartInfo info = new()
+ {
+ Arguments = $"-File {script} {source} {destination} {args}",
+ WindowStyle = ProcessWindowStyle.Hidden,
+ CreateNoWindow = true,
+ FileName = "PowerShell.exe"
+ };
+ Process.Start(info);
+
+ // Lets try a graceful shutdown, PowerShell will kill if needed
+ if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
+ Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
+ }
+
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
{
// Use PowerShell to kill the process after 8 sec just in case
@@ -115,7 +143,7 @@ public class ApplicationStateManager
};
Process.Start(info);
}
-
+
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj
index 74ef13f12..96d6d57fb 100644
--- a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj
+++ b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj
@@ -12,20 +12,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ PreserveNewest
+
application.ico
diff --git a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs
index 000931d92..4b3bf7fda 100644
--- a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs
+++ b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs
@@ -20,7 +20,6 @@ public static class UIContainerExtensions
{
container.Register(Reuse.Singleton);
container.Register(Reuse.Singleton);
- container.Register(Reuse.Singleton);
container.Register();
container.Register(serviceKey: WindowsInputProvider.Id);
}
diff --git a/src/Artemis.UI.Windows/Screens/Update/UpdateDialogView.axaml b/src/Artemis.UI.Windows/Screens/Update/UpdateDialogView.axaml
deleted file mode 100644
index 481709577..000000000
--- a/src/Artemis.UI.Windows/Screens/Update/UpdateDialogView.axaml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
- A new Artemis update is available! 🥳
-
-
-
- Retrieving changes...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Changelog (auto-generated)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We couldn't retrieve any changes
- View online
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Artemis.UI.Windows/Screens/Update/UpdateDialogView.axaml.cs b/src/Artemis.UI.Windows/Screens/Update/UpdateDialogView.axaml.cs
deleted file mode 100644
index 9c33b35b8..000000000
--- a/src/Artemis.UI.Windows/Screens/Update/UpdateDialogView.axaml.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Artemis.UI.Shared;
-using Avalonia;
-using Avalonia.Markup.Xaml;
-
-namespace Artemis.UI.Windows.Screens.Update;
-
-public class UpdateDialogView : ReactiveCoreWindow
-{
- public UpdateDialogView()
- {
- InitializeComponent();
-#if DEBUG
- this.AttachDevTools();
-#endif
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.UI.Windows/Screens/Update/UpdateDialogViewModel.cs b/src/Artemis.UI.Windows/Screens/Update/UpdateDialogViewModel.cs
deleted file mode 100644
index a19aecc6a..000000000
--- a/src/Artemis.UI.Windows/Screens/Update/UpdateDialogViewModel.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Disposables;
-using System.Threading.Tasks;
-using Artemis.Core;
-using Artemis.UI.Shared;
-using Artemis.UI.Shared.Providers;
-using Artemis.UI.Shared.Services;
-using Artemis.UI.Shared.Services.Builders;
-using Artemis.UI.Windows.Models;
-using Artemis.UI.Windows.Providers;
-using Avalonia.Threading;
-using DynamicData;
-using ReactiveUI;
-
-namespace Artemis.UI.Windows.Screens.Update;
-
-public class UpdateDialogViewModel : DialogViewModelBase
-{
- // Based on https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#skipping-ci-for-individual-commits
- private readonly string[] _excludedCommitMessages =
- {
- "[skip ci]",
- "[ci skip]",
- "skip-checks: true",
- "skip-checks:true",
- "[skip azurepipelines]",
- "[azurepipelines skip]",
- "[skip azpipelines]",
- "[azpipelines skip]",
- "[skip azp]",
- "[azp skip]",
- "***NO_CI***"
- };
-
- private readonly INotificationService _notificationService;
- private readonly UpdateProvider _updateProvider;
- private bool _hasChanges;
- private string? _latestBuild;
-
- private bool _retrievingChanges;
-
- public UpdateDialogViewModel(string channel, IUpdateProvider updateProvider, INotificationService notificationService)
- {
- _updateProvider = (UpdateProvider) updateProvider;
- _notificationService = notificationService;
-
- Channel = channel;
- CurrentBuild = Constants.BuildInfo.BuildNumberDisplay;
-
- this.WhenActivated((CompositeDisposable _) => Dispatcher.UIThread.InvokeAsync(GetBuildChanges));
- Install = ReactiveCommand.Create(() => Close(true));
- AskLater = ReactiveCommand.Create(() => Close(false));
- }
-
- public ReactiveCommand Install { get; }
- public ReactiveCommand AskLater { get; }
-
- public string Channel { get; }
- public string CurrentBuild { get; }
-
- public ObservableCollection Changes { get; } = new();
-
- public bool RetrievingChanges
- {
- get => _retrievingChanges;
- set => RaiseAndSetIfChanged(ref _retrievingChanges, value);
- }
-
- public bool HasChanges
- {
- get => _hasChanges;
- set => RaiseAndSetIfChanged(ref _hasChanges, value);
- }
-
- public string? LatestBuild
- {
- get => _latestBuild;
- set => RaiseAndSetIfChanged(ref _latestBuild, value);
- }
-
- private async Task GetBuildChanges()
- {
- try
- {
- RetrievingChanges = true;
- Task currentTask = _updateProvider.GetBuildInfo(1, CurrentBuild);
- Task latestTask = _updateProvider.GetBuildInfo(1);
-
- DevOpsBuild? current = await currentTask;
- DevOpsBuild? latest = await latestTask;
-
- LatestBuild = latest?.BuildNumber;
- if (current != null && latest != null)
- {
- GitHubDifference difference = await _updateProvider.GetBuildDifferences(current, latest);
-
- // Only take commits with one parents (no merges)
- Changes.Clear();
- Changes.AddRange(difference.Commits.Where(c => c.Parents.Count == 1)
- .SelectMany(c => c.Commit.Message.Split("\n"))
- .Select(m => m.Trim())
- .Where(m => !string.IsNullOrWhiteSpace(m) && !_excludedCommitMessages.Contains(m))
- .OrderBy(m => m)
- );
- HasChanges = Changes.Any();
- }
- }
- catch (Exception e)
- {
- _notificationService.CreateNotification().WithTitle("Failed to retrieve build changes").WithMessage(e.Message).WithSeverity(NotificationSeverity.Error).Show();
- }
- finally
- {
- RetrievingChanges = false;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.UI.Windows/Scripts/update.ps1 b/src/Artemis.UI.Windows/Scripts/update.ps1
new file mode 100644
index 000000000..4247178dc
--- /dev/null
+++ b/src/Artemis.UI.Windows/Scripts/update.ps1
@@ -0,0 +1,42 @@
+param (
+ [Parameter(Mandatory=$true)][string]$sourceDirectory,
+ [Parameter(Mandatory=$true)][string]$destinationDirectory,
+ [Parameter(Mandatory=$false)][string]$artemisArgs
+)
+
+# Wait up to 10 seconds for the process to shut down
+for ($i=1; $i -le 10; $i++) {
+ $process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue
+ if (!$process) {
+ break
+ }
+ Write-Host "Waiting for Artemis to shut down ($i / 10)"
+ Start-Sleep -Seconds 1
+}
+
+# If the process is still running, kill it
+$process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue
+if ($process) {
+ Stop-Process -Id $process.Id -Force
+ Start-Sleep -Seconds 1
+}
+
+# Check if the destination directory exists
+if (!(Test-Path $destinationDirectory)) {
+ Write-Error "The destination directory does not exist"
+}
+
+# If the destination directory exists, clear it
+Get-ChildItem $destinationDirectory | Remove-Item -Recurse -Force
+
+# Move the contents of the source directory to the destination directory
+Get-ChildItem $sourceDirectory | Move-Item -Destination $destinationDirectory
+
+Start-Sleep -Seconds 1
+
+# When finished, run the updated version
+if ($artemisArgs) {
+ Start-Process -FilePath "$destinationDirectory\Artemis.UI.Windows.exe" -WorkingDirectory $destinationDirectory -ArgumentList $artemisArgs
+} else {
+ Start-Process -FilePath "$destinationDirectory\Artemis.UI.Windows.exe" -WorkingDirectory $destinationDirectory
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj
index 101b1ac8a..086380658 100644
--- a/src/Artemis.UI/Artemis.UI.csproj
+++ b/src/Artemis.UI/Artemis.UI.csproj
@@ -29,6 +29,7 @@
+
diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml
index 8407e5714..183116c26 100644
--- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml
+++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml
@@ -27,7 +27,8 @@
Width="200"
VerticalAlignment="Center"
Items="{CompiledBinding Descriptors}"
- SelectedItem="{CompiledBinding SelectedDescriptor}">
+ SelectedItem="{CompiledBinding SelectedDescriptor}"
+ PlaceholderText="Please select a brush">
diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
index 26c548dca..8f9ae6277 100644
--- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
+++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
@@ -59,7 +59,7 @@ public class BrushPropertyInputViewModel : PropertyInputViewModel
protected override void ApplyInputValue()
{
- if (LayerProperty.ProfileElement is not Layer layer || layer.LayerBrush == null || SelectedDescriptor == null)
+ if (LayerProperty.ProfileElement is not Layer layer || SelectedDescriptor == null)
return;
_profileEditorService.ExecuteCommand(new ChangeLayerBrush(layer, SelectedDescriptor));
diff --git a/src/Artemis.UI/DryIoc/ContainerExtensions.cs b/src/Artemis.UI/DryIoc/ContainerExtensions.cs
index f4c484bec..58970b2fd 100644
--- a/src/Artemis.UI/DryIoc/ContainerExtensions.cs
+++ b/src/Artemis.UI/DryIoc/ContainerExtensions.cs
@@ -4,6 +4,7 @@ using Artemis.UI.DryIoc.InstanceProviders;
using Artemis.UI.Screens;
using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Services.Interfaces;
+using Artemis.UI.Services.Updating;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.ProfileEditor;
@@ -36,6 +37,7 @@ public static class UIContainerExtensions
container.Register(Reuse.Singleton);
container.Register(Reuse.Singleton);
+ container.Register();
container.RegisterMany(thisAssembly, type => type.IsAssignableTo(), Reuse.Singleton);
}
diff --git a/src/Artemis.UI/Extensions/CompositeDisposableExtensions.cs b/src/Artemis.UI/Extensions/CompositeDisposableExtensions.cs
new file mode 100644
index 000000000..743035d3e
--- /dev/null
+++ b/src/Artemis.UI/Extensions/CompositeDisposableExtensions.cs
@@ -0,0 +1,14 @@
+using System.Reactive.Disposables;
+using System.Threading;
+
+namespace Artemis.UI.Extensions;
+
+public static class CompositeDisposableExtensions
+{
+ public static CancellationToken AsCancellationToken(this CompositeDisposable disposable)
+ {
+ CancellationTokenSource tokenSource = new();
+ Disposable.Create(tokenSource, s => s.Cancel()).DisposeWith(disposable);
+ return tokenSource.Token;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Extensions/ZipArchiveExtensions.cs b/src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
new file mode 100644
index 000000000..0d1fc507d
--- /dev/null
+++ b/src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
@@ -0,0 +1,74 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Threading;
+
+namespace Artemis.UI.Extensions;
+
+// Taken from System.IO.Compression with progress reporting slapped on top
+public static class ZipArchiveExtensions
+{
+ ///
+ /// Extracts all the files in the zip archive to a directory on the file system.
+ ///
+ /// The zip archive to extract files from.
+ /// The path to the directory to place the extracted files in. You can specify either a relative or an absolute path. A relative path is interpreted as relative to the current working directory.
+ /// A boolean indicating whether to override existing files
+ /// The progress to report to.
+ /// A cancellation token
+ public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, IProgress progress, CancellationToken cancellationToken)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ if (destinationDirectoryName == null)
+ throw new ArgumentNullException(nameof(destinationDirectoryName));
+
+ for (int index = 0; index < source.Entries.Count; index++)
+ {
+ ZipArchiveEntry entry = source.Entries[index];
+ entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles);
+ progress.Report((index + 1f) / source.Entries.Count * 100f);
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ }
+
+ private static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ if (destinationDirectoryName == null)
+ throw new ArgumentNullException(nameof(destinationDirectoryName));
+
+ // Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists:
+ DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
+ string destinationDirectoryFullPath = di.FullName;
+ if (!destinationDirectoryFullPath.EndsWith(Path.DirectorySeparatorChar))
+ destinationDirectoryFullPath += Path.DirectorySeparatorChar;
+
+ string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, source.FullName));
+
+ if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, StringComparison))
+ throw new IOException($"The file '{fileDestinationPath}' already exists.");
+
+ if (Path.GetFileName(fileDestinationPath).Length == 0)
+ {
+ // If it is a directory:
+
+ if (source.Length != 0)
+ throw new IOException("Extracting Zip entry would have resulted in a file outside the specified destination directory.");
+
+ Directory.CreateDirectory(fileDestinationPath);
+ }
+ else
+ {
+ // If it is a file:
+ // Create containing directory:
+ Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!);
+ source.ExtractToFile(fileDestinationPath, overwrite: overwrite);
+ }
+ }
+ private static StringComparison StringComparison => IsCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
+ private static bool IsCaseSensitive => !(OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS());
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs
index b2ba9e8cd..fbd81e943 100644
--- a/src/Artemis.UI/Screens/Root/RootViewModel.cs
+++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs
@@ -7,6 +7,7 @@ using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Models;
using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Services.Interfaces;
+using Artemis.UI.Services.Updating;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.MainWindow;
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml
index d8cd0ff65..953f0ac1c 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml
+++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml
@@ -137,7 +137,7 @@
-
+
Updating
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs
index 06d212fa1..9d3aa0d1b 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs
@@ -12,6 +12,7 @@ using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services.Interfaces;
+using Artemis.UI.Services.Updating;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
@@ -30,6 +31,7 @@ public class GeneralTabViewModel : ActivatableViewModelBase
private readonly PluginSetting _defaultLayerBrushDescriptor;
private readonly ISettingsService _settingsService;
private readonly IUpdateService _updateService;
+ private readonly INotificationService _notificationService;
private readonly IWindowService _windowService;
private bool _startupWizardOpen;
@@ -38,13 +40,15 @@ public class GeneralTabViewModel : ActivatableViewModelBase
IPluginManagementService pluginManagementService,
IDebugService debugService,
IWindowService windowService,
- IUpdateService updateService)
+ IUpdateService updateService,
+ INotificationService notificationService)
{
DisplayName = "General";
_settingsService = settingsService;
_debugService = debugService;
_windowService = windowService;
_updateService = updateService;
+ _notificationService = notificationService;
_autoRunProvider = container.Resolve(IfUnresolved.ReturnDefault);
List layerBrushProviders = pluginManagementService.GetFeaturesOfType();
@@ -88,7 +92,6 @@ public class GeneralTabViewModel : ActivatableViewModelBase
public ReactiveCommand ShowDataFolder { get; }
public bool IsAutoRunSupported => _autoRunProvider != null;
- public bool IsUpdatingSupported => _updateService.UpdatingSupported;
public ObservableCollection LayerBrushDescriptors { get; }
public ObservableCollection GraphicsContexts { get; }
@@ -142,8 +145,8 @@ public class GeneralTabViewModel : ActivatableViewModelBase
public PluginSetting UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
- public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
- public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.AutoUpdate", false);
+ public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true);
+ public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", false);
public PluginSetting ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
public PluginSetting CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
@@ -159,7 +162,14 @@ public class GeneralTabViewModel : ActivatableViewModelBase
private async Task ExecuteCheckForUpdate(CancellationToken cancellationToken)
{
- await _updateService.ManualUpdate();
+ // If an update was available a popup was shown, no need to continue
+ if (await _updateService.CheckForUpdate())
+ return;
+
+ _notificationService.CreateNotification()
+ .WithTitle("No update available")
+ .WithMessage("You are running the latest version in your current channel")
+ .Show();
}
private async Task ExecuteShowSetupWizard()
diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableView.axaml b/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableView.axaml
new file mode 100644
index 000000000..acf70d5b4
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableView.axaml
@@ -0,0 +1,216 @@
+
+
+
+
+ A new Artemis update is available! 🥳
+
+
+
+ Retrieving release...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableView.axaml.cs b/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableView.axaml.cs
new file mode 100644
index 000000000..ed0373da4
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableView.axaml.cs
@@ -0,0 +1,29 @@
+using Artemis.UI.Shared;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.Settings.Updating;
+
+public partial class ReleaseAvailableView : ReactiveCoreWindow
+{
+ public ReleaseAvailableView()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void Button_OnClick(object? sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableViewModel.cs b/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableViewModel.cs
new file mode 100644
index 000000000..118177c7f
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseAvailableViewModel.cs
@@ -0,0 +1,76 @@
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Artemis.Core;
+using Artemis.UI.Extensions;
+using Artemis.UI.Services.Updating;
+using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services;
+using Artemis.WebClient.Updating;
+using ReactiveUI;
+using Serilog;
+using StrawberryShake;
+
+namespace Artemis.UI.Screens.Settings.Updating;
+
+public class ReleaseAvailableViewModel : ActivatableViewModelBase
+{
+ private readonly string _nextReleaseId;
+ private readonly ILogger _logger;
+ private readonly IUpdateService _updateService;
+ private readonly IUpdatingClient _updatingClient;
+ private readonly INotificationService _notificationService;
+ private IGetReleaseById_Release? _release;
+
+ public ReleaseAvailableViewModel(string nextReleaseId, ILogger logger, IUpdateService updateService, IUpdatingClient updatingClient, INotificationService notificationService)
+ {
+ _nextReleaseId = nextReleaseId;
+ _logger = logger;
+ _updateService = updateService;
+ _updatingClient = updatingClient;
+ _notificationService = notificationService;
+
+ CurrentVersion = _updateService.CurrentVersion ?? "Development build";
+ Install = ReactiveCommand.Create(ExecuteInstall, this.WhenAnyValue(vm => vm.Release).Select(r => r != null));
+
+ this.WhenActivated(async d => await RetrieveRelease(d.AsCancellationToken()));
+ }
+
+ private void ExecuteInstall()
+ {
+ _updateService.InstallRelease(_nextReleaseId);
+ }
+
+ private async Task RetrieveRelease(CancellationToken cancellationToken)
+ {
+ IOperationResult result = await _updatingClient.GetReleaseById.ExecuteAsync(_nextReleaseId, cancellationToken);
+ // Borrow GraphQLClientException for messaging, how lazy of me..
+ if (result.Errors.Count > 0)
+ {
+ GraphQLClientException exception = new(result.Errors);
+ _logger.Error(exception, "Failed to retrieve release details");
+ _notificationService.CreateNotification().WithTitle("Failed to retrieve release details").WithMessage(exception.Message).Show();
+ return;
+ }
+
+ if (result.Data?.Release == null)
+ {
+ _notificationService.CreateNotification().WithTitle("Failed to retrieve release details").WithMessage("Release not found").Show();
+ return;
+ }
+
+ Release = result.Data.Release;
+ }
+
+ public string CurrentVersion { get; }
+
+ public IGetReleaseById_Release? Release
+ {
+ get => _release;
+ set => RaiseAndSetIfChanged(ref _release, value);
+ }
+
+ public ReactiveCommand Install { get; }
+ public ReactiveCommand AskLater { get; }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerView.axaml b/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerView.axaml
new file mode 100644
index 000000000..46699f5ae
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerView.axaml
@@ -0,0 +1,40 @@
+
+
+
+ Downloading & installing update...
+
+
+
+ This should not take long, when finished Artemis must restart.
+
+
+
+
+
+
+
+ Done, click restart to apply the update 🫡
+
+
+ Restart when finished
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerView.axaml.cs b/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerView.axaml.cs
new file mode 100644
index 000000000..ec2a689e6
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerView.axaml.cs
@@ -0,0 +1,28 @@
+using Artemis.UI.Shared;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace Artemis.UI.Screens.Settings.Updating;
+
+public partial class ReleaseInstallerView : ReactiveCoreWindow
+{
+ public ReleaseInstallerView()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void Cancel_OnClick(object? sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerViewModel.cs b/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerViewModel.cs
new file mode 100644
index 000000000..79a80aa06
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseInstallerViewModel.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Reactive;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Artemis.Core;
+using Artemis.UI.Extensions;
+using Artemis.UI.Services.Updating;
+using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services;
+using ReactiveUI;
+
+namespace Artemis.UI.Screens.Settings.Updating;
+
+public class ReleaseInstallerViewModel : ActivatableViewModelBase
+{
+ private readonly ReleaseInstaller _releaseInstaller;
+ private readonly IWindowService _windowService;
+ private ObservableAsPropertyHelper? _overallProgress;
+ private ObservableAsPropertyHelper? _stepProgress;
+ private bool _ready;
+ private bool _restartWhenFinished;
+
+ public ReleaseInstallerViewModel(ReleaseInstaller releaseInstaller, IWindowService windowService)
+ {
+ _releaseInstaller = releaseInstaller;
+ _windowService = windowService;
+
+ Restart = ReactiveCommand.Create(() => Utilities.ApplyUpdate(false));
+ this.WhenActivated(d =>
+ {
+ _overallProgress = Observable.FromEventPattern(x => _releaseInstaller.OverallProgress.ProgressChanged += x, x => _releaseInstaller.OverallProgress.ProgressChanged -= x)
+ .Select(e => e.EventArgs)
+ .ToProperty(this, vm => vm.OverallProgress)
+ .DisposeWith(d);
+ _stepProgress = Observable.FromEventPattern(x => _releaseInstaller.StepProgress.ProgressChanged += x, x => _releaseInstaller.StepProgress.ProgressChanged -= x)
+ .Select(e => e.EventArgs)
+ .ToProperty(this, vm => vm.StepProgress)
+ .DisposeWith(d);
+
+ Task.Run(() => InstallUpdate(d.AsCancellationToken()));
+ });
+ }
+
+ public ReactiveCommand Restart { get; }
+
+ public float OverallProgress => _overallProgress?.Value ?? 0;
+ public float StepProgress => _stepProgress?.Value ?? 0;
+
+ public bool Ready
+ {
+ get => _ready;
+ set => RaiseAndSetIfChanged(ref _ready, value);
+ }
+
+ public bool RestartWhenFinished
+ {
+ get => _restartWhenFinished;
+ set => RaiseAndSetIfChanged(ref _restartWhenFinished, value);
+ }
+
+ private async Task InstallUpdate(CancellationToken cancellationToken)
+ {
+ try
+ {
+ await _releaseInstaller.InstallAsync(cancellationToken);
+ Ready = true;
+ if (RestartWhenFinished)
+ Utilities.ApplyUpdate(false);
+ }
+ catch (TaskCanceledException)
+ {
+ // ignored
+ }
+ catch (Exception e)
+ {
+ _windowService.ShowExceptionDialog("Something went wrong while installing the update", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Updating/UpdateInstallationViewModel.cs b/src/Artemis.UI/Screens/Settings/Updating/UpdateInstallationViewModel.cs
deleted file mode 100644
index 4b1950846..000000000
--- a/src/Artemis.UI/Screens/Settings/Updating/UpdateInstallationViewModel.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Artemis.Core;
-using Artemis.UI.Shared;
-
-namespace Artemis.UI.Screens.Settings.Updating;
-
-public class UpdateInstallationViewModel : DialogViewModelBase
-{
- private readonly string _nextReleaseId;
-
- public UpdateInstallationViewModel(string nextReleaseId)
- {
- _nextReleaseId = nextReleaseId;
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs
index e2a3e516a..dd72303e1 100644
--- a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs
+++ b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs
@@ -11,6 +11,7 @@ using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Services.Interfaces;
+using Artemis.UI.Services.Updating;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
@@ -81,13 +82,12 @@ public class StartupWizardViewModel : DialogViewModelBase
public ObservableCollection DeviceProviders { get; }
public bool IsAutoRunSupported => _autoRunProvider != null;
- public bool IsUpdatingSupported => _updateService.UpdatingSupported;
public PluginSetting UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
- public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
- public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.AutoUpdate", false);
+ public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true);
+ public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", false);
public int CurrentStep
{
@@ -119,7 +119,7 @@ public class StartupWizardViewModel : DialogViewModelBase
CurrentStep--;
// Skip the settings step if none of it's contents are supported
- if (CurrentStep == 4 && !IsAutoRunSupported && !IsUpdatingSupported)
+ if (CurrentStep == 4 && !IsAutoRunSupported)
CurrentStep--;
SetupButtons();
@@ -131,7 +131,7 @@ public class StartupWizardViewModel : DialogViewModelBase
CurrentStep++;
// Skip the settings step if none of it's contents are supported
- if (CurrentStep == 4 && !IsAutoRunSupported && !IsUpdatingSupported)
+ if (CurrentStep == 4 && !IsAutoRunSupported)
CurrentStep++;
SetupButtons();
diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml
index 294f59e91..bf68bf234 100644
--- a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml
+++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml
@@ -68,7 +68,7 @@
-
+
Updating
diff --git a/src/Artemis.UI/Services/Updating/IUpdateService.cs b/src/Artemis.UI/Services/Updating/IUpdateService.cs
index 157d1ef07..5212e4eec 100644
--- a/src/Artemis.UI/Services/Updating/IUpdateService.cs
+++ b/src/Artemis.UI/Services/Updating/IUpdateService.cs
@@ -1,22 +1,11 @@
using System.Threading.Tasks;
+using Artemis.UI.Services.Interfaces;
-namespace Artemis.UI.Services.Interfaces;
+namespace Artemis.UI.Services.Updating;
public interface IUpdateService : IArtemisUIService
{
- ///
- /// Gets a boolean indicating whether updating is supported.
- ///
- bool UpdatingSupported { get; }
-
- ///
- /// Gets or sets a boolean indicating whether auto-updating is suspended.
- ///
- bool SuspendAutoCheck { get; set; }
-
- ///
- /// Manually checks for updates and offers to install it if found.
- ///
- /// Whether an update was found, regardless of whether the user chose to install it.
- Task ManualUpdate();
+ Task CheckForUpdate();
+ Task InstallRelease(string releaseId);
+ string? CurrentVersion { get; }
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs b/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs
index f868298b7..55a3e6a26 100644
--- a/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs
+++ b/src/Artemis.UI/Services/Updating/ReleaseInstaller.cs
@@ -1,16 +1,13 @@
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;
@@ -19,19 +16,16 @@ using StrawberryShake;
namespace Artemis.UI.Services.Updating;
///
-/// Represents the installation process of a release
+/// Represents the installation process of a release
///
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 OverallProgress { get; } = new Progress();
- public IProgress StepProgress { get; } = new Progress();
+ private readonly HttpClient _httpClient;
+ private readonly ILogger _logger;
+ private readonly string _releaseId;
+ private readonly Platform _updatePlatform;
+ private readonly IUpdatingClient _updatingClient;
public ReleaseInstaller(string releaseId, ILogger logger, IUpdatingClient updatingClient, HttpClient httpClient)
{
@@ -43,7 +37,7 @@ public class ReleaseInstaller
if (OperatingSystem.IsWindows())
_updatePlatform = Platform.Windows;
- if (OperatingSystem.IsLinux())
+ else if (OperatingSystem.IsLinux())
_updatePlatform = Platform.Linux;
else if (OperatingSystem.IsMacOS())
_updatePlatform = Platform.Osx;
@@ -54,9 +48,13 @@ public class ReleaseInstaller
Directory.CreateDirectory(_dataFolder);
}
+
+ public Progress OverallProgress { get; } = new();
+ public Progress StepProgress { get; } = new();
+
public async Task InstallAsync(CancellationToken cancellationToken)
{
- OverallProgress.Report(0);
+ ((IProgress) OverallProgress).Report(0);
_logger.Information("Retrieving details for release {ReleaseId}", _releaseId);
IOperationResult result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
@@ -70,7 +68,7 @@ public class ReleaseInstaller
if (artifact == null)
throw new Exception("Found the release but it has no artifact for the current platform");
- OverallProgress.Report(0.1f);
+ ((IProgress) OverallProgress).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)
@@ -84,22 +82,26 @@ public class ReleaseInstaller
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);
+ ((IProgress) OverallProgress).Report(33);
await PatchDelta(stream, previousRelease, cancellationToken);
}
- private async Task PatchDelta(MemoryStream deltaStream, string previousRelease, CancellationToken cancellationToken)
+ private async Task PatchDelta(Stream 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);
+
+ await Task.Run(() =>
+ {
+ DeltaApplier deltaApplier = new();
+ deltaApplier.Apply(baseStream, new BinaryDeltaReader(deltaStream, new DeltaApplierProgressReporter(StepProgress)), newFileStream);
+ });
cancellationToken.ThrowIfCancellationRequested();
-
- OverallProgress.Report(0.66f);
+
+ ((IProgress) OverallProgress).Report(66);
await Extract(newFileStream, cancellationToken);
}
@@ -108,21 +110,28 @@ public class ReleaseInstaller
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);
+ ((IProgress) OverallProgress).Report(50);
+ await Extract(stream, cancellationToken);
}
- private async Task Extract(FileStream archiveStream, CancellationToken cancellationToken)
+ private async Task Extract(Stream 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);
+
+
+
+ await Task.Run(() =>
+ {
+ archiveStream.Seek(0, SeekOrigin.Begin);
+ using ZipArchive archive = new(archiveStream);
+ archive.ExtractToDirectory(extractDirectory, false, StepProgress, cancellationToken);
+ });
- archiveStream.Seek(0, SeekOrigin.Begin);
- using ZipArchive archive = new(archiveStream);
- archive.ExtractToDirectory(extractDirectory);
- OverallProgress.Report(1);
+ ((IProgress) OverallProgress).Report(100);
}
}
@@ -137,6 +146,6 @@ internal class DeltaApplierProgressReporter : IProgressReporter
public void ReportProgress(string operation, long currentPosition, long total)
{
- _stepProgress.Report((float) currentPosition / total);
+ _stepProgress.Report(currentPosition / total * 100);
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/Updating/SimpleUpdateNotificationProvider.cs b/src/Artemis.UI/Services/Updating/SimpleUpdateNotificationProvider.cs
index 94bcaf3be..86f8f7a91 100644
--- a/src/Artemis.UI/Services/Updating/SimpleUpdateNotificationProvider.cs
+++ b/src/Artemis.UI/Services/Updating/SimpleUpdateNotificationProvider.cs
@@ -1,6 +1,12 @@
+using System.Threading.Tasks;
+
namespace Artemis.UI.Services.Updating;
public class SimpleUpdateNotificationProvider : IUpdateNotificationProvider
{
-
+ ///
+ public async Task ShowNotification(string releaseId)
+ {
+ throw new System.NotImplementedException();
+ }
}
\ 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 1077d549c..83f65739e 100644
--- a/src/Artemis.UI/Services/Updating/UpdateService.cs
+++ b/src/Artemis.UI/Services/Updating/UpdateService.cs
@@ -1,21 +1,15 @@
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;
@@ -25,17 +19,17 @@ namespace Artemis.UI.Services;
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 PluginSetting _channel;
+ private readonly Func _getReleaseInstaller;
private readonly ILogger _logger;
private readonly IMainWindowService _mainWindowService;
- private readonly IWindowService _windowService;
- private readonly IUpdatingClient _updatingClient;
private readonly Lazy _updateNotificationProvider;
- private readonly Func _getReleaseInstaller;
private readonly Platform _updatePlatform;
- private readonly PluginSetting _channel;
- private readonly PluginSetting _autoCheck;
- private readonly PluginSetting _autoInstall;
+ private readonly IUpdatingClient _updatingClient;
+ private readonly IWindowService _windowService;
private bool _suspendAutoCheck;
@@ -56,7 +50,7 @@ public class UpdateService : IUpdateService
if (OperatingSystem.IsWindows())
_updatePlatform = Platform.Windows;
- if (OperatingSystem.IsLinux())
+ else if (OperatingSystem.IsLinux())
_updatePlatform = Platform.Linux;
else if (OperatingSystem.IsMacOS())
_updatePlatform = Platform.Osx;
@@ -72,14 +66,61 @@ public class UpdateService : IUpdateService
timer.Elapsed += HandleAutoUpdateEvent;
timer.Start();
}
+
+ public string? CurrentVersion
+ {
+ get
+ {
+ object[] attributes = typeof(UpdateService).Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
+ return attributes.Length == 0 ? null : ((AssemblyInformationalVersionAttribute) attributes[0]).InformationalVersion;
+ }
+ }
+
+ 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(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);
+ Utilities.ApplyUpdate(true);
+ }
+
+ 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");
+ }
+ }
public async Task CheckForUpdate()
{
- string? currentVersion = AssemblyProductVersion;
+ string? currentVersion = CurrentVersion;
if (currentVersion == null)
return false;
- IOperationResult result = await _updatingClient.GetNextRelease.ExecuteAsync(currentVersion, _channel.Value, _updatePlatform);
+ // IOperationResult result = await _updatingClient.GetNextRelease.ExecuteAsync(currentVersion, _channel.Value, _updatePlatform);
+ IOperationResult result = await _updatingClient.GetNextRelease.ExecuteAsync(currentVersion, "feature/gh-actions", _updatePlatform);
result.EnsureNoErrors();
// No update was found
@@ -99,49 +140,16 @@ public class UpdateService : IUpdateService
return true;
}
-
- private async Task ShowUpdateDialog(string nextReleaseId)
+
+ ///
+ public async Task InstallRelease(string releaseId)
{
- await Dispatcher.UIThread.InvokeAsync(async () =>
+ ReleaseInstaller installer = _getReleaseInstaller(releaseId);
+ await Dispatcher.UIThread.InvokeAsync(() =>
{
// Main window is probably already open but this will bring it into focus
_mainWindowService.OpenMainWindow();
- await _windowService.ShowDialogAsync(nextReleaseId);
+ _windowService.ShowWindow(installer);
});
}
-
- 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;
- }
- }
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Styles/Artemis.axaml b/src/Artemis.UI/Styles/Artemis.axaml
index b10f8bdb6..d92851a90 100644
--- a/src/Artemis.UI/Styles/Artemis.axaml
+++ b/src/Artemis.UI/Styles/Artemis.axaml
@@ -6,4 +6,5 @@
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Styles/Markdown.axaml b/src/Artemis.UI/Styles/Markdown.axaml
new file mode 100644
index 000000000..34acbe75f
--- /dev/null
+++ b/src/Artemis.UI/Styles/Markdown.axaml
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ## Core
+ * Cleaned up ProfileService render condition
+ * Core - Added fading in and out of profiles
+ * Core - Apply opacity layer only when fading
+ * Core - Fixed when condition stops being true mid-fade
+ * Core - Removed FadingStatus enum
+
+ # General
+ - Meta - Fixed warnings
+ - Meta - Update RGB.NET
+
+ # Plugins
+ - Plugins - Ignore version when loading shared assemblies
+
+ # UI
+ - Sidebar - Improved category reordering code
+
+
+
+
+
+
\ 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 cd7e8b2a8..56af7ecb5 100644
--- a/src/Artemis.WebClient.Updating/Queries/GetReleaseById.graphql
+++ b/src/Artemis.WebClient.Updating/Queries/GetReleaseById.graphql
@@ -4,6 +4,7 @@ query GetReleaseById($id: String!) {
commit
version
previousRelease
+ changelog
artifacts {
platform
artifactId