installer updating to use r2

This commit is contained in:
IsWaffle 2024-05-01 10:32:22 -04:00
parent a7af608d56
commit 4968f48e8b
5 changed files with 142 additions and 256 deletions

View File

@ -1,65 +0,0 @@
<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>
<Style Selector="ProgressBar">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="ProgressBar.checking">
<Setter Property="IsIndeterminate" Value="True"/>
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="ProgressBar.updating">
<Setter Property="IsIndeterminate" Value="false"/>
<Setter Property="IsVisible" Value="True"/>
</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 UpdateAvailable, 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 UpdateAvailable, 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}}"
Classes.updating="{Binding Updating, RelativeSource={RelativeSource AncestorType=UserControl}}"
Classes.checking="{Binding IndeterminateProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</Grid>
</UserControl>

View File

@ -1,76 +0,0 @@
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 bool UpdateAvailable
{
get => GetValue(UpdateAvailableProperty);
set => SetValue(UpdateAvailableProperty, value);
}
public static readonly StyledProperty<bool> UpdateAvailableProperty =
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(UpdateAvailable));
public bool IndeterminateProgress
{
get => GetValue(IndeterminateProgressProperty);
set => SetValue(IndeterminateProgressProperty, value);
}
public static readonly StyledProperty<bool> IndeterminateProgressProperty =
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(IndeterminateProgress));
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

