finish auto update feature

This commit is contained in:
IsWaffle 2023-07-30 16:15:52 -04:00
parent e70e30ff57
commit f135a3e325
14 changed files with 323 additions and 58 deletions

View File

@ -168,7 +168,29 @@
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
</Style>
<!-- Button outlined Style -->
<!-- Button outlined Style -->
<Style Selector="Button.outlined">
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
<Style Selector="Button.outlined:pointerover /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
<Style Selector="Button.outlined:pressed /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<!-- Button outlinedTLCorner Style -->
<Style Selector="Button.outlinedTLCorner">
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
<Setter Property="Background" Value="Transparent"/>
@ -180,7 +202,7 @@
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
<Setter Property="BorderThickness" Value="1 1 0 0"/>
<Setter Property="BorderThickness" Value="2 2 0 0"/>
</Style>
<Style Selector="Button.outlinedTLCorner:pressed /template/ ContentPresenter">

View File

@ -0,0 +1,53 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SPTInstaller.CustomControls.UpdateInfoCard"
MinHeight="100" MinWidth="300">
<UserControl.Styles>
<Style Selector="Grid">
<Setter Property="Opacity" Value="0"/>
<Setter Property="Transitions">
<Setter.Value>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
</Transitions>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Grid.show">
<Setter Property="Opacity" Value="1"/>
</Style>
</UserControl.Styles>
<Grid ColumnDefinitions="10,*,AUTO,AUTO,10" RowDefinitions="10,AUTO,AUTO,10"
Classes.show="{Binding ShowUpdateCard, RelativeSource={RelativeSource AncestorType=UserControl}}">
<Border Grid.ColumnSpan="5" Grid.RowSpan="4" Background="{StaticResource AKI_Background_Light}"
BoxShadow="2 2 10 .1 black" CornerRadius="8"
/>
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" MaxWidth="400"
Text="{Binding InfoText, RelativeSource={RelativeSource AncestorType=UserControl}}"
TextWrapping="Wrap" Margin="0 10"
/>
<Button Grid.Column="2" Grid.Row="2" Content="Not now"
Classes="outlined"
IsVisible="{Binding !Updating, RelativeSource={RelativeSource AncestorType=UserControl}}"
Command="{Binding NotNowCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
<Button Grid.Column="3" Grid.Row="2" Content="Update"
Classes="yellow" Margin="10 0 0 0"
IsVisible="{Binding !Updating, RelativeSource={RelativeSource AncestorType=UserControl}}"
Command="{Binding UpdateInstallerCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
<ProgressBar Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="3"
Value="{Binding DownloadProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
IsVisible="{Binding Updating, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</Grid>
</UserControl>

View File

@ -0,0 +1,60 @@
using Avalonia;
using Avalonia.Controls;
using System.Windows.Input;
namespace SPTInstaller.CustomControls;
public partial class UpdateInfoCard : UserControl
{
public UpdateInfoCard()
{
InitializeComponent();
}
public bool ShowUpdateCard
{
get => GetValue(ShowUpdateCardProperty);
set => SetValue(ShowUpdateCardProperty, value);
}
public static readonly StyledProperty<bool> ShowUpdateCardProperty =
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(ShowUpdateCard));
public bool Updating
{
get => GetValue(UpdatingProperty);
set => SetValue(UpdatingProperty, value);
}
public static readonly StyledProperty<bool> UpdatingProperty =
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(Updating));
public string InfoText
{
get => GetValue(InfoTextProperty);
set => SetValue(InfoTextProperty, value);
}
public static readonly StyledProperty<string> InfoTextProperty =
AvaloniaProperty.Register<UpdateInfoCard, string>(nameof(InfoText));
public int DownloadProgress
{
get => GetValue(DownloadProgressProperty);
set => SetValue(DownloadProgressProperty, value);
}
public static readonly StyledProperty<int> DownloadProgressProperty =
AvaloniaProperty.Register<UpdateInfoCard, int>(nameof(DownloadProgress));
public ICommand NotNowCommand
{
get => GetValue(NotNowCommandProperty);
set => SetValue(NotNowCommandProperty, value);
}
public static readonly StyledProperty<ICommand> NotNowCommandProperty =
AvaloniaProperty.Register<UpdateInfoCard, ICommand>(nameof(NotNowCommand));
public ICommand UpdateInstallerCommand
{
get => GetValue(UpdateInstallerCommandProperty);
set => SetValue(UpdateInstallerCommandProperty, value);
}
public static readonly StyledProperty<ICommand> UpdateInstallerCommandProperty =
AvaloniaProperty.Register<UpdateInfoCard, ICommand>(nameof(UpdateInstallerCommand));
}

