mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Implemented most of the updating mechanism
This commit is contained in:
parent
0cd65a2ebf
commit
acd005e4a2
@ -9,4 +9,6 @@ public interface IQueuedActionRepository : IRepository
|
|||||||
void Remove(QueuedActionEntity queuedActionEntity);
|
void Remove(QueuedActionEntity queuedActionEntity);
|
||||||
List<QueuedActionEntity> GetAll();
|
List<QueuedActionEntity> GetAll();
|
||||||
List<QueuedActionEntity> GetByType(string type);
|
List<QueuedActionEntity> GetByType(string type);
|
||||||
|
bool IsTypeQueued(string type);
|
||||||
|
void ClearByType(string type);
|
||||||
}
|
}
|
||||||
@ -41,5 +41,17 @@ public class QueuedActionRepository : IQueuedActionRepository
|
|||||||
return _repository.Query<QueuedActionEntity>().Where(q => q.Type == type).ToList();
|
return _repository.Query<QueuedActionEntity>().Where(q => q.Type == type).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsTypeQueued(string type)
|
||||||
|
{
|
||||||
|
return _repository.Query<QueuedActionEntity>().Where(q => q.Type == type).Count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void ClearByType(string type)
|
||||||
|
{
|
||||||
|
_repository.DeleteMany<QueuedActionEntity>(q => q.Type == type);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
34
src/Artemis.UI.Shared/Converters/BytesToStringConverter.cs
Normal file
34
src/Artemis.UI.Shared/Converters/BytesToStringConverter.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Humanizer;
|
||||||
|
using Humanizer.Bytes;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Converters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts bytes to a string
|
||||||
|
/// </summary>
|
||||||
|
public class BytesToStringConverter : IValueConverter
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is int intBytes)
|
||||||
|
return intBytes.Bytes().Humanize();
|
||||||
|
if (value is long longBytes)
|
||||||
|
return longBytes.Bytes().Humanize();
|
||||||
|
if (value is double doubleBytes)
|
||||||
|
return doubleBytes.Bytes().Humanize();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is string formatted && ByteSize.TryParse(formatted, out ByteSize result))
|
||||||
|
return result.Bytes;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
<!-- Custom styles -->
|
<!-- Custom styles -->
|
||||||
<StyleInclude Source="/Styles/Border.axaml" />
|
<StyleInclude Source="/Styles/Border.axaml" />
|
||||||
|
<StyleInclude Source="/Styles/Skeleton.axaml" />
|
||||||
<StyleInclude Source="/Styles/Button.axaml" />
|
<StyleInclude Source="/Styles/Button.axaml" />
|
||||||
<StyleInclude Source="/Styles/Condensed.axaml" />
|
<StyleInclude Source="/Styles/Condensed.axaml" />
|
||||||
<StyleInclude Source="/Styles/ColorPickerButton.axaml" />
|
<StyleInclude Source="/Styles/ColorPickerButton.axaml" />
|
||||||
|
|||||||
174
src/Artemis.UI.Shared/Styles/Skeleton.axaml
Normal file
174
src/Artemis.UI.Shared/Styles/Skeleton.axaml
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Design.PreviewWith>
|
||||||
|
<Grid ColumnDefinitions="*,*">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Margin="20" Grid.Column="0">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Classes="h1">This is heading 1</TextBlock>
|
||||||
|
<TextBlock Classes="h2">This is heading 2</TextBlock>
|
||||||
|
<TextBlock Classes="h3">This is heading 3</TextBlock>
|
||||||
|
<TextBlock Classes="h4">This is heading 4</TextBlock>
|
||||||
|
<TextBlock Classes="h5">This is heading 5</TextBlock>
|
||||||
|
<TextBlock Classes="h6">This is heading 6</TextBlock>
|
||||||
|
<TextBlock>This is regular text</TextBlock>
|
||||||
|
<TextBlock>This is regular text</TextBlock>
|
||||||
|
<TextBlock>This is regular text</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Margin="20" Grid.Column="1">
|
||||||
|
<StackPanel>
|
||||||
|
<Border Width="400" Classes="skeleton-text h1"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h2"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h3"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h4"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h5"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h6"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text"></Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel.Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="Background" Value="#55ff0000"></Setter>
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Styles>
|
||||||
|
<TextBlock Classes="h1">This is heading 1</TextBlock>
|
||||||
|
<TextBlock Classes="h2">This is heading 2</TextBlock>
|
||||||
|
<TextBlock Classes="h3">This is heading 3</TextBlock>
|
||||||
|
<TextBlock Classes="h4">This is heading 4</TextBlock>
|
||||||
|
<TextBlock Classes="h5">This is heading 5</TextBlock>
|
||||||
|
<TextBlock Classes="h6">This is heading 6</TextBlock>
|
||||||
|
<TextBlock>This is regular text</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Margin="20" Grid.Column="0" Row="1">
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<Border Width="400" Classes="skeleton-text h1 no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h2 no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h3 no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h4 no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h5 no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text h6 no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text no-margin"></Border>
|
||||||
|
<Border Width="400" Classes="skeleton-text no-margin"></Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<StackPanel.Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="Background" Value="#55ff0000"></Setter>
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Styles>
|
||||||
|
<TextBlock Classes="h1 no-margin">This is heading 1</TextBlock>
|
||||||
|
<TextBlock Classes="h2 no-margin">This is heading 2</TextBlock>
|
||||||
|
<TextBlock Classes="h3 no-margin">This is heading 3</TextBlock>
|
||||||
|
<TextBlock Classes="h4 no-margin">This is heading 4</TextBlock>
|
||||||
|
<TextBlock Classes="h5 no-margin">This is heading 5</TextBlock>
|
||||||
|
<TextBlock Classes="h6 no-margin">This is heading 6</TextBlock>
|
||||||
|
<TextBlock>This is regular text</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Design.PreviewWith>
|
||||||
|
|
||||||
|
<Styles.Resources>
|
||||||
|
<CornerRadius x:Key="CardCornerRadius">8</CornerRadius>
|
||||||
|
</Styles.Resources>
|
||||||
|
|
||||||
|
<Style Selector="Border.skeleton-text">
|
||||||
|
<Setter Property="Height" Value="17"></Setter>
|
||||||
|
<Setter Property="Margin" Value="0 1" />
|
||||||
|
<Setter Property="IsHitTestVisible" Value="False" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
|
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration="0:0:1.5" IterationCount="Infinite" PlaybackDirection="Normal">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<LinearGradientBrush StartPoint="-100%,-100%" EndPoint="0%,0%">
|
||||||
|
<LinearGradientBrush.GradientStops>
|
||||||
|
<GradientStop Offset="0" Color="Gray" />
|
||||||
|
<GradientStop Offset="0.4" Color="#595959" />
|
||||||
|
<GradientStop Offset="0.6" Color="#595959" />
|
||||||
|
<GradientStop Offset="1" Color="Gray" />
|
||||||
|
</LinearGradientBrush.GradientStops>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<LinearGradientBrush StartPoint="100%,100%" EndPoint="200%,200%">
|
||||||
|
<LinearGradientBrush.GradientStops>
|
||||||
|
<GradientStop Offset="0" Color="Gray" />
|
||||||
|
<GradientStop Offset="0.4" Color="#595959" />
|
||||||
|
<GradientStop Offset="0.6" Color="#595959" />
|
||||||
|
<GradientStop Offset="1" Color="Gray" />
|
||||||
|
</LinearGradientBrush.GradientStops>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Border.skeleton-text.h1">
|
||||||
|
<Setter Property="Height" Value="65" />
|
||||||
|
<Setter Property="Margin" Value="0 10 0 20" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h2">
|
||||||
|
<Setter Property="Height" Value="44" />
|
||||||
|
<Setter Property="Margin" Value="0 10 0 20" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h3">
|
||||||
|
<Setter Property="Height" Value="33" />
|
||||||
|
<Setter Property="Margin" Value="0 5 0 15" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h4">
|
||||||
|
<Setter Property="Height" Value="28" />
|
||||||
|
<Setter Property="Margin" Value="0 2 0 12" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h5">
|
||||||
|
<Setter Property="Height" Value="20" />
|
||||||
|
<Setter Property="Margin" Value="0 2 0 7" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h6">
|
||||||
|
<Setter Property="Height" Value="15" />
|
||||||
|
<Setter Property="Margin" Value="0 2 0 4" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Border.skeleton-text.h1.no-margin">
|
||||||
|
<Setter Property="Margin" Value="0 10 0 10" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h2.no-margin">
|
||||||
|
<Setter Property="Margin" Value="0 10 0 10" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h3.no-margin">
|
||||||
|
<Setter Property="Margin" Value="0 5 0 5" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h4.no-margin">
|
||||||
|
<Setter Property="Margin" Value="0 2 0 2" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h5.no-margin">
|
||||||
|
<Setter Property="Margin" Value="0 2 0 2" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.skeleton-text.h6.no-margin">
|
||||||
|
<Setter Property="Margin" Value="0 2 0 2" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</Styles>
|
||||||
@ -108,8 +108,6 @@ public class ApplicationStateManager
|
|||||||
ProcessStartInfo info = new()
|
ProcessStartInfo info = new()
|
||||||
{
|
{
|
||||||
Arguments = $"-File {script} {source} {destination} {args}",
|
Arguments = $"-File {script} {source} {destination} {args}",
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
FileName = "PowerShell.exe"
|
FileName = "PowerShell.exe"
|
||||||
};
|
};
|
||||||
Process.Start(info);
|
Process.Start(info);
|
||||||
|
|||||||
@ -4,6 +4,10 @@ param (
|
|||||||
[Parameter(Mandatory=$false)][string]$artemisArgs
|
[Parameter(Mandatory=$false)][string]$artemisArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Write-Host "Artemis update script v1"
|
||||||
|
Write-Host "Please do not close this window, this should not take long"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
# Wait up to 10 seconds for the process to shut down
|
# Wait up to 10 seconds for the process to shut down
|
||||||
for ($i=1; $i -le 10; $i++) {
|
for ($i=1; $i -le 10; $i++) {
|
||||||
$process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue
|
$process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue
|
||||||
@ -26,12 +30,17 @@ if (!(Test-Path $destinationDirectory)) {
|
|||||||
Write-Error "The destination directory does not exist"
|
Write-Error "The destination directory does not exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# If the destination directory exists, clear it
|
# If the destination directory exists, clear it
|
||||||
|
Write-Host "Cleaning up old version where needed"
|
||||||
Get-ChildItem $destinationDirectory | Remove-Item -Recurse -Force
|
Get-ChildItem $destinationDirectory | Remove-Item -Recurse -Force
|
||||||
|
|
||||||
# Move the contents of the source directory to the destination directory
|
# Move the contents of the source directory to the destination directory
|
||||||
|
Write-Host "Installing new files"
|
||||||
Get-ChildItem $sourceDirectory | Move-Item -Destination $destinationDirectory
|
Get-ChildItem $sourceDirectory | Move-Item -Destination $destinationDirectory
|
||||||
|
|
||||||
|
|
||||||
|
Write-Host "Finished! Restarting Artemis"
|
||||||
Start-Sleep -Seconds 1
|
Start-Sleep -Seconds 1
|
||||||
|
|
||||||
# When finished, run the updated version
|
# When finished, run the updated version
|
||||||
|
|||||||
@ -43,4 +43,15 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Screens\Settings\Tabs\ReleasesTabView.axaml.cs">
|
||||||
|
<DependentUpon>UpdatingTabView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Screens\Settings\Updating\ReleaseView.axaml.cs">
|
||||||
|
<DependentUpon>UpdatingTabView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -31,14 +31,13 @@ public static class UIContainerExtensions
|
|||||||
container.Register<IAssetLoader, AssetLoader>(Reuse.Singleton);
|
container.Register<IAssetLoader, AssetLoader>(Reuse.Singleton);
|
||||||
|
|
||||||
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ViewModelBase>());
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ViewModelBase>());
|
||||||
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<MainScreenViewModel>(), ifAlreadyRegistered: IfAlreadyRegistered.Replace);
|
|
||||||
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IToolViewModel>() && type.IsInterface);
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IToolViewModel>() && type.IsInterface);
|
||||||
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IVmFactory>() && type != typeof(PropertyVmFactory));
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IVmFactory>() && type != typeof(PropertyVmFactory));
|
||||||
|
|
||||||
container.Register<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);
|
container.Register<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);
|
||||||
container.Register<IPropertyVmFactory, PropertyVmFactory>(Reuse.Singleton);
|
container.Register<IPropertyVmFactory, PropertyVmFactory>(Reuse.Singleton);
|
||||||
container.Register<IUpdateNotificationProvider, SimpleUpdateNotificationProvider>();
|
container.Register<IUpdateNotificationProvider, SimpleUpdateNotificationProvider>();
|
||||||
|
|
||||||
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IArtemisUIService>(), Reuse.Singleton);
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IArtemisUIService>(), Reuse.Singleton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.LayerBrushes;
|
using Artemis.Core.LayerBrushes;
|
||||||
@ -17,6 +18,7 @@ using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
|||||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
||||||
using Artemis.UI.Screens.Scripting;
|
using Artemis.UI.Screens.Scripting;
|
||||||
using Artemis.UI.Screens.Settings;
|
using Artemis.UI.Screens.Settings;
|
||||||
|
using Artemis.UI.Screens.Settings.Updating;
|
||||||
using Artemis.UI.Screens.Sidebar;
|
using Artemis.UI.Screens.Sidebar;
|
||||||
using Artemis.UI.Screens.SurfaceEditor;
|
using Artemis.UI.Screens.SurfaceEditor;
|
||||||
using Artemis.UI.Screens.VisualScripting;
|
using Artemis.UI.Screens.VisualScripting;
|
||||||
@ -474,4 +476,23 @@ public class ScriptVmFactory : IScriptVmFactory
|
|||||||
{
|
{
|
||||||
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { profile, scriptConfiguration });
|
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { profile, scriptConfiguration });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IReleaseVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
ReleaseViewModel ReleaseListViewModel(string releaseId, string version, DateTimeOffset createdAt);
|
||||||
|
}
|
||||||
|
public class ReleaseVmFactory : IReleaseVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public ReleaseVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReleaseViewModel ReleaseListViewModel(string releaseId, string version, DateTimeOffset createdAt)
|
||||||
|
{
|
||||||
|
return _container.Resolve<ReleaseViewModel>(new object[] { releaseId, version, createdAt });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -10,6 +10,7 @@ public class SettingsViewModel : MainScreenViewModel
|
|||||||
GeneralTabViewModel generalTabViewModel,
|
GeneralTabViewModel generalTabViewModel,
|
||||||
PluginsTabViewModel pluginsTabViewModel,
|
PluginsTabViewModel pluginsTabViewModel,
|
||||||
DevicesTabViewModel devicesTabViewModel,
|
DevicesTabViewModel devicesTabViewModel,
|
||||||
|
ReleasesTabViewModel releasesTabViewModel,
|
||||||
AboutTabViewModel aboutTabViewModel) : base(hostScreen, "settings")
|
AboutTabViewModel aboutTabViewModel) : base(hostScreen, "settings")
|
||||||
{
|
{
|
||||||
SettingTabs = new ObservableCollection<ActivatableViewModelBase>
|
SettingTabs = new ObservableCollection<ActivatableViewModelBase>
|
||||||
@ -17,6 +18,7 @@ public class SettingsViewModel : MainScreenViewModel
|
|||||||
generalTabViewModel,
|
generalTabViewModel,
|
||||||
pluginsTabViewModel,
|
pluginsTabViewModel,
|
||||||
devicesTabViewModel,
|
devicesTabViewModel,
|
||||||
|
releasesTabViewModel,
|
||||||
aboutTabViewModel
|
aboutTabViewModel
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml
Normal file
26
src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<UserControl 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:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||||
|
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400"
|
||||||
|
x:Class="Artemis.UI.Screens.Settings.ReleasesTabView"
|
||||||
|
x:DataType="settings:ReleasesTabViewModel">
|
||||||
|
<Grid ColumnDefinitions="300,*" Margin="0 10">
|
||||||
|
<Border Classes="card-condensed" Grid.Column="0" Margin="0 0 10 0">
|
||||||
|
<ListBox Items="{CompiledBinding ReleaseViewModels}" SelectedItem="{CompiledBinding SelectedReleaseViewModel}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="updating:ReleaseViewModel">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<TextBlock Text="{CompiledBinding Version}" VerticalAlignment="Center" />
|
||||||
|
<TextBlock Text="{CompiledBinding CreatedAt, StringFormat={}{0:g}}" VerticalAlignment="Center" Classes="subtitle" FontSize="13" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ContentControl Grid.Column="1" Content="{CompiledBinding SelectedReleaseViewModel}"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings;
|
||||||
|
|
||||||
|
public class ReleasesTabView : ReactiveUserControl<ReleasesTabViewModel>
|
||||||
|
{
|
||||||
|
public ReleasesTabView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs
Normal file
106
src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.DryIoc.Factories;
|
||||||
|
using Artemis.UI.Extensions;
|
||||||
|
using Artemis.UI.Screens.Settings.Updating;
|
||||||
|
using Artemis.UI.Services.Updating;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
|
using Artemis.WebClient.Updating;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Serilog;
|
||||||
|
using StrawberryShake;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings;
|
||||||
|
|
||||||
|
public class ReleasesTabViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IUpdatingClient _updatingClient;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
private readonly SourceList<IGetReleases_PublishedReleases_Nodes> _releases;
|
||||||
|
private IGetReleases_PublishedReleases_PageInfo? _lastPageInfo;
|
||||||
|
private bool _loading;
|
||||||
|
private ReleaseViewModel? _selectedReleaseViewModel;
|
||||||
|
|
||||||
|
public ReleasesTabViewModel(ILogger logger, IUpdateService updateService, IUpdatingClient updatingClient, IReleaseVmFactory releaseVmFactory, INotificationService notificationService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_updatingClient = updatingClient;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
|
||||||
|
_releases = new SourceList<IGetReleases_PublishedReleases_Nodes>();
|
||||||
|
_releases.Connect()
|
||||||
|
.Sort(SortExpressionComparer<IGetReleases_PublishedReleases_Nodes>.Descending(p => p.CreatedAt))
|
||||||
|
.Transform(r => releaseVmFactory.ReleaseListViewModel(r.Id, r.Version, r.CreatedAt))
|
||||||
|
.ObserveOn(AvaloniaScheduler.Instance)
|
||||||
|
.Bind(out ReadOnlyObservableCollection<ReleaseViewModel> releaseViewModels)
|
||||||
|
.Subscribe();
|
||||||
|
|
||||||
|
DisplayName = "Releases";
|
||||||
|
ReleaseViewModels = releaseViewModels;
|
||||||
|
this.WhenActivated(async d =>
|
||||||
|
{
|
||||||
|
await updateService.CacheLatestRelease();
|
||||||
|
await GetMoreReleases(d.AsCancellationToken());
|
||||||
|
SelectedReleaseViewModel = ReleaseViewModels.FirstOrDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObservableCollection<ReleaseViewModel> ReleaseViewModels { get; }
|
||||||
|
|
||||||
|
public ReleaseViewModel? SelectedReleaseViewModel
|
||||||
|
{
|
||||||
|
get => _selectedReleaseViewModel;
|
||||||
|
set => RaiseAndSetIfChanged(ref _selectedReleaseViewModel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Loading
|
||||||
|
{
|
||||||
|
get => _loading;
|
||||||
|
private set => RaiseAndSetIfChanged(ref _loading, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GetMoreReleases(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_lastPageInfo != null && !_lastPageInfo.HasNextPage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Loading = true;
|
||||||
|
|
||||||
|
IOperationResult<IGetReleasesResult> result = await _updatingClient.GetReleases.ExecuteAsync("feature/gh-actions", Platform.Windows, 20, _lastPageInfo?.EndCursor, cancellationToken);
|
||||||
|
if (result.Data?.PublishedReleases?.Nodes == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastPageInfo = result.Data.PublishedReleases.PageInfo;
|
||||||
|
_releases.AddRange(result.Data.PublishedReleases.Nodes);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Warning(e, "Failed to retrieve releases");
|
||||||
|
_notificationService.CreateNotification()
|
||||||
|
.WithTitle("Failed to retrieve releases")
|
||||||
|
.WithMessage(e.Message)
|
||||||
|
.WithSeverity(NotificationSeverity.Warning)
|
||||||
|
.Show();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
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,49 +1,163 @@
|
|||||||
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||||
xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
|
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:mdc="clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia"
|
xmlns:mde="clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia"
|
||||||
xmlns:mde="clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia"
|
xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia"
|
||||||
xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia"
|
xmlns:avalonia1="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
x:Class="Artemis.UI.Screens.Settings.Updating.ReleaseAvailableView"
|
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||||
x:DataType="updating:ReleaseAvailableViewModel"
|
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400"
|
||||||
Title="Artemis | Update available"
|
x:Class="Artemis.UI.Screens.Settings.Updating.ReleaseView"
|
||||||
Width="750"
|
x:DataType="updating:ReleaseViewModel">
|
||||||
Height="750"
|
<UserControl.Resources>
|
||||||
WindowStartupLocation="CenterOwner">
|
<converters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
||||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
</UserControl.Resources>
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector=":is(Control).fade-in">
|
||||||
|
<Setter Property="Opacity" Value="0"></Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector=":is(Control).fade-in[IsVisible=True]">
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration="0:00:00.250" FillMode="Forward" Easing="CubicEaseInOut">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="Opacity" Value="0.0" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="Opacity" Value="1.0" />
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid.info-container">
|
||||||
|
<Setter Property="Margin" Value="10" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="avalonia1|MaterialIcon.info-icon">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Top" />
|
||||||
|
<Setter Property="Margin" Value="0 3 10 0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.info-title">
|
||||||
|
<Setter Property="Margin" Value="0 0 0 5" />
|
||||||
|
<Setter Property="Opacity" Value="0.8" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.info-body">
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.info-link">
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorLight3}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.info-link:pointerover">
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorLight1}" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Classes="h4">
|
<Grid RowDefinitions="Auto,*" IsVisible="{CompiledBinding !Loading}" Classes="fade-in">
|
||||||
A new Artemis update is available! 🥳
|
<Border Grid.Row="0" Classes="card" Margin="0 0 0 10">
|
||||||
</TextBlock>
|
<StackPanel>
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<TextBlock Classes="h4 no-margin">Release info</TextBlock>
|
||||||
|
|
||||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding Release, Converter={x:Static ObjectConverters.IsNull}}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
<Panel Grid.Column="1" IsVisible="{CompiledBinding InstallationAvailable}">
|
||||||
<TextBlock>Retrieving release...</TextBlock>
|
<!-- Install progress -->
|
||||||
<ProgressBar IsIndeterminate="True"></ProgressBar>
|
<Grid ColumnDefinitions="*,*"
|
||||||
</StackPanel>
|
RowDefinitions="*,*"
|
||||||
|
IsVisible="{CompiledBinding InstallationInProgress}">
|
||||||
|
<ProgressBar Grid.Column="0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Width="300"
|
||||||
|
Value="{CompiledBinding ReleaseInstaller.Progress, FallbackValue=0}">
|
||||||
|
</ProgressBar>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Grid.Row="1"
|
||||||
|
Classes="subtitle"
|
||||||
|
TextAlignment="Right"
|
||||||
|
Text="{CompiledBinding ReleaseInstaller.Status, FallbackValue=Installing}" />
|
||||||
|
<Button Grid.Column="1" Grid.Row="0" Grid.RowSpan="2"
|
||||||
|
Classes="accent"
|
||||||
|
Margin="15 0 0 0"
|
||||||
|
Width="80"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Command="{CompiledBinding CancelInstall}">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Border Grid.Row="1" Classes="card" IsVisible="{CompiledBinding Release, Converter={x:Static ObjectConverters.IsNotNull}}">
|
<Panel IsVisible="{CompiledBinding !InstallationInProgress}" HorizontalAlignment="Right">
|
||||||
<Grid RowDefinitions="Auto,*">
|
<!-- Install button -->
|
||||||
<StackPanel Grid.Row="0">
|
<Button Classes="accent"
|
||||||
<StackPanel Orientation="Horizontal">
|
Width="80"
|
||||||
<TextBlock Text="You are currently running version " />
|
Command="{CompiledBinding Install}"
|
||||||
<TextBlock Text="{CompiledBinding CurrentVersion, Mode=OneWay}"></TextBlock>
|
IsVisible="{CompiledBinding !InstallationFinished}">
|
||||||
<TextBlock Text=" while the latest build is " />
|
Install
|
||||||
<TextBlock Text="{CompiledBinding Release.Version, Mode=OneWay, FallbackValue='Unknown'}"></TextBlock>
|
</Button>
|
||||||
<TextBlock Text="." />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<TextBlock Text="Updating Artemis will give you the latest bug(fixes), features and improvements." />
|
<!-- Restart button -->
|
||||||
<Separator Classes="card-separator" />
|
<Grid ColumnDefinitions="*,*" IsVisible="{CompiledBinding InstallationFinished}">
|
||||||
</StackPanel>
|
<TextBlock Grid.Column="0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Classes="subtitle"
|
||||||
|
TextAlignment="Right"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
Ready, restart to install
|
||||||
|
</TextBlock>
|
||||||
|
<Button Grid.Column="1" Grid.Row="0"
|
||||||
|
Classes="accent"
|
||||||
|
Margin="15 0 0 0"
|
||||||
|
Width="80"
|
||||||
|
Command="{CompiledBinding Restart}"
|
||||||
|
IsVisible="{CompiledBinding InstallationFinished}">
|
||||||
|
Restart
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Panel>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
<avalonia:MarkdownScrollViewer Grid.Row="1"
|
</Grid>
|
||||||
VerticalAlignment="Top"
|
<Separator Classes="card-separator" />
|
||||||
Markdown="{CompiledBinding Release.Changelog}">
|
<Grid Margin="-5 -10" ColumnDefinitions="*,*,*">
|
||||||
|
<Grid Grid.Column="0" ColumnDefinitions="*,*" RowDefinitions="*,*,*" Classes="info-container" HorizontalAlignment="Left">
|
||||||
|
<avalonia1:MaterialIcon Kind="Calendar" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||||
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Release date</TextBlock>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
Classes="info-body"
|
||||||
|
Text="{CompiledBinding CreatedAt, StringFormat={}{0:g}, FallbackValue=Loading...}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Column="1" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Center">
|
||||||
|
<avalonia1:MaterialIcon Kind="Git" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||||
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Source</TextBlock>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
Classes="info-body info-link"
|
||||||
|
Cursor="Hand"
|
||||||
|
PointerReleased="InputElement_OnPointerReleased"
|
||||||
|
Text="{CompiledBinding ShortCommit, FallbackValue=Loading...}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Column="2" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Right">
|
||||||
|
<avalonia1:MaterialIcon Kind="BoxOutline" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||||
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">File size</TextBlock>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
Classes="info-body"
|
||||||
|
Text="{CompiledBinding FileSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay, FallbackValue=Loading...}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Row="1" Classes="card">
|
||||||
|
<Grid RowDefinitions="Auto,Auto,*">
|
||||||
|
<TextBlock Grid.Row="0" Classes="h5 no-margin">Release notes</TextBlock>
|
||||||
|
<Separator Grid.Row="1" Classes="card-separator" />
|
||||||
|
|
||||||
|
<avalonia:MarkdownScrollViewer Grid.Row="2" Markdown="{CompiledBinding Changelog}">
|
||||||
<avalonia:MarkdownScrollViewer.Styles>
|
<avalonia:MarkdownScrollViewer.Styles>
|
||||||
<Style Selector="ctxt|CTextBlock">
|
<Style Selector="ctxt|CTextBlock">
|
||||||
<Style.Setters>
|
<Style.Setters>
|
||||||
@ -207,10 +321,6 @@
|
|||||||
</avalonia:MarkdownScrollViewer>
|
</avalonia:MarkdownScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</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>
|
</Grid>
|
||||||
</controls:CoreWindow>
|
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings.Updating;
|
||||||
|
|
||||||
|
public class ReleaseView : ReactiveUserControl<ReleaseViewModel>
|
||||||
|
{
|
||||||
|
public ReleaseView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel?.NavigateToSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
202
src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs
Normal file
202
src/Artemis.UI/Screens/Settings/Updating/ReleaseViewModel.cs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Threading.Tasks;
|
||||||
|
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.UI.Shared.Services.Builders;
|
||||||
|
using Artemis.WebClient.Updating;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Serilog;
|
||||||
|
using StrawberryShake;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings.Updating;
|
||||||
|
|
||||||
|
public class ReleaseViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
private readonly string _releaseId;
|
||||||
|
private readonly Platform _updatePlatform;
|
||||||
|
private readonly IUpdatingClient _updatingClient;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private CancellationTokenSource? _installerCts;
|
||||||
|
private string _changelog = string.Empty;
|
||||||
|
private string _commit = string.Empty;
|
||||||
|
private string _shortCommit = string.Empty;
|
||||||
|
private long _fileSize;
|
||||||
|
private bool _installationAvailable;
|
||||||
|
private bool _installationFinished;
|
||||||
|
private bool _installationInProgress;
|
||||||
|
private bool _loading = true;
|
||||||
|
private bool _retrievedDetails;
|
||||||
|
|
||||||
|
public ReleaseViewModel(string releaseId,
|
||||||
|
string version,
|
||||||
|
DateTimeOffset createdAt,
|
||||||
|
ILogger logger,
|
||||||
|
IUpdatingClient updatingClient,
|
||||||
|
INotificationService notificationService,
|
||||||
|
IUpdateService updateService,
|
||||||
|
IWindowService windowService)
|
||||||
|
{
|
||||||
|
_releaseId = releaseId;
|
||||||
|
_logger = logger;
|
||||||
|
_updatingClient = updatingClient;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
_windowService = windowService;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
_updatePlatform = Platform.Windows;
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
_updatePlatform = Platform.Linux;
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
_updatePlatform = Platform.Osx;
|
||||||
|
else
|
||||||
|
throw new PlatformNotSupportedException("Cannot auto update on the current platform");
|
||||||
|
|
||||||
|
Version = version;
|
||||||
|
CreatedAt = createdAt;
|
||||||
|
ReleaseInstaller = updateService.GetReleaseInstaller(_releaseId);
|
||||||
|
|
||||||
|
Install = ReactiveCommand.CreateFromTask(ExecuteInstall);
|
||||||
|
Restart = ReactiveCommand.Create(() => Utilities.ApplyUpdate(false));
|
||||||
|
CancelInstall = ReactiveCommand.Create(() => _installerCts?.Cancel());
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
// There's no point in running anything but the latest version of the current channel.
|
||||||
|
// Perhaps later that won't be true anymore, then we could consider allowing to install
|
||||||
|
// older versions with compatible database versions.
|
||||||
|
InstallationAvailable = updateService.CachedLatestRelease?.Id == _releaseId;
|
||||||
|
RetrieveDetails(d.AsCancellationToken()).ToObservable();
|
||||||
|
Disposable.Create(_installerCts, cts => cts?.Cancel()).DisposeWith(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> Restart { get; set; }
|
||||||
|
public ReactiveCommand<Unit, Unit> Install { get; }
|
||||||
|
public ReactiveCommand<Unit, Unit> CancelInstall { get; }
|
||||||
|
|
||||||
|
public string Version { get; }
|
||||||
|
public DateTimeOffset CreatedAt { get; }
|
||||||
|
public ReleaseInstaller ReleaseInstaller { get; }
|
||||||
|
|
||||||
|
public string Changelog
|
||||||
|
{
|
||||||
|
get => _changelog;
|
||||||
|
set => RaiseAndSetIfChanged(ref _changelog, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Commit
|
||||||
|
{
|
||||||
|
get => _commit;
|
||||||
|
set => RaiseAndSetIfChanged(ref _commit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ShortCommit
|
||||||
|
{
|
||||||
|
get => _shortCommit;
|
||||||
|
set => RaiseAndSetIfChanged(ref _shortCommit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long FileSize
|
||||||
|
{
|
||||||
|
get => _fileSize;
|
||||||
|
set => RaiseAndSetIfChanged(ref _fileSize, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Loading
|
||||||
|
{
|
||||||
|
get => _loading;
|
||||||
|
private set => RaiseAndSetIfChanged(ref _loading, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InstallationAvailable
|
||||||
|
{
|
||||||
|
get => _installationAvailable;
|
||||||
|
set => RaiseAndSetIfChanged(ref _installationAvailable, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InstallationInProgress
|
||||||
|
{
|
||||||
|
get => _installationInProgress;
|
||||||
|
set => RaiseAndSetIfChanged(ref _installationInProgress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InstallationFinished
|
||||||
|
{
|
||||||
|
get => _installationFinished;
|
||||||
|
set => RaiseAndSetIfChanged(ref _installationFinished, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NavigateToSource()
|
||||||
|
{
|
||||||
|
Utilities.OpenUrl($"https://github.com/Artemis-RGB/Artemis/commit/{Commit}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteInstall(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_installerCts = new CancellationTokenSource();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InstallationInProgress = true;
|
||||||
|
await ReleaseInstaller.InstallAsync(_installerCts.Token);
|
||||||
|
InstallationFinished = true;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_windowService.ShowExceptionDialog("Failed to install update", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
InstallationInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RetrieveDetails(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_retrievedDetails)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Loading = true;
|
||||||
|
|
||||||
|
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
|
||||||
|
IGetReleaseById_PublishedRelease? release = result.Data?.PublishedRelease;
|
||||||
|
if (release == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Changelog = release.Changelog;
|
||||||
|
Commit = release.Commit;
|
||||||
|
ShortCommit = release.Commit.Substring(0, 7);
|
||||||
|
FileSize = release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform)?.FileInfo.DownloadSize ?? 0;
|
||||||
|
|
||||||
|
_retrievedDetails = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Warning(e, "Failed to retrieve release details");
|
||||||
|
_notificationService.CreateNotification()
|
||||||
|
.WithTitle("Failed to retrieve details")
|
||||||
|
.WithMessage(e.Message)
|
||||||
|
.WithSeverity(NotificationSeverity.Warning)
|
||||||
|
.Show();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,18 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
using Artemis.WebClient.Updating;
|
||||||
|
|
||||||
namespace Artemis.UI.Services.Updating;
|
namespace Artemis.UI.Services.Updating;
|
||||||
|
|
||||||
public interface IUpdateService : IArtemisUIService
|
public interface IUpdateService : IArtemisUIService
|
||||||
{
|
{
|
||||||
|
string? CurrentVersion { get; }
|
||||||
|
IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; }
|
||||||
|
|
||||||
|
Task CacheLatestRelease();
|
||||||
Task<bool> CheckForUpdate();
|
Task<bool> CheckForUpdate();
|
||||||
Task InstallRelease(string releaseId);
|
Task InstallRelease(string releaseId);
|
||||||
string? CurrentVersion { get; }
|
void QueueUpdate();
|
||||||
|
|
||||||
|
ReleaseInstaller GetReleaseInstaller(string releaseId);
|
||||||
}
|
}
|
||||||
@ -3,9 +3,11 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
using Artemis.UI.Extensions;
|
using Artemis.UI.Extensions;
|
||||||
using Artemis.WebClient.Updating;
|
using Artemis.WebClient.Updating;
|
||||||
using Octodiff.Core;
|
using Octodiff.Core;
|
||||||
@ -18,7 +20,7 @@ namespace Artemis.UI.Services.Updating;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the installation process of a release
|
/// Represents the installation process of a release
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ReleaseInstaller
|
public class ReleaseInstaller : CorePropertyChanged
|
||||||
{
|
{
|
||||||
private readonly string _dataFolder;
|
private readonly string _dataFolder;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
@ -26,6 +28,10 @@ public class ReleaseInstaller
|
|||||||
private readonly string _releaseId;
|
private readonly string _releaseId;
|
||||||
private readonly Platform _updatePlatform;
|
private readonly Platform _updatePlatform;
|
||||||
private readonly IUpdatingClient _updatingClient;
|
private readonly IUpdatingClient _updatingClient;
|
||||||
|
private readonly Progress<float> _progress = new();
|
||||||
|
private Progress<float> _stepProgress = new();
|
||||||
|
private string _status;
|
||||||
|
private float _progress1;
|
||||||
|
|
||||||
public ReleaseInstaller(string releaseId, ILogger logger, IUpdatingClient updatingClient, HttpClient httpClient)
|
public ReleaseInstaller(string releaseId, ILogger logger, IUpdatingClient updatingClient, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
@ -46,29 +52,42 @@ public class ReleaseInstaller
|
|||||||
|
|
||||||
if (!Directory.Exists(_dataFolder))
|
if (!Directory.Exists(_dataFolder))
|
||||||
Directory.CreateDirectory(_dataFolder);
|
Directory.CreateDirectory(_dataFolder);
|
||||||
|
|
||||||
|
_progress.ProgressChanged += (_, f) => Progress = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Status
|
||||||
|
{
|
||||||
|
get => _status;
|
||||||
|
private set => SetAndNotify(ref _status, value);
|
||||||
|
}
|
||||||
|
|
||||||
public Progress<float> OverallProgress { get; } = new();
|
public float Progress
|
||||||
public Progress<float> StepProgress { get; } = new();
|
{
|
||||||
|
get => _progress1;
|
||||||
|
set => SetAndNotify(ref _progress1, value);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task InstallAsync(CancellationToken cancellationToken)
|
public async Task InstallAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
((IProgress<float>) OverallProgress).Report(0);
|
_stepProgress = new Progress<float>();
|
||||||
|
|
||||||
|
((IProgress<float>) _progress).Report(0);
|
||||||
|
|
||||||
|
Status = "Retrieving details";
|
||||||
_logger.Information("Retrieving details for release {ReleaseId}", _releaseId);
|
_logger.Information("Retrieving details for release {ReleaseId}", _releaseId);
|
||||||
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
|
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
|
||||||
result.EnsureNoErrors();
|
result.EnsureNoErrors();
|
||||||
|
|
||||||
IGetReleaseById_Release? release = result.Data?.Release;
|
IGetReleaseById_PublishedRelease? release = result.Data?.PublishedRelease;
|
||||||
if (release == null)
|
if (release == null)
|
||||||
throw new Exception($"Could not find release with ID {_releaseId}");
|
throw new Exception($"Could not find release with ID {_releaseId}");
|
||||||
|
|
||||||
IGetReleaseById_Release_Artifacts? artifact = release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform);
|
IGetReleaseById_PublishedRelease_Artifacts? artifact = release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform);
|
||||||
if (artifact == null)
|
if (artifact == null)
|
||||||
throw new Exception("Found the release but it has no artifact for the current platform");
|
throw new Exception("Found the release but it has no artifact for the current platform");
|
||||||
|
|
||||||
((IProgress<float>) OverallProgress).Report(10);
|
((IProgress<float>) _progress).Report(10);
|
||||||
|
|
||||||
// Determine whether the last update matches our local version, then we can download the delta
|
// Determine whether the last update matches our local version, then we can download the delta
|
||||||
if (release.PreviousRelease != null && File.Exists(Path.Combine(_dataFolder, $"{release.PreviousRelease}.zip")) && artifact.DeltaFileInfo.DownloadSize != 0)
|
if (release.PreviousRelease != null && File.Exists(Path.Combine(_dataFolder, $"{release.PreviousRelease}.zip")) && artifact.DeltaFileInfo.DownloadSize != 0)
|
||||||
@ -77,75 +96,92 @@ public class ReleaseInstaller
|
|||||||
await Download(artifact, cancellationToken);
|
await Download(artifact, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadDelta(IGetReleaseById_Release_Artifacts artifact, string previousRelease, CancellationToken cancellationToken)
|
private async Task DownloadDelta(IGetReleaseById_PublishedRelease_Artifacts artifact, string previousRelease, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 10 - 50%
|
||||||
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
|
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(10f + e * 0.4f);
|
||||||
|
|
||||||
|
Status = "Downloading...";
|
||||||
await using MemoryStream stream = new();
|
await using MemoryStream stream = new();
|
||||||
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/download/{artifact.ArtifactId}/delta", stream, StepProgress, cancellationToken);
|
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/{artifact.ArtifactId}/delta", stream, _stepProgress, cancellationToken);
|
||||||
|
|
||||||
((IProgress<float>) OverallProgress).Report(33);
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
|
await PatchDelta(stream, previousRelease, artifact, cancellationToken);
|
||||||
await PatchDelta(stream, previousRelease, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PatchDelta(Stream deltaStream, string previousRelease, CancellationToken cancellationToken)
|
private async Task PatchDelta(Stream deltaStream, string previousRelease, IGetReleaseById_PublishedRelease_Artifacts artifact, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await using FileStream baseStream = File.OpenRead(previousRelease);
|
// 50 - 60%
|
||||||
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
|
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(50f + e * 0.1f);
|
||||||
|
|
||||||
|
Status = "Patching...";
|
||||||
await using FileStream newFileStream = new(Path.Combine(_dataFolder, $"{_releaseId}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
await using FileStream newFileStream = new(Path.Combine(_dataFolder, $"{_releaseId}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
|
await using (FileStream baseStream = File.OpenRead(previousRelease))
|
||||||
deltaStream.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
await Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
DeltaApplier deltaApplier = new();
|
deltaStream.Seek(0, SeekOrigin.Begin);
|
||||||
deltaApplier.Apply(baseStream, new BinaryDeltaReader(deltaStream, new DeltaApplierProgressReporter(StepProgress)), newFileStream);
|
DeltaApplier deltaApplier = new() {SkipHashCheck = true};
|
||||||
});
|
// Patching is not async and so fast that it's not worth adding a progress reporter
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
deltaApplier.Apply(baseStream, new BinaryDeltaReader(deltaStream, new NullProgressReporter()), newFileStream);
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
|
||||||
((IProgress<float>) OverallProgress).Report(66);
|
// The previous release is no longer required now that the latest has been downloaded
|
||||||
|
File.Delete(previousRelease);
|
||||||
|
|
||||||
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
|
|
||||||
|
await ValidateArchive(newFileStream, artifact, cancellationToken);
|
||||||
await Extract(newFileStream, cancellationToken);
|
await Extract(newFileStream, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Download(IGetReleaseById_Release_Artifacts artifact, CancellationToken cancellationToken)
|
private async Task Download(IGetReleaseById_PublishedRelease_Artifacts artifact, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await using MemoryStream stream = new();
|
// 10 - 60%
|
||||||
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/download/{artifact.ArtifactId}", stream, StepProgress, cancellationToken);
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
|
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(10f + e * 0.5f);
|
||||||
|
|
||||||
((IProgress<float>) OverallProgress).Report(50);
|
Status = "Downloading...";
|
||||||
|
await using FileStream stream = new(Path.Combine(_dataFolder, $"{_releaseId}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
|
await _httpClient.DownloadDataAsync($"https://updating.artemis-rgb.com/api/artifacts/{artifact.ArtifactId}", stream, _stepProgress, cancellationToken);
|
||||||
|
|
||||||
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
|
|
||||||
|
await ValidateArchive(stream, artifact, cancellationToken);
|
||||||
await Extract(stream, cancellationToken);
|
await Extract(stream, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Extract(Stream archiveStream, CancellationToken cancellationToken)
|
private async Task Extract(Stream archiveStream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 60 - 100%
|
||||||
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
|
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(60f + e * 0.4f);
|
||||||
|
|
||||||
|
Status = "Extracting...";
|
||||||
// Ensure the directory is empty
|
// Ensure the directory is empty
|
||||||
string extractDirectory = Path.Combine(_dataFolder, "pending");
|
string extractDirectory = Path.Combine(_dataFolder, "pending");
|
||||||
if (Directory.Exists(extractDirectory))
|
if (Directory.Exists(extractDirectory))
|
||||||
Directory.Delete(extractDirectory, true);
|
Directory.Delete(extractDirectory, true);
|
||||||
Directory.CreateDirectory(extractDirectory);
|
Directory.CreateDirectory(extractDirectory);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
archiveStream.Seek(0, SeekOrigin.Begin);
|
archiveStream.Seek(0, SeekOrigin.Begin);
|
||||||
using ZipArchive archive = new(archiveStream);
|
using ZipArchive archive = new(archiveStream);
|
||||||
archive.ExtractToDirectory(extractDirectory, false, StepProgress, cancellationToken);
|
archive.ExtractToDirectory(extractDirectory, false, _stepProgress, cancellationToken);
|
||||||
});
|
}, cancellationToken);
|
||||||
|
|
||||||
((IProgress<float>) OverallProgress).Report(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class DeltaApplierProgressReporter : IProgressReporter
|
((IProgress<float>) _progress).Report(100);
|
||||||
{
|
_stepProgress.ProgressChanged -= StepProgressOnProgressChanged;
|
||||||
private readonly IProgress<float> _stepProgress;
|
|
||||||
|
|
||||||
public DeltaApplierProgressReporter(IProgress<float> stepProgress)
|
|
||||||
{
|
|
||||||
_stepProgress = stepProgress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReportProgress(string operation, long currentPosition, long total)
|
private async Task ValidateArchive(Stream archiveStream, IGetReleaseById_PublishedRelease_Artifacts artifact, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_stepProgress.Report(currentPosition / total * 100);
|
using MD5 md5 = MD5.Create();
|
||||||
|
archiveStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
string hash = BitConverter.ToString(await md5.ComputeHashAsync(archiveStream, cancellationToken)).Replace("-", "");
|
||||||
|
if (hash != artifact.FileInfo.Md5Hash)
|
||||||
|
throw new ArtemisUIException($"Update file hash mismatch, expected \"{artifact.FileInfo.Md5Hash}\" but got \"{hash}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,8 +4,9 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.Storage.Entities.General;
|
||||||
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using Artemis.UI.Screens.Settings.Updating;
|
using Artemis.UI.Screens.Settings.Updating;
|
||||||
using Artemis.UI.Services.Updating;
|
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Shared.Services.MainWindow;
|
using Artemis.UI.Shared.Services.MainWindow;
|
||||||
using Artemis.WebClient.Updating;
|
using Artemis.WebClient.Updating;
|
||||||
@ -14,7 +15,7 @@ using Serilog;
|
|||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
using Timer = System.Timers.Timer;
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
namespace Artemis.UI.Services;
|
namespace Artemis.UI.Services.Updating;
|
||||||
|
|
||||||
public class UpdateService : IUpdateService
|
public class UpdateService : IUpdateService
|
||||||
{
|
{
|
||||||
@ -26,24 +27,24 @@ public class UpdateService : IUpdateService
|
|||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IMainWindowService _mainWindowService;
|
private readonly IMainWindowService _mainWindowService;
|
||||||
|
private readonly IQueuedActionRepository _queuedActionRepository;
|
||||||
private readonly Lazy<IUpdateNotificationProvider> _updateNotificationProvider;
|
private readonly Lazy<IUpdateNotificationProvider> _updateNotificationProvider;
|
||||||
private readonly Platform _updatePlatform;
|
private readonly Platform _updatePlatform;
|
||||||
private readonly IUpdatingClient _updatingClient;
|
private readonly IUpdatingClient _updatingClient;
|
||||||
private readonly IWindowService _windowService;
|
|
||||||
|
|
||||||
private bool _suspendAutoCheck;
|
private bool _suspendAutoCheck;
|
||||||
|
|
||||||
public UpdateService(ILogger logger,
|
public UpdateService(ILogger logger,
|
||||||
ISettingsService settingsService,
|
ISettingsService settingsService,
|
||||||
IMainWindowService mainWindowService,
|
IMainWindowService mainWindowService,
|
||||||
IWindowService windowService,
|
IQueuedActionRepository queuedActionRepository,
|
||||||
IUpdatingClient updatingClient,
|
IUpdatingClient updatingClient,
|
||||||
Lazy<IUpdateNotificationProvider> updateNotificationProvider,
|
Lazy<IUpdateNotificationProvider> updateNotificationProvider,
|
||||||
Func<string, ReleaseInstaller> getReleaseInstaller)
|
Func<string, ReleaseInstaller> getReleaseInstaller)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mainWindowService = mainWindowService;
|
_mainWindowService = mainWindowService;
|
||||||
_windowService = windowService;
|
_queuedActionRepository = queuedActionRepository;
|
||||||
_updatingClient = updatingClient;
|
_updatingClient = updatingClient;
|
||||||
_updateNotificationProvider = updateNotificationProvider;
|
_updateNotificationProvider = updateNotificationProvider;
|
||||||
_getReleaseInstaller = getReleaseInstaller;
|
_getReleaseInstaller = getReleaseInstaller;
|
||||||
@ -65,24 +66,32 @@ public class UpdateService : IUpdateService
|
|||||||
Timer timer = new(UPDATE_CHECK_INTERVAL);
|
Timer timer = new(UPDATE_CHECK_INTERVAL);
|
||||||
timer.Elapsed += HandleAutoUpdateEvent;
|
timer.Elapsed += HandleAutoUpdateEvent;
|
||||||
timer.Start();
|
timer.Start();
|
||||||
|
|
||||||
|
_channel.Value = "feature/gh-actions";
|
||||||
|
_channel.Save();
|
||||||
|
|
||||||
|
|
||||||
|
InstallQueuedUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? CurrentVersion
|
private void InstallQueuedUpdate()
|
||||||
{
|
{
|
||||||
get
|
if (!_queuedActionRepository.IsTypeQueued("InstallUpdate"))
|
||||||
{
|
return;
|
||||||
object[] attributes = typeof(UpdateService).Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
|
|
||||||
return attributes.Length == 0 ? null : ((AssemblyInformationalVersionAttribute) attributes[0]).InformationalVersion;
|
_queuedActionRepository.ClearByType("InstallUpdate");
|
||||||
}
|
_logger.Information("Installing queued update");
|
||||||
|
Utilities.ApplyUpdate(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ShowUpdateDialog(string nextReleaseId)
|
private async Task ShowUpdateDialog(string nextReleaseId)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
// Main window is probably already open but this will bring it into focus
|
// Main window is probably already open but this will bring it into focus
|
||||||
_mainWindowService.OpenMainWindow();
|
_mainWindowService.OpenMainWindow();
|
||||||
await _windowService.ShowDialogAsync<ReleaseAvailableViewModel>(nextReleaseId);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,19 +121,35 @@ public class UpdateService : IUpdateService
|
|||||||
_logger.Warning(ex, "Auto update failed");
|
_logger.Warning(ex, "Auto update failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IGetNextRelease_NextPublishedRelease? CachedLatestRelease { get; private set; }
|
||||||
|
|
||||||
|
public string? CurrentVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
object[] attributes = typeof(UpdateService).Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
|
||||||
|
return attributes.Length == 0 ? null : ((AssemblyInformationalVersionAttribute) attributes[0]).InformationalVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task CacheLatestRelease()
|
||||||
|
{
|
||||||
|
IOperationResult<IGetNextReleaseResult> result = await _updatingClient.GetNextRelease.ExecuteAsync(CurrentVersion, _channel.Value, _updatePlatform);
|
||||||
|
CachedLatestRelease = result.Data?.NextPublishedRelease;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> CheckForUpdate()
|
public async Task<bool> CheckForUpdate()
|
||||||
{
|
{
|
||||||
string? currentVersion = CurrentVersion;
|
IOperationResult<IGetNextReleaseResult> result = await _updatingClient.GetNextRelease.ExecuteAsync(CurrentVersion, _channel.Value, _updatePlatform);
|
||||||
if (currentVersion == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 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();
|
result.EnsureNoErrors();
|
||||||
|
|
||||||
|
// Update cache
|
||||||
|
CachedLatestRelease = result.Data?.NextPublishedRelease;
|
||||||
|
|
||||||
// No update was found
|
// No update was found
|
||||||
if (result.Data?.NextRelease == null)
|
if (CachedLatestRelease == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Only offer it once per session
|
// Only offer it once per session
|
||||||
@ -132,15 +157,15 @@ public class UpdateService : IUpdateService
|
|||||||
|
|
||||||
// If the window is open show the changelog, don't auto-update while the user is busy
|
// If the window is open show the changelog, don't auto-update while the user is busy
|
||||||
if (_mainWindowService.IsMainWindowOpen)
|
if (_mainWindowService.IsMainWindowOpen)
|
||||||
await ShowUpdateDialog(result.Data.NextRelease.Id);
|
await ShowUpdateDialog(CachedLatestRelease.Id);
|
||||||
else if (!_autoInstall.Value)
|
else if (!_autoInstall.Value)
|
||||||
await ShowUpdateNotification(result.Data.NextRelease.Id);
|
await ShowUpdateNotification(CachedLatestRelease.Id);
|
||||||
else
|
else
|
||||||
await AutoInstallUpdate(result.Data.NextRelease.Id);
|
await AutoInstallUpdate(CachedLatestRelease.Id);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task InstallRelease(string releaseId)
|
public async Task InstallRelease(string releaseId)
|
||||||
{
|
{
|
||||||
@ -149,7 +174,19 @@ public class UpdateService : IUpdateService
|
|||||||
{
|
{
|
||||||
// Main window is probably already open but this will bring it into focus
|
// Main window is probably already open but this will bring it into focus
|
||||||
_mainWindowService.OpenMainWindow();
|
_mainWindowService.OpenMainWindow();
|
||||||
_windowService.ShowWindow<ReleaseInstallerViewModel>(installer);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void QueueUpdate()
|
||||||
|
{
|
||||||
|
if (!_queuedActionRepository.IsTypeQueued("InstallUpdate"))
|
||||||
|
_queuedActionRepository.Add(new QueuedActionEntity {Type = "InstallUpdate"});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ReleaseInstaller GetReleaseInstaller(string releaseId)
|
||||||
|
{
|
||||||
|
return _getReleaseInstaller(releaseId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,29 @@
|
|||||||
|
query GetReleases($branch: String!, $platform: Platform!, $take: Int!, $after: String) {
|
||||||
|
publishedReleases(
|
||||||
|
first: $take
|
||||||
|
after: $after
|
||||||
|
where: {
|
||||||
|
and: [
|
||||||
|
{ branch: { eq: $branch } }
|
||||||
|
{ artifacts: { some: { platform: { eq: $platform } } } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
version
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
query GetReleaseById($id: String!) {
|
query GetReleaseById($id: String!) {
|
||||||
release(id: $id) {
|
publishedRelease(id: $id) {
|
||||||
branch
|
branch
|
||||||
commit
|
commit
|
||||||
version
|
version
|
||||||
@ -23,9 +47,10 @@ fragment fileInfo on ArtifactFileInfo {
|
|||||||
downloadSize
|
downloadSize
|
||||||
}
|
}
|
||||||
|
|
||||||
query GetNextRelease($currentVersion: String!, $branch: String!, $platform: Platform!) {
|
query GetNextRelease($currentVersion: String, $branch: String!, $platform: Platform!) {
|
||||||
nextRelease(version: $currentVersion, branch: $branch, platform: $platform) {
|
nextPublishedRelease(version: $currentVersion, branch: $branch, platform: $platform) {
|
||||||
id
|
id
|
||||||
version
|
version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,6 @@ type Artifact {
|
|||||||
artifactId: Long!
|
artifactId: Long!
|
||||||
deltaFileInfo: ArtifactFileInfo!
|
deltaFileInfo: ArtifactFileInfo!
|
||||||
fileInfo: ArtifactFileInfo!
|
fileInfo: ArtifactFileInfo!
|
||||||
fileName(deltaFile: Boolean!): String!
|
|
||||||
platform: Platform!
|
platform: Platform!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,13 +54,69 @@ type Mutation {
|
|||||||
updateReleaseChangelog(input: UpdateReleaseChangelogInput!): UpdateReleaseChangelogPayload!
|
updateReleaseChangelog(input: UpdateReleaseChangelogInput!): UpdateReleaseChangelogPayload!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Information about pagination in a connection."
|
||||||
|
type PageInfo {
|
||||||
|
"When paginating forwards, the cursor to continue."
|
||||||
|
endCursor: String
|
||||||
|
"Indicates whether more edges exist following the set defined by the clients arguments."
|
||||||
|
hasNextPage: Boolean!
|
||||||
|
"Indicates whether more edges exist prior the set defined by the clients arguments."
|
||||||
|
hasPreviousPage: Boolean!
|
||||||
|
"When paginating backwards, the cursor to continue."
|
||||||
|
startCursor: String
|
||||||
|
}
|
||||||
|
|
||||||
|
"A connection to a list of items."
|
||||||
|
type PublishedReleasesConnection {
|
||||||
|
"A list of edges."
|
||||||
|
edges: [PublishedReleasesEdge!]
|
||||||
|
"A flattened list of the nodes."
|
||||||
|
nodes: [Release!]
|
||||||
|
"Information to aid in pagination."
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
totalCount: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
"An edge in a connection."
|
||||||
|
type PublishedReleasesEdge {
|
||||||
|
"A cursor for use in pagination."
|
||||||
|
cursor: String!
|
||||||
|
"The item at the end of the edge."
|
||||||
|
node: Release!
|
||||||
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
channelByBranch(branch: String!): ArtemisChannel
|
channelByBranch(branch: String!): ArtemisChannel
|
||||||
channels: [ArtemisChannel!]!
|
channels: [ArtemisChannel!]!
|
||||||
nextRelease(branch: String!, platform: Platform!, version: String!): Release
|
nextPublishedRelease(branch: String!, platform: Platform!, version: String): Release
|
||||||
|
publishedChannels: [String!]!
|
||||||
|
publishedRelease(id: String!): Release
|
||||||
|
publishedReleases(
|
||||||
|
"Returns the elements in the list that come after the specified cursor."
|
||||||
|
after: String,
|
||||||
|
"Returns the elements in the list that come before the specified cursor."
|
||||||
|
before: String,
|
||||||
|
"Returns the first _n_ elements from the list."
|
||||||
|
first: Int,
|
||||||
|
"Returns the last _n_ elements from the list."
|
||||||
|
last: Int,
|
||||||
|
order: [ReleaseSortInput!],
|
||||||
|
where: ReleaseFilterInput
|
||||||
|
): PublishedReleasesConnection
|
||||||
release(id: String!): Release
|
release(id: String!): Release
|
||||||
releaseStatistics(order: [ReleaseStatisticSortInput!], where: ReleaseStatisticFilterInput): [ReleaseStatistic!]!
|
releaseStatistics(order: [ReleaseStatisticSortInput!], where: ReleaseStatisticFilterInput): [ReleaseStatistic!]!
|
||||||
releases(order: [ReleaseSortInput!], where: ReleaseFilterInput): [Release!]!
|
releases(
|
||||||
|
"Returns the elements in the list that come after the specified cursor."
|
||||||
|
after: String,
|
||||||
|
"Returns the elements in the list that come before the specified cursor."
|
||||||
|
before: String,
|
||||||
|
"Returns the first _n_ elements from the list."
|
||||||
|
first: Int,
|
||||||
|
"Returns the last _n_ elements from the list."
|
||||||
|
last: Int,
|
||||||
|
order: [ReleaseSortInput!],
|
||||||
|
where: ReleaseFilterInput
|
||||||
|
): ReleasesConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
type Release {
|
type Release {
|
||||||
@ -86,6 +141,25 @@ type ReleaseStatistic {
|
|||||||
windowsCount: Int!
|
windowsCount: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"A connection to a list of items."
|
||||||
|
type ReleasesConnection {
|
||||||
|
"A list of edges."
|
||||||
|
edges: [ReleasesEdge!]
|
||||||
|
"A flattened list of the nodes."
|
||||||
|
nodes: [Release!]
|
||||||
|
"Information to aid in pagination."
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
totalCount: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
"An edge in a connection."
|
||||||
|
type ReleasesEdge {
|
||||||
|
"A cursor for use in pagination."
|
||||||
|
cursor: String!
|
||||||
|
"The item at the end of the edge."
|
||||||
|
node: Release!
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateReleaseChangelogPayload {
|
type UpdateReleaseChangelogPayload {
|
||||||
release: Release
|
release: Release
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user