using System.Collections.ObjectModel; using System.Diagnostics; using System.Reflection; using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Threading; using DialogHostAvalonia; using Newtonsoft.Json; using ReactiveUI; using Serilog; using SPTInstaller.Controllers; using SPTInstaller.CustomControls; using SPTInstaller.CustomControls.Dialogs; using SPTInstaller.Helpers; using SPTInstaller.Models; using SPTInstaller.Models.ReleaseInfo; namespace SPTInstaller.ViewModels; public class PreChecksViewModel : ViewModelBase { private bool _hasPreCheckSelected; public bool HasPreCheckSelected { get => _hasPreCheckSelected; set => this.RaiseAndSetIfChanged(ref _hasPreCheckSelected, value); } public ObservableCollection PreChecks { get; set; } = new(ServiceHelper.GetAll()); public ICommand SelectPreCheckCommand { get; set; } public ICommand StartInstallCommand { get; set; } public ICommand UpdateInstallerCommand { get; set; } public ICommand DismissUpdateCommand { get; set; } public ICommand WhatsNewCommand { get; set; } public ICommand LaunchWithDebug { get; set; } public InstallerUpdateInfo UpdateInfo { get; set; } = new(); private bool _debugging; public bool Debugging { get => _debugging; set => this.RaiseAndSetIfChanged(ref _debugging, value); } private string _installPath; public string InstallPath { get => _installPath; set => this.RaiseAndSetIfChanged(ref _installPath, value); } private string _installButtonText; public string InstallButtonText { get => _installButtonText; set => this.RaiseAndSetIfChanged(ref _installButtonText, value); } private bool _allowInstall; public bool AllowInstall { get => _allowInstall; set => this.RaiseAndSetIfChanged(ref _allowInstall, value); } private bool _allowDetailsButton = false; public bool AllowDetailsButton { get => _allowDetailsButton; set => this.RaiseAndSetIfChanged(ref _allowDetailsButton, value); } private string _cacheInfoText; public string CacheInfoText { get => _cacheInfoText; set => this.RaiseAndSetIfChanged(ref _cacheInfoText, value); } private StatusSpinner.SpinnerState _cacheCheckState; public StatusSpinner.SpinnerState CacheCheckState { get => _cacheCheckState; set => this.RaiseAndSetIfChanged(ref _cacheCheckState, value); } private StatusSpinner.SpinnerState _installButtonCheckState; public StatusSpinner.SpinnerState InstallButtonCheckState { get => _installButtonCheckState; set => this.RaiseAndSetIfChanged(ref _installButtonCheckState, value); } private void ReCheckRequested(object? sender, EventArgs e) { Task.Run(async () => { if (sender is InstallController installer) { var result = await installer.RunPreChecks(); AllowInstall = result.Succeeded; } }); } public PreChecksViewModel(IScreen host, bool debugging) : base(host) { Debugging = debugging; var data = ServiceHelper.Get(); var installer = ServiceHelper.Get(); installer.RecheckRequested += ReCheckRequested; InstallButtonText = "Please wait ..."; InstallButtonCheckState = StatusSpinner.SpinnerState.Pending; if (data == null || installer == null) { NavigateTo(new MessageViewModel(HostScreen, Result.FromError("Failed to get required service for prechecks"))); return; } data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath(); data.TargetInstallPath = Environment.CurrentDirectory; InstallPath = data.TargetInstallPath; Log.Information($"Install Path: {FileHelper.GetRedactedPath(InstallPath)}"); #if !TEST if (data.OriginalGamePath == null) { NavigateTo(new MessageViewModel(HostScreen, Result.FromError("Could not find EFT install.\n\nDo you own and have the game installed?"))); return; } #endif if (data.OriginalGamePath == data.TargetInstallPath) { Log.CloseAndFlush(); var logFiles = Directory.GetFiles(InstallPath, "spt-aki-installer_*.log"); // remove log file from original game path if they exist foreach (var file in logFiles) { try { File.Delete(file); } catch { } } NavigateTo(new MessageViewModel(HostScreen, Result.FromError( "Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide"), noLog: true)); return; } Task.Run(async () => { if (FileHelper.CheckPathForProblemLocations(InstallPath, out var failedCheck)) { switch (failedCheck.CheckAction) { case PathCheckAction.Warn: { await Dispatcher.UIThread.InvokeAsync(async () => { Log.Warning("Problem path detected, confirming install path ..."); var confirmation = await DialogHost.Show(new ConfirmationDialog( $"We suspect you may be installing into a problematic folder: {failedCheck.Target}.\nYou might want to consider installing somewhere else to avoid issues.\n\nAre you sure you want to install to this path?\n{InstallPath}")); if (confirmation == null || !bool.TryParse(confirmation.ToString(), out var confirm) || !confirm) { Log.Information("User declined install path, exiting"); Environment.Exit(0); } }); break; } case PathCheckAction.Deny: { Log.Error("Problem path detected, install denied"); NavigateTo(new MessageViewModel(HostScreen, Result.FromError( $"We suspect you may be installing into a problematic folder: {failedCheck.Target}.\nWe won't be letting you install here. Please move the installer to another folder.\nSuggestion: a folder under your drive root, such as 'C:\\spt\\'\nDenied Path: {InstallPath}"))); break; } default: throw new ArgumentOutOfRangeException(); } Log.Information("User accepted install path"); } }); LaunchWithDebug = ReactiveCommand.Create(async () => { try { var installerPath = Path.Join(_installPath, "SPTInstaller.exe"); Process.Start(new ProcessStartInfo() { FileName = installerPath, Arguments = "debug" }); Environment.Exit(0); } catch (Exception ex) { Log.Error(ex, "Failed to enter debug mode"); } }); SelectPreCheckCommand = ReactiveCommand.Create(async (PreCheckBase check) => { foreach (var precheck in PreChecks) { if (check.Id == precheck.Id) { precheck.IsSelected = true; HasPreCheckSelected = true; continue; } precheck.IsSelected = false; } }); StartInstallCommand = ReactiveCommand.Create(async () => { UpdateInfo.Show = false; NavigateTo(new InstallViewModel(HostScreen)); }); UpdateInstallerCommand = ReactiveCommand.Create(async () => { AllowDetailsButton = false; AllowInstall = false; await UpdateInfo.UpdateInstaller(); }); DismissUpdateCommand = ReactiveCommand.Create(() => { UpdateInfo.Show = false; }); WhatsNewCommand = ReactiveCommand.Create(async () => await DialogHost.Show(new ChangeLogDialog(UpdateInfo.NewVersion.ToString(), UpdateInfo.ChangeLog))); Task.Run(async () => { // run prechecks var result = await installer.RunPreChecks(); // check for updates await UpdateInfo.CheckForUpdates(Assembly.GetExecutingAssembly().GetName()?.Version); // get latest spt version InstallButtonText = "Getting latest release ..."; InstallButtonCheckState = StatusSpinner.SpinnerState.Running; var progress = new Progress((d) => { }); var akiReleaseInfoFile = await DownloadCacheHelper.GetOrDownloadFileAsync("release.json", DownloadCacheHelper.ReleaseMirrorUrl, progress, DownloadCacheHelper.SuggestedTtl); if (akiReleaseInfoFile == null) { InstallButtonText = "Could not get SPT release metadata"; InstallButtonCheckState = StatusSpinner.SpinnerState.Error; return; } var akiReleaseInfo = JsonConvert.DeserializeObject(File.ReadAllText(akiReleaseInfoFile.FullName)); if (akiReleaseInfo == null) { InstallButtonText = "Could not parse latest SPT release"; InstallButtonCheckState = StatusSpinner.SpinnerState.Error; return; } InstallButtonText = $"Start Install: SPT v{akiReleaseInfo.AkiVersion}"; InstallButtonCheckState = StatusSpinner.SpinnerState.OK; AllowDetailsButton = true; AllowInstall = result.Succeeded; }); Task.Run(() => { CacheInfoText = "Getting cache size ..."; CacheCheckState = StatusSpinner.SpinnerState.Running; CacheInfoText = $"Cache Size: {DownloadCacheHelper.GetCacheSizeText()}"; CacheCheckState = StatusSpinner.SpinnerState.OK; }); } }