View File

@ -9,14 +9,14 @@ public static class DownloadCacheHelper
{
private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromHours(1) };
private static string _cachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache");
public static string CachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache");
private static bool CheckCache(FileInfo cacheFile, string expectedHash = null)
{
try
{
cacheFile.Refresh();
Directory.CreateDirectory(_cachePath);
Directory.CreateDirectory(CachePath);
if (cacheFile.Exists)
{
@ -108,7 +108,7 @@ public static class DownloadCacheHelper
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash = null)
{
var cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
var cacheFile = new FileInfo(Path.Join(CachePath, fileName));
try
{
@ -125,7 +125,7 @@ public static class DownloadCacheHelper
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null)
{
var cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
var cacheFile = new FileInfo(Path.Join(CachePath, fileName));
try
{

View File

@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Serilog;
using SPTInstaller.Models;
@ -83,4 +85,30 @@ public static class FileHelper
return Result.FromError(ex.Message);
}
}
public static void StreamAssemblyResourceOut(string resourceName, string outputFilePath)
{
var assembly = Assembly.GetExecutingAssembly();
FileInfo outputFile = new FileInfo(outputFilePath);
if (outputFile.Exists)
{
outputFile.Delete();
}
if (!outputFile.Directory.Exists)
{
Directory.CreateDirectory(outputFile.Directory.FullName);
}
var resName = assembly.GetManifestResourceNames().First(x => x.EndsWith(resourceName));
using (FileStream fs = File.Create(outputFilePath))
using (Stream s = assembly.GetManifestResourceStream(resName))
{
s.CopyTo(fs);
}
}
}

View File

@ -2,12 +2,24 @@
using Gitea.Client;
using ReactiveUI;
using Serilog;
using SPTInstaller.Helpers;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Input;
namespace SPTInstaller.Models;
public class InstallerUpdateInfo : ReactiveObject
{
private Version? _newVersion;
public string NewInstallerUrl = "";
private string _updateInfoText = "";
public string UpdateInfoText
{
get => _updateInfoText;
set => this.RaiseAndSetIfChanged(ref _updateInfoText, value);
}
private bool _updateAvailable;
public bool UpdateAvailable
{
@ -15,31 +27,67 @@ public class InstallerUpdateInfo : ReactiveObject
set => this.RaiseAndSetIfChanged(ref _updateAvailable, value);
}
private Version _currentVersion;
public Version CurrentVersion
private bool _updating;
public bool Updating
{
get => _currentVersion;
set => this.RaiseAndSetIfChanged(ref _currentVersion, value);
get => _updating;
set => this.RaiseAndSetIfChanged(ref _updating, value);
}
private Version _newVersion;
public Version NewVersion
private int _downloadProgress;
public int DownloadProgress
{
get => _newVersion;
set => this.RaiseAndSetIfChanged(ref _newVersion, value);
get => _downloadProgress;
set => this.RaiseAndSetIfChanged(ref _downloadProgress, value);
}
private bool _checkingForUpdates;
public bool CheckingForUpdates
public async Task UpdateInstaller()
{
get => _checkingForUpdates;
set => this.RaiseAndSetIfChanged(ref _checkingForUpdates, value);
Updating = true;
var updater = new FileInfo(Path.Join(DownloadCacheHelper.CachePath, "update.ps1"));
FileHelper.StreamAssemblyResourceOut("update.ps1", updater.FullName);
if (!updater.Exists)
{
UpdateInfoText = "Failed to get updater from resources :(";
return;
}
var newInstallerPath = await DownloadNewInstaller();
if(string.IsNullOrWhiteSpace(newInstallerPath))
return;
Process.Start(new ProcessStartInfo
{
FileName = "powershell.exe",
ArgumentList = { "-ExecutionPolicy", "Bypass", "-File", $"{updater.FullName}", $"{newInstallerPath}", $"{Path.Join(Environment.CurrentDirectory, "SPTInstaller.exe")}" }
});
}
public async Task<bool> CheckForUpdates()
private async Task<string> DownloadNewInstaller()
{
CheckingForUpdates = true;
UpdateInfoText = $"Downloading new installer v{_newVersion}";
var progress = new Progress<double>(x => DownloadProgress = (int)x);
var file = await DownloadCacheHelper.GetOrDownloadFileAsync("SPTInstller.exe", NewInstallerUrl, progress);
if (file == null || !file.Exists)
{
UpdateInfoText = "Failed to download new installer :(";
return "";
}
return file.FullName;
}
public async Task CheckForUpdates(Version? currentVersion)
{
if (currentVersion == null)
return;
try
{
@ -48,45 +96,33 @@ public class InstallerUpdateInfo : ReactiveObject
var releases = await repo.RepoListReleasesAsync("CWX", "SPT-AKI-Installer");
if (releases == null || releases.Count == 0)
return false;
return;
var latest = releases.FindAll(x => !x.Prerelease)[0];
if (latest == null)
return false;
return;
var latestVersion = new Version(latest.TagName);
if (latestVersion == null || latestVersion <= CurrentVersion)
return false;
if (latestVersion == null || latestVersion <= currentVersion)
return;
NewVersion = latestVersion;
UpdateAvailable = true;
CheckingForUpdates = false;
return true;
_newVersion = latestVersion;
UpdateInfoText = $"A newer installer is available, version {latestVersion}";
NewInstallerUrl = latest.Assets[0].BrowserDownloadUrl;
return;
}
catch (Exception ex)
{
Log.Logger.Error(ex, "Failed to check for updates");
}
CheckingForUpdates = false;
return false;
}
public ICommand UpdateInstaller { get; set; }
public InstallerUpdateInfo(Version? currentVersion)
{
if (currentVersion == null)
return;
CurrentVersion = currentVersion;
UpdateInstaller = ReactiveCommand.Create(() =>
{
// TODO: update installer here
});
return;
}
}

View File

@ -0,0 +1,27 @@
param(
[string]$source,
[string]$destination
)
clear
Write-Host "Stopping installer ..."
$installer = Stop-Process -Name "SPTInstaller" -ErrorAction SilentlyContinue
if ($installer -ne $null) {
Write-Host "Something went wrong, couldn't stop installer process'"
return;
}
Write-Host "Copying new installer ..."
Import-Module BitsTransfer
Start-BitsTransfer -Source $source -Destination $destination -DisplayName "Updating" -Description "Copying new installer"
Remove-Module -Name BitsTransfer
Start-Process $destination
Write-Host "Done"

View File

@ -17,6 +17,11 @@
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
<None Remove="Assets\icon.ico" />
<None Remove="Resources\update.ps1" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\update.ps1" />
</ItemGroup>
<ItemGroup>

View File

@ -3,7 +3,7 @@
namespace SPTInstaller.ViewModels;
public class DetailedPreChecksViewModel : PreChecksViewModel
{
public DetailedPreChecksViewModel(IScreen host) : base(host)
public DetailedPreChecksViewModel(IScreen host) : base(host, null)
{
}
}

View File

@ -12,6 +12,7 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
{
public RoutingState Router { get; } = new();
public ViewModelActivator Activator { get; } = new();
public InstallerUpdateInfo UpdateInfo { get; } = new();
private string _title;
public string Title
@ -31,14 +32,12 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
Log.Information($"========= {Title} Started =========");
Log.Information(Environment.OSVersion.VersionString);
var updateInfo = new InstallerUpdateInfo(version);
Task.Run(async () =>
{
await updateInfo.CheckForUpdates();
await UpdateInfo.CheckForUpdates(version);
});
Router.Navigate.Execute(new PreChecksViewModel(this));
Router.Navigate.Execute(new PreChecksViewModel(this, DismissUpdateCommand));
}
public void CloseCommand()
@ -57,4 +56,14 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
}
}
public void DismissUpdateCommand()
{
UpdateInfo.UpdateAvailable = false;
}
public async Task UpdateInstallerCommand()
{
Router.Navigate.Execute(new MessageViewModel(this, Result.FromSuccess("Please wait while the update is installed"), false));
await UpdateInfo.UpdateInstaller();
}
}

