diff --git a/SPTInstaller/Assets/Styles.axaml b/SPTInstaller/Assets/Styles.axaml
index 401fbc5..7f446d4 100644
--- a/SPTInstaller/Assets/Styles.axaml
+++ b/SPTInstaller/Assets/Styles.axaml
@@ -168,7 +168,29 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SPTInstaller/CustomControls/UpdateInfoCard.axaml.cs b/SPTInstaller/CustomControls/UpdateInfoCard.axaml.cs
new file mode 100644
index 0000000..a6c004f
--- /dev/null
+++ b/SPTInstaller/CustomControls/UpdateInfoCard.axaml.cs
@@ -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 ShowUpdateCardProperty =
+ AvaloniaProperty.Register(nameof(ShowUpdateCard));
+
+ public bool Updating
+ {
+ get => GetValue(UpdatingProperty);
+ set => SetValue(UpdatingProperty, value);
+ }
+ public static readonly StyledProperty UpdatingProperty =
+ AvaloniaProperty.Register(nameof(Updating));
+
+ public string InfoText
+ {
+ get => GetValue(InfoTextProperty);
+ set => SetValue(InfoTextProperty, value);
+ }
+ public static readonly StyledProperty InfoTextProperty =
+ AvaloniaProperty.Register(nameof(InfoText));
+
+ public int DownloadProgress
+ {
+ get => GetValue(DownloadProgressProperty);
+ set => SetValue(DownloadProgressProperty, value);
+ }
+ public static readonly StyledProperty DownloadProgressProperty =
+ AvaloniaProperty.Register(nameof(DownloadProgress));
+
+ public ICommand NotNowCommand
+ {
+ get => GetValue(NotNowCommandProperty);
+ set => SetValue(NotNowCommandProperty, value);
+ }
+ public static readonly StyledProperty NotNowCommandProperty =
+ AvaloniaProperty.Register(nameof(NotNowCommand));
+
+ public ICommand UpdateInstallerCommand
+ {
+ get => GetValue(UpdateInstallerCommandProperty);
+ set => SetValue(UpdateInstallerCommandProperty, value);
+ }
+ public static readonly StyledProperty UpdateInstallerCommandProperty =
+ AvaloniaProperty.Register(nameof(UpdateInstallerCommand));
+}
diff --git a/SPTInstaller/Helpers/DownloadCacheHelper.cs b/SPTInstaller/Helpers/DownloadCacheHelper.cs
index 30cb479..3fa6ebd 100644
--- a/SPTInstaller/Helpers/DownloadCacheHelper.cs
+++ b/SPTInstaller/Helpers/DownloadCacheHelper.cs
@@ -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 GetOrDownloadFileAsync(string fileName, string targetLink, IProgress 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 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
{
diff --git a/SPTInstaller/Helpers/FileHelper.cs b/SPTInstaller/Helpers/FileHelper.cs
index 56de581..e240fee 100644
--- a/SPTInstaller/Helpers/FileHelper.cs
+++ b/SPTInstaller/Helpers/FileHelper.cs
@@ -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);
+ }
+ }
+
}
\ No newline at end of file
diff --git a/SPTInstaller/Models/InstallerUpdateInfo.cs b/SPTInstaller/Models/InstallerUpdateInfo.cs
index b19db7d..c798f97 100644
--- a/SPTInstaller/Models/InstallerUpdateInfo.cs
+++ b/SPTInstaller/Models/InstallerUpdateInfo.cs
@@ -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 CheckForUpdates()
+ private async Task DownloadNewInstaller()
{
- CheckingForUpdates = true;
+ UpdateInfoText = $"Downloading new installer v{_newVersion}";
+
+ var progress = new Progress(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;
}
}
diff --git a/SPTInstaller/Resources/update.ps1 b/SPTInstaller/Resources/update.ps1
new file mode 100644
index 0000000..7e75bec
--- /dev/null
+++ b/SPTInstaller/Resources/update.ps1
@@ -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"
\ No newline at end of file
diff --git a/SPTInstaller/SPTInstaller.csproj b/SPTInstaller/SPTInstaller.csproj
index 7f49e5a..2893b8b 100644
--- a/SPTInstaller/SPTInstaller.csproj
+++ b/SPTInstaller/SPTInstaller.csproj
@@ -17,6 +17,11 @@
+
+
+
+
+
diff --git a/SPTInstaller/ViewModels/DetailedPreChecksViewModel.cs b/SPTInstaller/ViewModels/DetailedPreChecksViewModel.cs
index 83c5618..eece790 100644
--- a/SPTInstaller/ViewModels/DetailedPreChecksViewModel.cs
+++ b/SPTInstaller/ViewModels/DetailedPreChecksViewModel.cs
@@ -3,7 +3,7 @@
namespace SPTInstaller.ViewModels;
public class DetailedPreChecksViewModel : PreChecksViewModel
{
- public DetailedPreChecksViewModel(IScreen host) : base(host)
+ public DetailedPreChecksViewModel(IScreen host) : base(host, null)
{
}
}
diff --git a/SPTInstaller/ViewModels/MainWindowViewModel.cs b/SPTInstaller/ViewModels/MainWindowViewModel.cs
index 996ddb7..e66ee14 100644
--- a/SPTInstaller/ViewModels/MainWindowViewModel.cs
+++ b/SPTInstaller/ViewModels/MainWindowViewModel.cs
@@ -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();
+ }
}
\ No newline at end of file
diff --git a/SPTInstaller/ViewModels/MessageViewModel.cs b/SPTInstaller/ViewModels/MessageViewModel.cs
index ab8c097..7324f24 100644
--- a/SPTInstaller/ViewModels/MessageViewModel.cs
+++ b/SPTInstaller/ViewModels/MessageViewModel.cs
@@ -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)
diff --git a/SPTInstaller/ViewModels/PreChecksViewModel.cs b/SPTInstaller/ViewModels/PreChecksViewModel.cs
index 4580ca3..97793fe 100644
--- a/SPTInstaller/ViewModels/PreChecksViewModel.cs
+++ b/SPTInstaller/ViewModels/PreChecksViewModel.cs
@@ -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();
var installer = ServiceHelper.Get();
@@ -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));
});
diff --git a/SPTInstaller/Views/MainWindow.axaml b/SPTInstaller/Views/MainWindow.axaml
index 089734d..4894196 100644
--- a/SPTInstaller/Views/MainWindow.axaml
+++ b/SPTInstaller/Views/MainWindow.axaml
@@ -31,7 +31,17 @@
XButtonCommand="{Binding CloseCommand}"
MinButtonCommand="{Binding MinimizeCommand}"
/>
-
+
+
+
diff --git a/SPTInstaller/Views/MessageView.axaml b/SPTInstaller/Views/MessageView.axaml
index a4f8d76..fc1464a 100644
--- a/SPTInstaller/Views/MessageView.axaml
+++ b/SPTInstaller/Views/MessageView.axaml
@@ -1,4 +1,4 @@
-