@ -8,11 +8,15 @@ namespace SPTInstaller.Helpers;
public static class DownloadCacheHelper
{
private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromHours(1) };
public 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");
public static string ReleaseMirrorUrl = "https://spt-releases.modd.in/release.json";
public static string PatchMirrorUrl = "https://slugma.waffle-lord.net/mirrors.json";
public static string InstallerUrl = "https://ligma.waffle-lord.net/SPTInstaller.exe";
public static string InstallerInfoUrl = "https://ligma.waffle-lord.net/installer.json";
public static string GetCacheSizeText()
{
if (!Directory.Exists(CachePath))
@ -21,24 +25,24 @@ public static class DownloadCacheHelper
Log.Information(message);
return message;
}
var cacheDir = new DirectoryInfo(CachePath);
var cacheSize = DirectorySizeHelper.GetSizeOfDirectory(cacheDir);
if (cacheSize == -1)
{
var message = "An error occurred while getting the cache size :(";
Log.Error(message);
return message;
}
if (cacheSize == 0)
return "Empty";
return DirectorySizeHelper.SizeSuffix(cacheSize);
}
/// <summary>
/// Check if a file in the cache already exists
/// </summary>
@ -46,42 +50,42 @@ public static class DownloadCacheHelper
/// <param name="expectedHash">The expected hash of the file in the cache</param>
/// <param name="cachedFile">The file found in the cache; null if no file is found</param>
/// <returns>True if the file is in the cache and its hash matches the expected hash, otherwise false</returns>
public static bool CheckCache(string fileName, string expectedHash, out FileInfo cachedFile)
public static bool CheckCache(string fileName, string expectedHash, out FileInfo cachedFile)
=> CheckCache(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
private static bool CheckCache(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
{
fileInCache = cacheFile;
try
{
cacheFile.Refresh();
Directory.CreateDirectory(CachePath);
if (!cacheFile.Exists || expectedHash == null)
{
Log.Information($"{cacheFile.Name} {(cacheFile.Exists ? "is in cache" : "NOT in cache")}");
Log.Information($"Expected hash: {(expectedHash == null ? "not provided" : expectedHash)}");
return false;
}
if (FileHashHelper.CheckHash(cacheFile, expectedHash))
{
fileInCache = cacheFile;
Log.Information("Hashes MATCH");
return true;
}
Log.Warning("Hashes DO NOT MATCH");
return false;
}
catch(Exception ex)
catch (Exception ex)
{
Log.Error(ex, "Something went wrong during hashing");
return false;
}
}
/// <summary>
/// Download a file to the cache folder
/// </summary>
@ -90,28 +94,29 @@ public static class DownloadCacheHelper
/// <param name="progress">A provider for progress updates</param>
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
/// <remarks>If the file exists, it is deleted before downloading</remarks>
public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink, IProgress<double> progress)
public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink,
IProgress<double> progress)
{
Directory.CreateDirectory(CachePath);
var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
try
{
if (outputFile.Exists)
outputFile.Delete();
// Use the provided extension method
using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
await _httpClient.DownloadDataAsync(targetLink, file, progress);
outputFile.Refresh();
if (!outputFile.Exists)
{
Log.Error("Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
return null;
}
return outputFile;
}
catch (Exception ex)
@ -120,7 +125,7 @@ public static class DownloadCacheHelper
return null;
}
}
/// <summary>
/// Download a file to the cache folder
/// </summary>
@ -132,36 +137,36 @@ public static class DownloadCacheHelper
{
Directory.CreateDirectory(CachePath);
var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
try
{
if (outputFile.Exists)
outputFile.Delete();
using var patcherFileStream = outputFile.Open(FileMode.Create);
{
await downloadStream.CopyToAsync(patcherFileStream);
}
patcherFileStream.Close();
outputFile.Refresh();
if (!outputFile.Exists)
{
Log.Error("Failed to download file from stream: {name}", outputFileName);
return null;
}
return outputFile;
}
catch(Exception ex)
catch (Exception ex)
{
Log.Error(ex, "Failed to download file from stream: {fileName}", outputFileName);
return null;
}
}
/// <summary>
/// Get the file from cache or download it
/// </summary>
@ -171,13 +176,14 @@ public static class DownloadCacheHelper
/// <param name="expectedHash">The expected hash of the cached file</param>
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
/// <remarks>Use <see cref="DownloadFileAsync(string, string, IProgress{double})"/> if you don't have an expected cache file hash</remarks>
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash)
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink,
IProgress<double> progress, string expectedHash)
{
try
{
if (CheckCache(fileName, expectedHash, out var cacheFile))
return cacheFile;
return await DownloadFileAsync(fileName, targetLink, progress);
}
catch (Exception ex)
@ -186,7 +192,7 @@ public static class DownloadCacheHelper
return null;
}
}
/// <summary>
/// Get the file from cache or download it
/// </summary>
@ -195,13 +201,14 @@ public static class DownloadCacheHelper
/// <param name="expectedHash">The expected hash of the cached file</param>
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
/// <remarks>Use <see cref="DownloadFileAsync(string, Stream)"/> if you don't have an expected cache file hash</remarks>
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash)
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream,
string expectedHash)
{
try
{
if (CheckCache(fileName, expectedHash, out var cacheFile))
return cacheFile;
return await DownloadFileAsync(fileName, fileDownloadStream);
}
catch (Exception ex)

View File

@ -0,0 +1,7 @@
namespace SPTInstaller.Models;
public class InstallerInfo
{
public string LatestVersion { get; set; }
public string ChangeLog { get; set; }
}

View File

@ -3,105 +3,118 @@ using Serilog;
using SPTInstaller.Helpers;
using System.Diagnostics;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace SPTInstaller.Models;
public class InstallerUpdateInfo : ReactiveObject
{
private Version? _newVersion;
public string NewInstallerUrl = "";
public string ChangeLog = "";
private string _updateInfoText = "";
public string UpdateInfoText
{
get => _updateInfoText;
set => this.RaiseAndSetIfChanged(ref _updateInfoText, value);
}
private bool _show = false;
public bool Show
{
get => _show;
set => this.RaiseAndSetIfChanged(ref _show, value);
}
private bool _updating = false;
public bool Updating
{
get => _updating;
set => this.RaiseAndSetIfChanged(ref _updating, value);
}
private bool _updateAvailable = false;
public bool UpdateAvailable
{
get => _updateAvailable;
set => this.RaiseAndSetIfChanged(ref _updateAvailable, value);
}
private bool _checkingForUpdates = false;
public bool CheckingForUpdates
{
get => _checkingForUpdates;
set => this.RaiseAndSetIfChanged(ref _checkingForUpdates, value);
}
private int _downloadProgress;
public int DownloadProgress
{
get => _downloadProgress;
set => this.RaiseAndSetIfChanged(ref _downloadProgress, value);
}
public async Task UpdateInstaller()
{
Updating = true;
UpdateAvailable = false;
var updater = new FileInfo(Path.Join(DownloadCacheHelper.CachePath, "update.ps1"));
if (!FileHelper.StreamAssemblyResourceOut("update.ps1", updater.FullName))
{
Log.Fatal("Failed to prepare update file");
return;
}
if (!updater.Exists)
{
UpdateInfoText = "Failed to get updater from resources :(";
return;
}
var newInstallerPath = await DownloadNewInstaller();
if(string.IsNullOrWhiteSpace(newInstallerPath))
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")}" }
ArgumentList =
{
"-ExecutionPolicy", "Bypass", "-File", $"{updater.FullName}", $"{newInstallerPath}",
$"{Path.Join(Environment.CurrentDirectory, "SPTInstaller.exe")}"
}
});
}
private async Task<string> DownloadNewInstaller()
{
UpdateInfoText = $"Downloading installer v{_newVersion}";
var progress = new Progress<double>(x => DownloadProgress = (int)x);
var file = await DownloadCacheHelper.DownloadFileAsync("SPTInstller.exe", NewInstallerUrl, progress);
var file = await DownloadCacheHelper.DownloadFileAsync("SPTInstaller.exe", DownloadCacheHelper.InstallerUrl,
progress);
if (file == null || !file.Exists)
{
UpdateInfoText = "Failed to download new installer :(";
return "";
}
return file.FullName;
}
private void EndCheck(string infoText, bool updateAvailable, bool log = true)
{
if (log)
@ -114,58 +127,58 @@ public class InstallerUpdateInfo : ReactiveObject
CheckingForUpdates = false;
UpdateAvailable = updateAvailable;
}
// public async Task CheckForUpdates(Version? currentVersion)
// {
// if (currentVersion == null)
// return;
//
// UpdateInfoText = "Checking for installer updates";
// Show = true;
// CheckingForUpdates = true;
//
// try
// {
// var repo = new RepositoryApi(Configuration.Default);
//
// var releases = await repo.RepoListReleasesAsync("CWX", "SPT-AKI-Installer");
//
// if (releases == null || releases.Count == 0)
// {
// EndCheck("No releases available", false);
// return;
// }
//
// var latest = releases.FindAll(x => !x.Prerelease)[0];
//
// if (latest == null)
// {
// EndCheck("could not get latest release", false);
// return;
// }
//
// var latestVersion = new Version(latest.TagName);
//
// if (latestVersion == null || latestVersion <= currentVersion)
// {
// EndCheck("No updates available", false);
// return;
// }
//
// _newVersion = latestVersion;
//
// NewInstallerUrl = latest.Assets[0].BrowserDownloadUrl;
//
// EndCheck($"Update available: v{latestVersion}", true);
//
// return;
// }
// catch (Exception ex)
// {
// EndCheck(ex.Message, false, false);
// Log.Error(ex, "Failed to check for updates");
// }
//
// return;
// }
}
public async Task CheckForUpdates(Version? currentVersion)
{
if (currentVersion == null)
return;
UpdateInfoText = "Checking for installer updates";
Show = true;
CheckingForUpdates = true;
try
{
var installerInfoFile =
await DownloadCacheHelper.DownloadFileAsync("installer.json", DownloadCacheHelper.InstallerInfoUrl,
null);
if (installerInfoFile == null)
{
EndCheck("Failed to download installer info", false);
return;
}
var installerInfo =
JsonConvert.DeserializeObject<InstallerInfo>(File.ReadAllText(installerInfoFile.FullName));
if (installerInfo == null)
{
EndCheck("Failed to parse installer info json", false);
return;
}
var latestVersion = new Version(installerInfo.LatestVersion);
if (latestVersion <= currentVersion)
{
EndCheck("No updates available", false);
return;
}
_newVersion = latestVersion;
ChangeLog = installerInfo.ChangeLog;
EndCheck($"Update available: v{latestVersion}", true);
return;
}
catch (Exception ex)
{
EndCheck(ex.Message, false, false);
Log.Error(ex, "Failed to check for updates");
}
return;
}
}