View File

@ -22,6 +22,13 @@ public class MessageViewModel : ViewModelBase
set => this.RaiseAndSetIfChanged(ref _Message, value);
}
private bool _showCloseButton;
public bool ShowCloseButton
{
get => _showCloseButton;
set => this.RaiseAndSetIfChanged(ref _showCloseButton, value);
}
public ICommand CloseCommand { get; set; } = ReactiveCommand.Create(() =>
{
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
@ -30,8 +37,9 @@ public class MessageViewModel : ViewModelBase
}
});
public MessageViewModel(IScreen Host, IResult result) : base(Host)
public MessageViewModel(IScreen Host, IResult result, bool showCloseButton = true) : base(Host)
{
ShowCloseButton = showCloseButton;
Message = result.Message;
if(result.Succeeded)

View File

@ -24,13 +24,13 @@ public class PreChecksViewModel : ViewModelBase
set => this.RaiseAndSetIfChanged(ref _installPath, value);
}
public bool AllowInstall
public bool AllowInstall
{
get => _allowInstall;
set => this.RaiseAndSetIfChanged(ref _allowInstall, value);
}
public PreChecksViewModel(IScreen host) : base(host)
public PreChecksViewModel(IScreen host, Action? dismissUpdateCard) : base(host)
{
var data = ServiceHelper.Get<InternalData?>();
var installer = ServiceHelper.Get<InstallController?>();
@ -51,9 +51,15 @@ public class PreChecksViewModel : ViewModelBase
data.TargetInstallPath = Environment.CurrentDirectory;
InstallPath = data.TargetInstallPath;
StartInstallCommand = ReactiveCommand.Create(() => NavigateTo(new InstallViewModel(HostScreen)));
StartInstallCommand = ReactiveCommand.Create(() =>
{
dismissUpdateCard?.Invoke();
NavigateTo(new InstallViewModel(HostScreen));
});
ShowDetailedViewCommand = ReactiveCommand.Create(() =>
{
dismissUpdateCard?.Invoke();
Log.Logger.Information("Opening Detailed PreCheck View");
NavigateTo(new DetailedPreChecksViewModel(HostScreen));
});

View File

@ -31,7 +31,17 @@
XButtonCommand="{Binding CloseCommand}"
MinButtonCommand="{Binding MinimizeCommand}"
/>
<rxui:RoutedViewHost Router="{Binding Router}" Grid.Row="1"/>
<cc:UpdateInfoCard Grid.Row="1" Padding="10"
VerticalAlignment="Top" HorizontalAlignment="Left"
InfoText="{Binding UpdateInfo.UpdateInfoText}"
ShowUpdateCard="{Binding UpdateInfo.UpdateAvailable}"
NotNowCommand="{Binding DismissUpdateCommand}"
UpdateInstallerCommand="{Binding UpdateInstallerCommand}"
Updating="{Binding UpdateInfo.Updating}"
DownloadProgress="{Binding UpdateInfo.DownloadProgress}"
/>
</Grid>
</Window>

View File

@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<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"
@ -31,6 +31,7 @@
Content="Close" Command="{Binding CloseCommand}"
FontSize="15" FontWeight="SemiBold"
Classes.yellow="{Binding !HasErrors}"
IsVisible="{Binding ShowCloseButton}"
HorizontalAlignment="Center"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Padding="20 10"