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/Installer Tasks/ReleaseCheckTask.cs b/SPTInstaller/Installer Tasks/ReleaseCheckTask.cs
index e28b14e..926a812 100644
--- a/SPTInstaller/Installer Tasks/ReleaseCheckTask.cs
+++ b/SPTInstaller/Installer Tasks/ReleaseCheckTask.cs
@@ -20,8 +20,6 @@ public class ReleaseCheckTask : InstallerTaskBase
{
try
{
- Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1";
-
var repo = new RepositoryApi(Configuration.Default);
SetStatus("Checking SPT Releases", "", null, ProgressStyle.Indeterminate);
diff --git a/SPTInstaller/Models/InstallerUpdateInfo.cs b/SPTInstaller/Models/InstallerUpdateInfo.cs
new file mode 100644
index 0000000..c798f97
--- /dev/null
+++ b/SPTInstaller/Models/InstallerUpdateInfo.cs
@@ -0,0 +1,128 @@
+using Gitea.Api;
+using Gitea.Client;
+using ReactiveUI;
+using Serilog;
+using SPTInstaller.Helpers;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+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
+ {
+ get => _updateAvailable;
+ set => this.RaiseAndSetIfChanged(ref _updateAvailable, value);
+ }
+
+ private bool _updating;
+ public bool Updating
+ {
+ get => _updating;
+ set => this.RaiseAndSetIfChanged(ref _updating, value);
+ }
+
+ private int _downloadProgress;
+ public int DownloadProgress
+ {
+ get => _downloadProgress;
+ set => this.RaiseAndSetIfChanged(ref _downloadProgress, value);
+ }
+
+ public async Task UpdateInstaller()
+ {
+ 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")}" }
+ });
+ }
+
+ private async Task DownloadNewInstaller()
+ {
+ 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
+ {
+ var repo = new RepositoryApi(Configuration.Default);
+
+ var releases = await repo.RepoListReleasesAsync("CWX", "SPT-AKI-Installer");
+
+ if (releases == null || releases.Count == 0)
+ return;
+
+ var latest = releases.FindAll(x => !x.Prerelease)[0];
+
+ if (latest == null)
+ return;
+
+ var latestVersion = new Version(latest.TagName);
+
+ if (latestVersion == null || latestVersion <= currentVersion)
+ return;
+
+ UpdateAvailable = 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");
+ }
+
+ 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 7587cb0..2893b8b 100644
--- a/SPTInstaller/SPTInstaller.csproj
+++ b/SPTInstaller/SPTInstaller.csproj
@@ -9,14 +9,19 @@
icon.ico
Assets\icon.ico
Debug;Release;TEST
- 2.5
- 2.5
+ 2.6
+ 2.6
+
+
+
+
+
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 8b86439..e66ee14 100644
--- a/SPTInstaller/ViewModels/MainWindowViewModel.cs
+++ b/SPTInstaller/ViewModels/MainWindowViewModel.cs
@@ -1,7 +1,10 @@
using Avalonia;
+using Gitea.Client;
using ReactiveUI;
using Serilog;
+using SPTInstaller.Models;
using System.Reflection;
+using System.Threading.Tasks;
namespace SPTInstaller.ViewModels;
@@ -9,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
@@ -19,14 +23,21 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
public MainWindowViewModel()
{
- string? version = Assembly.GetExecutingAssembly().GetName()?.Version?.ToString();
+ Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1";
- Title = $"SPT Installer {"v" + version ?? "--unknown version--"}";
+ Version? version = Assembly.GetExecutingAssembly().GetName()?.Version;
+
+ Title = $"SPT Installer {"v" + version?.ToString() ?? "--unknown version--"}";
Log.Information($"========= {Title} Started =========");
Log.Information(Environment.OSVersion.VersionString);
- Router.Navigate.Execute(new PreChecksViewModel(this));
+ Task.Run(async () =>
+ {
+ await UpdateInfo.CheckForUpdates(version);
+ });
+
+ Router.Navigate.Execute(new PreChecksViewModel(this, DismissUpdateCommand));
}
public void CloseCommand()
@@ -45,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 659652b..4894196 100644
--- a/SPTInstaller/Views/MainWindow.axaml
+++ b/SPTInstaller/Views/MainWindow.axaml
@@ -1,4 +1,4 @@
-
-
+
@@ -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 @@
-