mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Implemented Artemis.Web.Update client
This commit is contained in:
parent
a5d2249593
commit
0cd65a2ebf
20
src/Artemis.Core/Events/UpdateEventArgs.cs
Normal file
20
src/Artemis.Core/Events/UpdateEventArgs.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about application update events
|
||||
/// </summary>
|
||||
public class UpdateEventArgs : EventArgs
|
||||
{
|
||||
internal UpdateEventArgs(bool silent)
|
||||
{
|
||||
Silent = silent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether to silently update or not.
|
||||
/// </summary>
|
||||
public bool Silent { get; }
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -50,6 +50,15 @@ public static class Utilities
|
||||
OnRestartRequested(new RestartEventArgs(elevate, delay, extraArgs.ToList()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a pending update
|
||||
/// </summary>
|
||||
/// <param name="silent">A boolean indicating whether to silently update or not.</param>
|
||||
public static void ApplyUpdate(bool silent)
|
||||
{
|
||||
OnUpdateRequested(new UpdateEventArgs(silent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the provided URL in the default web browser
|
||||
/// </summary>
|
||||
@ -96,11 +105,16 @@ public static class Utilities
|
||||
/// Occurs when the core has requested an application shutdown
|
||||
/// </summary>
|
||||
public static event EventHandler? ShutdownRequested;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the core has requested an application restart
|
||||
/// </summary>
|
||||
public static event EventHandler<RestartEventArgs>? RestartRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the core has requested a pending application update to be applied
|
||||
/// </summary>
|
||||
public static event EventHandler<UpdateEventArgs>? UpdateRequested;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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<string> 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);
|
||||
}
|
||||
@ -12,20 +12,10 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
<None Remove=".gitignore" />
|
||||
<None Remove="Assets\Cursors\aero_crosshair.cur" />
|
||||
<None Remove="Assets\Cursors\aero_crosshair_minus.cur" />
|
||||
<None Remove="Assets\Cursors\aero_crosshair_plus.cur" />
|
||||
<None Remove="Assets\Cursors\aero_drag.cur" />
|
||||
<None Remove="Assets\Cursors\aero_drag_ew.cur" />
|
||||
<None Remove="Assets\Cursors\aero_fill.cur" />
|
||||
<None Remove="Assets\Cursors\aero_pen_min.cur" />
|
||||
<None Remove="Assets\Cursors\aero_pen_plus.cur" />
|
||||
<None Remove="Assets\Cursors\aero_rotate.cur" />
|
||||
<None Remove="Assets\Cursors\aero_rotate_bl.cur" />
|
||||
<None Remove="Assets\Cursors\aero_rotate_br.cur" />
|
||||
<None Remove="Assets\Cursors\aero_rotate_tl.cur" />
|
||||
<None Remove="Assets\Cursors\aero_rotate_tr.cur" />
|
||||
|
||||
<Content Include="Scripts\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="..\Artemis.UI\Assets\Images\Logo\application.ico">
|
||||
<Link>application.ico</Link>
|
||||
</None>
|
||||
|
||||
@ -20,7 +20,6 @@ public static class UIContainerExtensions
|
||||
{
|
||||
container.Register<ICursorProvider, CursorProvider>(Reuse.Singleton);
|
||||
container.Register<IGraphicsContextProvider, GraphicsContextProvider>(Reuse.Singleton);
|
||||
container.Register<IUpdateProvider, UpdateProvider>(Reuse.Singleton);
|
||||
container.Register<IAutoRunProvider, AutoRunProvider>();
|
||||
container.Register<InputProvider, WindowsInputProvider>(serviceKey: WindowsInputProvider.Id);
|
||||
}
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:update="clr-namespace:Artemis.UI.Windows.Screens.Update"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Windows.Screens.Update.UpdateDialogView"
|
||||
x:DataType="update:UpdateDialogViewModel"
|
||||
Title="Artemis | Update available"
|
||||
Width="750"
|
||||
MinWidth="750"
|
||||
Height="500"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<TextBlock Grid.Row="0" Classes="h4">
|
||||
A new Artemis update is available! 🥳
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding RetrievingChanges}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock>Retrieving changes...</TextBlock>
|
||||
<ProgressBar IsIndeterminate="True"></ProgressBar>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="1" Classes="card" IsVisible="{CompiledBinding !RetrievingChanges}">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="You are currently running build " />
|
||||
<TextBlock Text="{CompiledBinding CurrentBuild, Mode=OneWay}"></TextBlock>
|
||||
<TextBlock Text=" while the latest build is " />
|
||||
<TextBlock Text="{CompiledBinding LatestBuild, Mode=OneWay}"></TextBlock>
|
||||
<TextBlock Text="." />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Updating Artemis will give you the latest bug(fixes), features and improvements." />
|
||||
<Separator Classes="card-separator" />
|
||||
|
||||
<TextBlock Classes="h5" IsVisible="{CompiledBinding HasChanges}">
|
||||
Changelog (auto-generated)
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="1" VerticalAlignment="Top" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" IsVisible="{CompiledBinding HasChanges}">
|
||||
<ItemsControl Items="{CompiledBinding Changes}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<TextBlock Text="•" Margin="10 0" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding}" TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding !HasChanges}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock HorizontalAlignment="Center">We couldn't retrieve any changes</TextBlock>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis/commits/master" HorizontalAlignment="Center">View online</controls:HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Grid.Row="2" Margin="0 15 0 0">
|
||||
<Button Classes="accent" Command="{CompiledBinding Install}">Install update</Button>
|
||||
<Button Command="{CompiledBinding AskLater}">Ask later</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@ -1,21 +0,0 @@
|
||||
using Artemis.UI.Shared;
|
||||
using Avalonia;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Windows.Screens.Update;
|
||||
|
||||
public class UpdateDialogView : ReactiveCoreWindow<UpdateDialogViewModel>
|
||||
{
|
||||
public UpdateDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@ -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<bool>
|
||||
{
|
||||
// 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<Unit, Unit> Install { get; }
|
||||
public ReactiveCommand<Unit, Unit> AskLater { get; }
|
||||
|
||||
public string Channel { get; }
|
||||
public string CurrentBuild { get; }
|
||||
|
||||
public ObservableCollection<string> 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<DevOpsBuild?> currentTask = _updateProvider.GetBuildInfo(1, CurrentBuild);
|
||||
Task<DevOpsBuild?> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Artemis.UI.Windows/Scripts/update.ps1
Normal file
42
src/Artemis.UI.Windows/Scripts/update.ps1
Normal file
@ -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
|
||||
}
|
||||
@ -29,6 +29,7 @@
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
|
||||
<PackageReference Include="Markdown.Avalonia.Tight" Version="0.10.13" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="1.1.10" />
|
||||
<PackageReference Include="Octopus.Octodiff" Version="2.0.100" />
|
||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||
|
||||
@ -27,7 +27,8 @@
|
||||
Width="200"
|
||||
VerticalAlignment="Center"
|
||||
Items="{CompiledBinding Descriptors}"
|
||||
SelectedItem="{CompiledBinding SelectedDescriptor}">
|
||||
SelectedItem="{CompiledBinding SelectedDescriptor}"
|
||||
PlaceholderText="Please select a brush">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
|
||||
<Grid ColumnDefinitions="30,*" RowDefinitions="Auto,Auto">
|
||||
|
||||
@ -59,7 +59,7 @@ public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushRefe
|
||||
/// <inheritdoc />
|
||||
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));
|
||||
|
||||
@ -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<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);
|
||||
container.Register<IPropertyVmFactory, PropertyVmFactory>(Reuse.Singleton);
|
||||
container.Register<IUpdateNotificationProvider, SimpleUpdateNotificationProvider>();
|
||||
|
||||
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IArtemisUIService>(), Reuse.Singleton);
|
||||
}
|
||||
|
||||
14
src/Artemis.UI/Extensions/CompositeDisposableExtensions.cs
Normal file
14
src/Artemis.UI/Extensions/CompositeDisposableExtensions.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
74
src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
Normal file
74
src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts all the files in the zip archive to a directory on the file system.
|
||||
/// </summary>
|
||||
/// <param name="source">The zip archive to extract files from.</param>
|
||||
/// <param name="destinationDirectoryName">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.</param>
|
||||
/// <param name="overwriteFiles">A boolean indicating whether to override existing files</param>
|
||||
/// <param name="progress">The progress to report to.</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, IProgress<float> 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());
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -137,7 +137,7 @@
|
||||
</Border>
|
||||
|
||||
<!-- Update settings -->
|
||||
<StackPanel IsVisible="{CompiledBinding IsUpdatingSupported}">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h4" Margin="0 15">
|
||||
Updating
|
||||
</TextBlock>
|
||||
|
||||
@ -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<LayerBrushReference> _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<IAutoRunProvider>(IfUnresolved.ReturnDefault);
|
||||
|
||||
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
|
||||
@ -88,7 +92,6 @@ public class GeneralTabViewModel : ActivatableViewModelBase
|
||||
public ReactiveCommand<Unit, Unit> ShowDataFolder { get; }
|
||||
|
||||
public bool IsAutoRunSupported => _autoRunProvider != null;
|
||||
public bool IsUpdatingSupported => _updateService.UpdatingSupported;
|
||||
|
||||
public ObservableCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; }
|
||||
public ObservableCollection<string> GraphicsContexts { get; }
|
||||
@ -142,8 +145,8 @@ public class GeneralTabViewModel : ActivatableViewModelBase
|
||||
public PluginSetting<bool> UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
|
||||
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
|
||||
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
|
||||
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
|
||||
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.AutoUpdate", false);
|
||||
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true);
|
||||
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", false);
|
||||
public PluginSetting<bool> ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
|
||||
public PluginSetting<LogEventLevel> CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
|
||||
public PluginSetting<string> 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()
|
||||
|
||||
@ -0,0 +1,216 @@
|
||||
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||
xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:mdc="clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia"
|
||||
xmlns:mde="clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia"
|
||||
xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia"
|
||||
x:Class="Artemis.UI.Screens.Settings.Updating.ReleaseAvailableView"
|
||||
x:DataType="updating:ReleaseAvailableViewModel"
|
||||
Title="Artemis | Update available"
|
||||
Width="750"
|
||||
Height="750"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<TextBlock Grid.Row="0" Classes="h4">
|
||||
A new Artemis update is available! 🥳
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding Release, Converter={x:Static ObjectConverters.IsNull}}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock>Retrieving release...</TextBlock>
|
||||
<ProgressBar IsIndeterminate="True"></ProgressBar>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="1" Classes="card" IsVisible="{CompiledBinding Release, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="You are currently running version " />
|
||||
<TextBlock Text="{CompiledBinding CurrentVersion, Mode=OneWay}"></TextBlock>
|
||||
<TextBlock Text=" while the latest build is " />
|
||||
<TextBlock Text="{CompiledBinding Release.Version, Mode=OneWay, FallbackValue='Unknown'}"></TextBlock>
|
||||
<TextBlock Text="." />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Updating Artemis will give you the latest bug(fixes), features and improvements." />
|
||||
<Separator Classes="card-separator" />
|
||||
</StackPanel>
|
||||
|
||||
<avalonia:MarkdownScrollViewer Grid.Row="1"
|
||||
VerticalAlignment="Top"
|
||||
Markdown="{CompiledBinding Release.Changelog}">
|
||||
<avalonia:MarkdownScrollViewer.Styles>
|
||||
<Style Selector="ctxt|CTextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1}" />
|
||||
<Setter Property="Margin" Value="0,5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="TextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading1">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 3.2}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading2">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1.6}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading3">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1.6}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading4">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1.2}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CHyperlink">
|
||||
<Style.Setters>
|
||||
<Setter Property="IsUnderline" Value="true" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SystemAccentColor}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="ctxt|CHyperlink:pointerover">
|
||||
<Setter Property="Foreground" Value="{mde:Complementary SystemAccentColor}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Table">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="BorderThickness" Value="0,0,1,1" />
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.Table > Border">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="BorderThickness" Value="1,1,0,0" />
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
<Setter Property="Padding" Value="2" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.TableHeader">
|
||||
<Style.Setters>
|
||||
<Setter Property="Background" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.3}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="Border.TableHeader ctxt|CTextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontWeight" Value="DemiBold" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.EvenTableRow">
|
||||
<Style.Setters>
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.CodeBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.2}" />
|
||||
<Setter Property="BorderThickness" Value="0,5,0,5" />
|
||||
<Setter Property="Margin" Value="5,0,5,0" />
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.CodeBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontFamily" Value="menlo,monaco,consolas,droid sans mono,inconsolata,courier new,monospace,dejavu sans mono" />
|
||||
<Setter Property="Foreground" Value="{mde:DivideColor Blue, TextFillColorPrimary, 0.4}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="Border.NoContainer">
|
||||
<Style.Setters>
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CCode">
|
||||
<Style.Setters>
|
||||
<Setter Property="Foreground" Value="{mde:DivideColor Blue, TextFillColorPrimary, 0.4}" />
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Note">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="5,0,5,0" />
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.2}" />
|
||||
<Setter Property="BorderThickness" Value="3,3,3,3" />
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="ctxt|CTextBlock.Note">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="10, 5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.List">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="15,0,0,0" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="ctxt|CTextBlock.ListMarker">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="0,5,5,5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Blockquote">
|
||||
<Style.Setters>
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.2}" />
|
||||
<Setter Property="BorderThickness" Value="5,0,0,0" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel.Blockquote">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="10, 5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="mdc|Rule">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="0,3" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</avalonia:MarkdownScrollViewer.Styles>
|
||||
</avalonia:MarkdownScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Grid.Row="2" Margin="0 15 0 0">
|
||||
<Button Classes="accent" Command="{CompiledBinding Install}" Click="Button_OnClick">Install update</Button>
|
||||
<Button Click="Button_OnClick">Ask later</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:CoreWindow>
|
||||
@ -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<ReleaseAvailableViewModel>
|
||||
{
|
||||
public ReleaseAvailableView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void Button_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
@ -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<IGetReleaseByIdResult> 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<Unit, Unit> Install { get; }
|
||||
public ReactiveCommand<Unit, Unit> AskLater { get; }
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Artemis.UI.Screens.Settings.Updating.ReleaseInstallerView"
|
||||
x:DataType="updating:ReleaseInstallerViewModel"
|
||||
Title="Artemis | Updating"
|
||||
ShowAsDialog="True"
|
||||
Width="465" Height="260"
|
||||
Padding="15"
|
||||
CanResize="False"
|
||||
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto" Width="450" Height="200">
|
||||
<TextBlock Grid.Row="0" Classes="h4" TextWrapping="Wrap">
|
||||
Downloading & installing update...
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="1" Classes="subtitle" TextWrapping="Wrap">
|
||||
This should not take long, when finished Artemis must restart.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Stretch" IsVisible="{CompiledBinding !Ready}">
|
||||
<ProgressBar Value="{CompiledBinding OverallProgress}" ></ProgressBar>
|
||||
<ProgressBar Margin="0 15 0 0" Value="{CompiledBinding StepProgress}"></ProgressBar>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="2" IsVisible="{CompiledBinding Ready}" VerticalAlignment="Top" Margin="0 15 0 0">Done, click restart to apply the update 🫡</TextBlock>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="15" Grid.Row="3" Margin="0 15 0 0" Height="30">
|
||||
<CheckBox IsVisible="{CompiledBinding !Ready}" IsChecked="{CompiledBinding RestartWhenFinished}">Restart when finished</CheckBox>
|
||||
<Button IsVisible="{CompiledBinding Ready}" Command="{CompiledBinding Restart}" Classes="accent">Restart</Button>
|
||||
<Button Click="Cancel_OnClick">Cancel</Button>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</controls:CoreWindow>
|
||||
@ -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<ReleaseInstallerViewModel>
|
||||
{
|
||||
public ReleaseInstallerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void Cancel_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
@ -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<float>? _overallProgress;
|
||||
private ObservableAsPropertyHelper<float>? _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<float>(x => _releaseInstaller.OverallProgress.ProgressChanged += x, x => _releaseInstaller.OverallProgress.ProgressChanged -= x)
|
||||
.Select(e => e.EventArgs)
|
||||
.ToProperty(this, vm => vm.OverallProgress)
|
||||
.DisposeWith(d);
|
||||
_stepProgress = Observable.FromEventPattern<float>(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<Unit, Unit> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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<bool>
|
||||
public ObservableCollection<PluginViewModel> DeviceProviders { get; }
|
||||
|
||||
public bool IsAutoRunSupported => _autoRunProvider != null;
|
||||
public bool IsUpdatingSupported => _updateService.UpdatingSupported;
|
||||
|
||||
public PluginSetting<bool> UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
|
||||
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
|
||||
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
|
||||
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
|
||||
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.AutoUpdate", false);
|
||||
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true);
|
||||
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", false);
|
||||
|
||||
public int CurrentStep
|
||||
{
|
||||
@ -119,7 +119,7 @@ public class StartupWizardViewModel : DialogViewModelBase<bool>
|
||||
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<bool>
|
||||
CurrentStep++;
|
||||
|
||||
// Skip the settings step if none of it's contents are supported
|
||||
if (CurrentStep == 4 && !IsAutoRunSupported && !IsUpdatingSupported)
|
||||
if (CurrentStep == 4 && !IsAutoRunSupported)
|
||||
CurrentStep++;
|
||||
|
||||
SetupButtons();
|
||||
|
||||
@ -68,7 +68,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<!-- Update settings -->
|
||||
<StackPanel IsVisible="{CompiledBinding IsUpdatingSupported}">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h4" Margin="0 15">
|
||||
Updating
|
||||
</TextBlock>
|
||||
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether updating is supported.
|
||||
/// </summary>
|
||||
bool UpdatingSupported { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether auto-updating is suspended.
|
||||
/// </summary>
|
||||
bool SuspendAutoCheck { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Manually checks for updates and offers to install it if found.
|
||||
/// </summary>
|
||||
/// <returns>Whether an update was found, regardless of whether the user chose to install it.</returns>
|
||||
Task ManualUpdate();
|
||||
Task<bool> CheckForUpdate();
|
||||
Task InstallRelease(string releaseId);
|
||||
string? CurrentVersion { get; }
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the installation process of a release
|
||||
/// 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>();
|
||||
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<float> OverallProgress { get; } = new();
|
||||
public Progress<float> StepProgress { get; } = new();
|
||||
|
||||
public async Task InstallAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
OverallProgress.Report(0);
|
||||
((IProgress<float>) OverallProgress).Report(0);
|
||||
|
||||
_logger.Information("Retrieving details for release {ReleaseId}", _releaseId);
|
||||
IOperationResult<IGetReleaseByIdResult> 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<float>) 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<float>) 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<float>) 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<float>) 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<float>) 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);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.UI.Services.Updating;
|
||||
|
||||
public class SimpleUpdateNotificationProvider : IUpdateNotificationProvider
|
||||
{
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ShowNotification(string releaseId)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -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<bool> _autoCheck;
|
||||
private readonly PluginSetting<bool> _autoInstall;
|
||||
private readonly PluginSetting<string> _channel;
|
||||
private readonly Func<string, ReleaseInstaller> _getReleaseInstaller;
|
||||
|
||||
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 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<ReleaseAvailableViewModel>(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<bool> CheckForUpdate()
|
||||
{
|
||||
string? currentVersion = AssemblyProductVersion;
|
||||
string? currentVersion = CurrentVersion;
|
||||
if (currentVersion == null)
|
||||
return false;
|
||||
|
||||
IOperationResult<IGetNextReleaseResult> result = await _updatingClient.GetNextRelease.ExecuteAsync(currentVersion, _channel.Value, _updatePlatform);
|
||||
// IOperationResult<IGetNextReleaseResult> result = await _updatingClient.GetNextRelease.ExecuteAsync(currentVersion, _channel.Value, _updatePlatform);
|
||||
IOperationResult<IGetNextReleaseResult> 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)
|
||||
|
||||
/// <inheritdoc />
|
||||
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<UpdateInstallationViewModel>(nextReleaseId);
|
||||
_windowService.ShowWindow<ReleaseInstallerViewModel>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,4 +6,5 @@
|
||||
<!-- <FluentTheme Mode="Dark"></FluentTheme> -->
|
||||
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml" />
|
||||
<StyleInclude Source="avares://Artemis.UI.Shared/Styles/Artemis.axaml" />
|
||||
<StyleInclude Source="avares://Artemis.UI/Styles/Markdown.axaml" />
|
||||
</Styles>
|
||||
191
src/Artemis.UI/Styles/Markdown.axaml
Normal file
191
src/Artemis.UI/Styles/Markdown.axaml
Normal file
@ -0,0 +1,191 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
|
||||
xmlns:mdc="clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia"
|
||||
xmlns:mde="clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia"
|
||||
xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
<avalonia:MarkdownScrollViewer Classes="Test">
|
||||
<avalonia:MarkdownScrollViewer.Styles>
|
||||
<Style Selector="ctxt|CTextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1}" />
|
||||
<Setter Property="Margin" Value="0,5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="TextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading1">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 3.2}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading2">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1.6}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading3">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1.6}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CTextBlock.Heading4">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontSize" Value="{mde:Multiply ControlContentThemeFontSize, 1.2}" />
|
||||
<Setter Property="Foreground" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
<Setter Property="FontWeight" Value="Light" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CHyperlink">
|
||||
<Style.Setters>
|
||||
<Setter Property="IsUnderline" Value="true" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SystemAccentColor}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="ctxt|CHyperlink:pointerover">
|
||||
<Setter Property="Foreground" Value="{mde:Complementary SystemAccentColor}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Table">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="BorderThickness" Value="0,0,1,1" />
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.Table > Border">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="BorderThickness" Value="1,1,0,0" />
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.7}" />
|
||||
<Setter Property="Padding" Value="2" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.TableHeader">
|
||||
<Style.Setters>
|
||||
<Setter Property="Background" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.3}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="Border.TableHeader ctxt|CTextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontWeight" Value="DemiBold" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.EvenTableRow">
|
||||
<Style.Setters>
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.CodeBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.2}" />
|
||||
<Setter Property="BorderThickness" Value="0,5,0,5" />
|
||||
<Setter Property="Margin" Value="5,0,5,0" />
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.CodeBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="FontFamily" Value="menlo,monaco,consolas,droid sans mono,inconsolata,courier new,monospace,dejavu sans mono" />
|
||||
<Setter Property="Foreground" Value="{mde:DivideColor Blue, TextFillColorPrimary, 0.4}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="Border.NoContainer">
|
||||
<Style.Setters>
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctxt|CCode">
|
||||
<Style.Setters>
|
||||
<Setter Property="Foreground" Value="{mde:DivideColor Blue, TextFillColorPrimary, 0.4}" />
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Note">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="5,0,5,0" />
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.2}" />
|
||||
<Setter Property="BorderThickness" Value="3,3,3,3" />
|
||||
<Setter Property="Background" Value="{mde:Alpha ControlFillColorDefaultBrush, 0.9}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="ctxt|CTextBlock.Note">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="10, 5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.List">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="15,0,0,0" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style Selector="ctxt|CTextBlock.ListMarker">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="0,5,5,5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Blockquote">
|
||||
<Style.Setters>
|
||||
<Setter Property="BorderBrush" Value="{mde:Alpha TextFillColorPrimaryBrush, 0.2}" />
|
||||
<Setter Property="BorderThickness" Value="5,0,0,0" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel.Blockquote">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="10, 5" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<Style Selector="mdc|Rule">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="0,3" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</avalonia:MarkdownScrollViewer.Styles>
|
||||
## 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
|
||||
</avalonia:MarkdownScrollViewer>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
|
||||
</Styles>
|
||||
@ -4,6 +4,7 @@ query GetReleaseById($id: String!) {
|
||||
commit
|
||||
version
|
||||
previousRelease
|
||||
changelog
|
||||
artifacts {
|
||||
platform
|
||||
artifactId
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user