From 4031faf6e0597d028a79b85699a8d0c1f1417dd1 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Mon, 27 Dec 2021 17:24:53 -0700 Subject: [PATCH] Several more fixes --- .../View Models/Compilers/ISubCompilerVM.cs | 5 +- .../View Models/Compilers/MO2CompilerVM.cs | 18 +- .../View Models/Installers/ISubInstallerVM.cs | 3 +- .../View Models/Installers/InstallerVM.cs | 777 +++++++++--------- Wabbajack.Lib/Consts.cs | 1 + 5 files changed, 406 insertions(+), 398 deletions(-) diff --git a/Wabbajack.App.Wpf/View Models/Compilers/ISubCompilerVM.cs b/Wabbajack.App.Wpf/View Models/Compilers/ISubCompilerVM.cs index b781da89..8444a96a 100644 --- a/Wabbajack.App.Wpf/View Models/Compilers/ISubCompilerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Compilers/ISubCompilerVM.cs @@ -1,8 +1,7 @@ using System; using System.Threading.Tasks; -using ReactiveUI; -using Wabbajack.Common; -using Wabbajack.Lib; +using Wabbajack.Compiler; +using Wabbajack.DTOs; namespace Wabbajack { diff --git a/Wabbajack.App.Wpf/View Models/Compilers/MO2CompilerVM.cs b/Wabbajack.App.Wpf/View Models/Compilers/MO2CompilerVM.cs index 4e55abb9..4c53206e 100644 --- a/Wabbajack.App.Wpf/View Models/Compilers/MO2CompilerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Compilers/MO2CompilerVM.cs @@ -10,12 +10,18 @@ using System.Reactive.Linq; using System.Threading.Tasks; using DynamicData; using Wabbajack.Common; +using Wabbajack.Compiler; +using Wabbajack.DTOs; +using Wabbajack.DTOs.GitHub; using Wabbajack.Lib; using Wabbajack.Lib.AuthorApi; using Wabbajack.Lib.Extensions; using Wabbajack.Lib.FileUploader; using Wabbajack.Lib.GitHub; +using Wabbajack.Paths; +using Wabbajack.Paths.IO; using WebSocketSharp; +using Consts = Wabbajack.Lib.Consts; namespace Wabbajack { @@ -112,7 +118,7 @@ namespace Wabbajack ModListLocation.AdditionalError = this.WhenAny(x => x.Mo2Folder) .Select(moFolder => { - if (moFolder.IsDirectory) return ErrorResponse.Success; + if (moFolder.DirectoryExists()) return ErrorResponse.Success; return ErrorResponse.Fail($"MO2 folder could not be located from the given ModList location.{Environment.NewLine}Make sure your ModList is inside a valid MO2 distribution."); }); @@ -167,7 +173,7 @@ namespace Wabbajack // If Mo2 folder changes and download location is empty, set it for convenience this.WhenAny(x => x.Mo2Folder) .DelayInitial(TimeSpan.FromMilliseconds(100), RxApp.MainThreadScheduler) - .Where(x => x.IsDirectory) + .Where(x => x.DirectoryExists()) .FlowSwitch( (this).WhenAny(x => x.DownloadLocation.Exists) .Invert()) @@ -197,11 +203,11 @@ namespace Wabbajack if (Parent.OutputLocation.TargetPath == default) { - outputFile = (profileName + Consts.ModListExtension).RelativeTo(AbsolutePath.EntryPoint); + outputFile = (profileName.ToRelativePath().WithExtension(Ext.Wabbajack)).RelativeTo(KnownFolders.EntryPoint); } else { - outputFile = Parent.OutputLocation.TargetPath.Combine(profileName + Consts.ModListExtension); + outputFile = Parent.OutputLocation.TargetPath.Combine(profileName).WithExtension(Ext.Wabbajack); } try @@ -216,7 +222,7 @@ namespace Wabbajack Version = ModlistSettings.Version, }; } - + /* TODO if (ModListLocation.TargetPath.FileName == Consts.NativeSettingsJson) { var settings = ModListLocation.TargetPath.FromJson(); @@ -258,6 +264,8 @@ namespace Wabbajack var success = await ActiveCompilation.Begin(); return GetResponse.Create(success, ActiveCompilation.ModList); } + */ + return GetResponse.Create(true, ActiveCompilation.ModList); } finally { diff --git a/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs index 30a2f8e9..c3178832 100644 --- a/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using ReactiveUI; using Wabbajack.Common; +using Wabbajack.Installer; using Wabbajack.Lib; using Wabbajack.Lib.Interventions; @@ -13,7 +14,7 @@ namespace Wabbajack public interface ISubInstallerVM { InstallerVM Parent { get; } - AInstaller ActiveInstallation { get; } + IInstaller ActiveInstallation { get; } void Unload(); bool SupportsAfterInstallNavigation { get; } void AfterInstallNavigation(); diff --git a/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs index fe290367..ec916419 100644 --- a/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs @@ -1,466 +1,465 @@ using System; using ReactiveUI; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows; using System.Windows.Media.Imaging; -using Wabbajack.Common; using Wabbajack.Lib; using ReactiveUI.Fody.Helpers; using System.Windows.Media; using DynamicData; using DynamicData.Binding; using System.Reactive; -using System.Collections.Generic; -using System.Reactive.Subjects; -using System.Windows.Input; +using Microsoft.Extensions.Logging; using Microsoft.WindowsAPICodePack.Dialogs; using Microsoft.WindowsAPICodePack.Shell; -using Wabbajack.Common.IO; +using Wabbajack.Installer; using Wabbajack.Lib.Extensions; using Wabbajack.Lib.Interventions; using Wabbajack.Paths; using Wabbajack.RateLimiter; using Wabbajack.View_Models; +using Consts = Wabbajack.Lib.Consts; -namespace Wabbajack +namespace Wabbajack; + +enum ModManager { - public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM - { - public SlideShow Slideshow { get; } + Standard +} - public MainWindowVM MWVM { get; } +public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM +{ + public SlideShow Slideshow { get; } - private readonly ObservableAsPropertyHelper _modList; - public ModListVM ModList => _modList.Value; + public MainWindowVM MWVM { get; } - public FilePickerVM ModListLocation { get; } + private readonly ObservableAsPropertyHelper _modList; + public ModListVM ModList => _modList.Value; - private readonly ObservableAsPropertyHelper _installer; - public ISubInstallerVM Installer => _installer.Value; + public FilePickerVM ModListLocation { get; } - private readonly ObservableAsPropertyHelper _installing; - public bool Installing => _installing.Value; + private readonly ObservableAsPropertyHelper _installer; + public ISubInstallerVM Installer => _installer.Value; - [Reactive] - public bool StartedInstallation { get; set; } + private readonly ObservableAsPropertyHelper _installing; + public bool Installing => _installing.Value; - [Reactive] - public ErrorResponse? Completed { get; set; } + [Reactive] + public bool StartedInstallation { get; set; } - private readonly ObservableAsPropertyHelper _image; - public ImageSource Image => _image.Value; + [Reactive] + public ErrorResponse? Completed { get; set; } - private readonly ObservableAsPropertyHelper _titleText; - public string TitleText => _titleText.Value; + private readonly ObservableAsPropertyHelper _image; + public ImageSource Image => _image.Value; - private readonly ObservableAsPropertyHelper _authorText; - public string AuthorText => _authorText.Value; + private readonly ObservableAsPropertyHelper _titleText; + public string TitleText => _titleText.Value; - private readonly ObservableAsPropertyHelper _description; - public string Description => _description.Value; + private readonly ObservableAsPropertyHelper _authorText; + public string AuthorText => _authorText.Value; - private readonly ObservableAsPropertyHelper _progressTitle; - public string ProgressTitle => _progressTitle.Value; + private readonly ObservableAsPropertyHelper _description; + public string Description => _description.Value; - private readonly ObservableAsPropertyHelper _modListName; - public string ModListName => _modListName.Value; + private readonly ObservableAsPropertyHelper _progressTitle; + public string ProgressTitle => _progressTitle.Value; - private readonly ObservableAsPropertyHelper _percentCompleted; - public Percent PercentCompleted => _percentCompleted.Value; + private readonly ObservableAsPropertyHelper _modListName; + public string ModListName => _modListName.Value; - public ObservableCollectionExtended StatusList { get; } = new ObservableCollectionExtended(); - public ObservableCollectionExtended Log => MWVM.Log; + private readonly ObservableAsPropertyHelper _percentCompleted; + public Percent PercentCompleted => _percentCompleted.Value; - private readonly ObservableAsPropertyHelper _TargetManager; - public ModManager? TargetManager => _TargetManager.Value; + public ObservableCollectionExtended StatusList { get; } = new ObservableCollectionExtended(); + public ObservableCollectionExtended Log => MWVM.Log; - private readonly ObservableAsPropertyHelper _ActiveGlobalUserIntervention; - public IUserIntervention ActiveGlobalUserIntervention => _ActiveGlobalUserIntervention.Value; + private readonly ObservableAsPropertyHelper _TargetManager; + public ModManager? TargetManager => _TargetManager.Value; - private readonly ObservableAsPropertyHelper<(int CurrentCPUs, int DesiredCPUs)> _CurrentCpuCount; - public (int CurrentCPUs, int DesiredCPUs) CurrentCpuCount => _CurrentCpuCount.Value; + private readonly ObservableAsPropertyHelper _ActiveGlobalUserIntervention; + public IUserIntervention ActiveGlobalUserIntervention => _ActiveGlobalUserIntervention.Value; - private readonly ObservableAsPropertyHelper _LoadingModlist; - public bool LoadingModlist => _LoadingModlist.Value; + private readonly ObservableAsPropertyHelper<(int CurrentCPUs, int DesiredCPUs)> _CurrentCpuCount; + public (int CurrentCPUs, int DesiredCPUs) CurrentCpuCount => _CurrentCpuCount.Value; - // Command properties - public ReactiveCommand ShowManifestCommand { get; } - public ReactiveCommand OpenReadmeCommand { get; } - public ReactiveCommand VisitModListWebsiteCommand { get; } + private readonly ObservableAsPropertyHelper _LoadingModlist; + private readonly ILogger _logger; + public bool LoadingModlist => _LoadingModlist.Value; + + // Command properties + public ReactiveCommand ShowManifestCommand { get; } + public ReactiveCommand OpenReadmeCommand { get; } + public ReactiveCommand VisitModListWebsiteCommand { get; } - public ReactiveCommand CloseWhenCompleteCommand { get; } - public ReactiveCommand OpenLogsCommand { get; } - public ReactiveCommand GoToInstallCommand { get; } - public ReactiveCommand BeginCommand { get; } + public ReactiveCommand CloseWhenCompleteCommand { get; } + public ReactiveCommand OpenLogsCommand { get; } + public ReactiveCommand GoToInstallCommand { get; } + public ReactiveCommand BeginCommand { get; } - public InstallerVM(MainWindowVM mainWindowVM) : base(mainWindowVM) + public InstallerVM(ILogger logger, MainWindowVM mainWindowVM) : base(logger, mainWindowVM) + { + _logger = logger; + var downloadsPath = KnownFolders.Downloads.Path; + /* TODO + var skyDrivePath = KnownFolders.SkyDrive.Path; + + if (downloadsPath != null && AbsolutePath.EntryPoint.IsChildOf(new AbsolutePath(downloadsPath))) { - var downloadsPath = KnownFolders.Downloads.Path; - var skyDrivePath = KnownFolders.SkyDrive.Path; + logger.LogError(Error(new CriticalFailureIntervention( + "Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " + + "conflict with the operations Wabbajack needs to perform. Please move Wabbajack outside of your Downloads folder and then restart the app.", + "Cannot run inside Downloads", true)); + } - if (downloadsPath != null && AbsolutePath.EntryPoint.IsChildOf(new AbsolutePath(downloadsPath))) + if (skyDrivePath != null && AbsolutePath.EntryPoint.IsChildOf(new AbsolutePath(skyDrivePath))) + { + Utils.Error(new CriticalFailureIntervention( + $"Wabbajack is running inside a OneDrive folder \"{skyDrivePath}\". This folder is known to cause issues with Wabbajack. " + + "Please move Wabbajack outside of your OneDrive folder and then restart the app.", + "Cannot run inside OneDrive", true)); + }*/ + + MWVM = mainWindowVM; + + ModListLocation = new FilePickerVM + { + ExistCheckOption = FilePickerVM.CheckOptions.On, + PathType = FilePickerVM.PathTypeOptions.File, + PromptTitle = "Select a ModList to install" + }; + ModListLocation.Filters.Add(new CommonFileDialogFilter("Wabbajack Modlist", "*.wabbajack")); + + // Swap to proper sub VM based on selected type + _installer = this.WhenAny(x => x.TargetManager) + // Delay so the initial VM swap comes in immediately, subVM comes right after + .DelayInitial(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) + .Select(type => { - Utils.Error(new CriticalFailureIntervention( - "Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " + - "conflict with the operations Wabbajack needs to perform. Please move Wabbajack outside of your Downloads folder and then restart the app.", - "Cannot run inside Downloads", true)); - } - - if (skyDrivePath != null && AbsolutePath.EntryPoint.IsChildOf(new AbsolutePath(skyDrivePath))) - { - Utils.Error(new CriticalFailureIntervention( - $"Wabbajack is running inside a OneDrive folder \"{skyDrivePath}\". This folder is known to cause issues with Wabbajack. " + - "Please move Wabbajack outside of your OneDrive folder and then restart the app.", - "Cannot run inside OneDrive", true)); - } - - MWVM = mainWindowVM; - - ModListLocation = new FilePickerVM - { - ExistCheckOption = FilePickerVM.CheckOptions.On, - PathType = FilePickerVM.PathTypeOptions.File, - PromptTitle = "Select a ModList to install" - }; - ModListLocation.Filters.Add(new CommonFileDialogFilter("Wabbajack Modlist", "*.wabbajack")); - - // Swap to proper sub VM based on selected type - _installer = this.WhenAny(x => x.TargetManager) - // Delay so the initial VM swap comes in immediately, subVM comes right after - .DelayInitial(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) - .Select(type => + switch (type) { - switch (type) + case ModManager.Standard: + return new MO2InstallerVM(this); + default: + return null; + } + }) + // Unload old VM + .Pairwise() + .Do(pair => + { + pair.Previous?.Unload(); + }) + .Select(p => p.Current) + .ToGuiProperty(this, nameof(Installer)); + + // Load settings + MWVM.Settings.SaveSignal + .Subscribe(_ => + { + MWVM.Settings.Installer.LastInstalledListLocation = ModListLocation.TargetPath; + }) + .DisposeWith(CompositeDisposable); + + _IsActive = this.ConstructIsActive(MWVM) + .ToGuiProperty(this, nameof(IsActive)); + + // Active path represents the path to currently have loaded + // If we're not actively showing, then "unload" the active path + var activePath = Observable.CombineLatest( + this.WhenAny(x => x.ModListLocation.TargetPath), + this.WhenAny(x => x.IsActive), + resultSelector: (path, active) => (path, active)) + .Select(x => + { + if (!x.active) return default; + return x.path; + }) + // Throttle slightly so changes happen more atomically + .Throttle(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) + .Replay(1) + .RefCount(); + + _modList = activePath + .ObserveOn(RxApp.TaskpoolScheduler) + // Convert from active path to modlist VM + .Select(modListPath => + { + if (modListPath == default) return default; + if (!modListPath.FileExists()) return default; + return new ModListVM(modListPath); + }) + .DisposeOld() + .ObserveOnGuiThread() + .StartWith(default(ModListVM)) + .ToGuiProperty(this, nameof(ModList)); + + // Force GC collect when modlist changes, just to make sure we clean up any loose large items immediately + this.WhenAny(x => x.ModList) + .Delay(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) + .Subscribe(x => + { + GC.Collect(); + }); + + _LoadingModlist = Observable.Merge( + // When active path changes, mark as loading + activePath + .Select(_ => true), + // When the resulting modlist comes in, mark it as done + this.WhenAny(x => x.ModList) + .Select(_ => false)) + .ToGuiProperty(this, nameof(LoadingModlist)); + _installing = this.WhenAny(x => x.Installer.ActiveInstallation) + .Select(i => i != null) + .ToGuiProperty(this, nameof(Installing)); + _TargetManager = this.WhenAny(x => x.ModList) + .Select(modList => ModManager.Standard) + .ToGuiProperty(this, nameof(TargetManager)); + + // Add additional error check on ModList + ModListLocation.AdditionalError = this.WhenAny(x => x.ModList) + .Select(modList => + { + if (modList == null) return ErrorResponse.Fail("Modlist path resulted in a null object."); + if (modList.Error != null) return ErrorResponse.Fail("Modlist is corrupt", modList.Error); + if (modList.WabbajackVersion != null && modList.WabbajackVersion > Consts.CurrentMinimumWabbajackVersion) + return ErrorResponse.Fail("The Modlist you are trying to install was made using a newer Version of Wabbajack. Please update Wabbajack before installing!"); + return ErrorResponse.Success; + }); + + BackCommand = ReactiveCommand.Create( + execute: () => + { + StartedInstallation = false; + Completed = null; + mainWindowVM.NavigateTo(mainWindowVM.ModeSelectionVM); + }, + canExecute: Observable.CombineLatest( + this.WhenAny(x => x.Installing) + .Select(x => !x), + this.ConstructCanNavigateBack(), + resultSelector: (i, b) => i && b) + .ObserveOnGuiThread()); + + _percentCompleted = this.WhenAny(x => x.Installer.ActiveInstallation) + .StartWith(default(IInstaller)) + .CombineLatest( + this.WhenAny(x => x.Completed), + (installer, completed) => + { + if (installer == null) { - case ModManager.MO2: - return new MO2InstallerVM(this); - default: - return null; + return Observable.Return(completed != null ? Percent.One : Percent.Zero); } + return installer.PercentCompleted.StartWith(Percent.Zero); }) - // Unload old VM - .Pairwise() - .Do(pair => + .Switch() + .Debounce(TimeSpan.FromMilliseconds(25), RxApp.MainThreadScheduler) + .ToGuiProperty(this, nameof(PercentCompleted)); + + Slideshow = new SlideShow(this); + + // Set display items to ModList if configuring or complete, + // or to the current slideshow data if installing + _image = Observable.CombineLatest( + this.WhenAny(x => x.ModList.Error), + this.WhenAny(x => x.ModList) + .Select(x => x?.ImageObservable ?? Observable.Return(default(BitmapImage))) + .Switch() + .StartWith(default(BitmapImage)), + this.WhenAny(x => x.Slideshow.Image) + .StartWith(default(BitmapImage)), + this.WhenAny(x => x.Installing), + this.WhenAny(x => x.LoadingModlist), + resultSelector: (err, modList, slideshow, installing, loading) => { - pair.Previous?.Unload(); - }) - .Select(p => p.Current) - .ToGuiProperty(this, nameof(Installer)); - - // Load settings - MWVM.Settings.SaveSignal - .Subscribe(_ => - { - MWVM.Settings.Installer.LastInstalledListLocation = ModListLocation.TargetPath; - }) - .DisposeWith(CompositeDisposable); - - _IsActive = this.ConstructIsActive(MWVM) - .ToGuiProperty(this, nameof(IsActive)); - - // Active path represents the path to currently have loaded - // If we're not actively showing, then "unload" the active path - var activePath = Observable.CombineLatest( - this.WhenAny(x => x.ModListLocation.TargetPath), - this.WhenAny(x => x.IsActive), - resultSelector: (path, active) => (path, active)) - .Select(x => - { - if (!x.active) return default; - return x.path; - }) - // Throttle slightly so changes happen more atomically - .Throttle(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) - .Replay(1) - .RefCount(); - - _modList = activePath - .ObserveOn(RxApp.TaskpoolScheduler) - // Convert from active path to modlist VM - .Select(modListPath => - { - if (modListPath == default) return default; - if (!modListPath.Exists) return default; - return new ModListVM(modListPath); - }) - .DisposeOld() - .ObserveOnGuiThread() - .StartWith(default(ModListVM)) - .ToGuiProperty(this, nameof(ModList)); - - // Force GC collect when modlist changes, just to make sure we clean up any loose large items immediately - this.WhenAny(x => x.ModList) - .Delay(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) - .Subscribe(x => - { - GC.Collect(); - }); - - _LoadingModlist = Observable.Merge( - // When active path changes, mark as loading - activePath - .Select(_ => true), - // When the resulting modlist comes in, mark it as done - this.WhenAny(x => x.ModList) - .Select(_ => false)) - .ToGuiProperty(this, nameof(LoadingModlist)); - _installing = this.WhenAny(x => x.Installer.ActiveInstallation) - .Select(i => i != null) - .ToGuiProperty(this, nameof(Installing)); - _TargetManager = this.WhenAny(x => x.ModList) - .Select(modList => modList?.ModManager) - .ToGuiProperty(this, nameof(TargetManager)); - - // Add additional error check on ModList - ModListLocation.AdditionalError = this.WhenAny(x => x.ModList) - .Select(modList => - { - if (modList == null) return ErrorResponse.Fail("Modlist path resulted in a null object."); - if (modList.Error != null) return ErrorResponse.Fail("Modlist is corrupt", modList.Error); - if (modList.WabbajackVersion != null && modList.WabbajackVersion > Consts.CurrentMinimumWabbajackVersion) - return ErrorResponse.Fail("The Modlist you are trying to install was made using a newer Version of Wabbajack. Please update Wabbajack before installing!"); - return ErrorResponse.Success; - }); - - BackCommand = ReactiveCommand.Create( - execute: () => - { - StartedInstallation = false; - Completed = null; - mainWindowVM.NavigateTo(mainWindowVM.ModeSelectionVM); - }, - canExecute: Observable.CombineLatest( - this.WhenAny(x => x.Installing) - .Select(x => !x), - this.ConstructCanNavigateBack(), - resultSelector: (i, b) => i && b) - .ObserveOnGuiThread()); - - _percentCompleted = this.WhenAny(x => x.Installer.ActiveInstallation) - .StartWith(default(AInstaller)) - .CombineLatest( - this.WhenAny(x => x.Completed), - (installer, completed) => + if (err != null) { - if (installer == null) - { - return Observable.Return(completed != null ? Percent.One : Percent.Zero); - } - return installer.PercentCompleted.StartWith(Percent.Zero); - }) - .Switch() - .Debounce(TimeSpan.FromMilliseconds(25), RxApp.MainThreadScheduler) - .ToGuiProperty(this, nameof(PercentCompleted)); + return ResourceLinks.WabbajackErrLogo.Value; + } + if (loading) return default; + return installing ? slideshow : modList; + }) + .Select(x => x) + .ToGuiProperty(this, nameof(Image)); + _titleText = Observable.CombineLatest( + this.WhenAny(x => x.ModList) + .Select(modList => modList?.Name ?? string.Empty), + this.WhenAny(x => x.Slideshow.TargetMod.State.Name) + .StartWith(default(string)), + this.WhenAny(x => x.Installing), + resultSelector: (modList, mod, installing) => installing ? mod : modList) + .ToGuiProperty(this, nameof(TitleText)); + _authorText = Observable.CombineLatest( + this.WhenAny(x => x.ModList) + .Select(modList => modList?.Author ?? string.Empty), + this.WhenAny(x => x.Slideshow.TargetMod.State.Author) + .StartWith(default(string)), + this.WhenAny(x => x.Installing), + resultSelector: (modList, mod, installing) => installing ? mod : modList) + .ToGuiProperty(this, nameof(AuthorText)); + _description = Observable.CombineLatest( + this.WhenAny(x => x.ModList) + .Select(modList => modList?.Description ?? string.Empty), + this.WhenAny(x => x.Slideshow.TargetMod.State.Description) + .StartWith(default(string)), + this.WhenAny(x => x.Installing), + resultSelector: (modList, mod, installing) => installing ? mod : modList) + .ToGuiProperty(this, nameof(Description)); + _modListName = Observable.CombineLatest( + this.WhenAny(x => x.ModList.Error) + .Select(x => x != null), + this.WhenAny(x => x.ModList) + .Select(x => x?.Name), + resultSelector: (err, name) => + { + if (err) return "Corrupted Modlist"; + return name; + }) + .Merge(this.WhenAny(x => x.Installer.ActiveInstallation) + .Where(c => c != null) + .SelectMany(c => c.TextStatus)) + .ToGuiProperty(this, nameof(ModListName)); - Slideshow = new SlideShow(this); - - // Set display items to ModList if configuring or complete, - // or to the current slideshow data if installing - _image = Observable.CombineLatest( - this.WhenAny(x => x.ModList.Error), - this.WhenAny(x => x.ModList) - .Select(x => x?.ImageObservable ?? Observable.Return(default(BitmapImage))) - .Switch() - .StartWith(default(BitmapImage)), - this.WhenAny(x => x.Slideshow.Image) - .StartWith(default(BitmapImage)), - this.WhenAny(x => x.Installing), - this.WhenAny(x => x.LoadingModlist), - resultSelector: (err, modList, slideshow, installing, loading) => - { - if (err != null) - { - return ResourceLinks.WabbajackErrLogo.Value; - } - if (loading) return default; - return installing ? slideshow : modList; - }) - .Select(x => x) - .ToGuiProperty(this, nameof(Image)); - _titleText = Observable.CombineLatest( - this.WhenAny(x => x.ModList) - .Select(modList => modList?.Name ?? string.Empty), - this.WhenAny(x => x.Slideshow.TargetMod.State.Name) - .StartWith(default(string)), - this.WhenAny(x => x.Installing), - resultSelector: (modList, mod, installing) => installing ? mod : modList) - .ToGuiProperty(this, nameof(TitleText)); - _authorText = Observable.CombineLatest( - this.WhenAny(x => x.ModList) - .Select(modList => modList?.Author ?? string.Empty), - this.WhenAny(x => x.Slideshow.TargetMod.State.Author) - .StartWith(default(string)), - this.WhenAny(x => x.Installing), - resultSelector: (modList, mod, installing) => installing ? mod : modList) - .ToGuiProperty(this, nameof(AuthorText)); - _description = Observable.CombineLatest( - this.WhenAny(x => x.ModList) - .Select(modList => modList?.Description ?? string.Empty), - this.WhenAny(x => x.Slideshow.TargetMod.State.Description) - .StartWith(default(string)), - this.WhenAny(x => x.Installing), - resultSelector: (modList, mod, installing) => installing ? mod : modList) - .ToGuiProperty(this, nameof(Description)); - _modListName = Observable.CombineLatest( - this.WhenAny(x => x.ModList.Error) - .Select(x => x != null), - this.WhenAny(x => x.ModList) - .Select(x => x?.Name), - resultSelector: (err, name) => - { - if (err) return "Corrupted Modlist"; - return name; - }) - .Merge(this.WhenAny(x => x.Installer.ActiveInstallation) - .Where(c => c != null) - .SelectMany(c => c.TextStatus)) - .ToGuiProperty(this, nameof(ModListName)); - - ShowManifestCommand = ReactiveCommand.Create(() => - { - Utils.OpenWebsite(new Uri("https://www.wabbajack.org/#/modlists/manifest")); - }, this.WhenAny(x => x.ModList) - .Select(x => x?.SourceModList != null) + ShowManifestCommand = ReactiveCommand.Create(() => + { + UIUtils.OpenWebsite(new Uri("https://www.wabbajack.org/#/modlists/manifest")); + }, this.WhenAny(x => x.ModList) + .Select(x => x?.SourceModList != null) + .ObserveOnGuiThread()); + + OpenReadmeCommand = ReactiveCommand.Create( + execute: () => this.ModList?.OpenReadme(), + canExecute: this.WhenAny(x => x.ModList) + .Select(modList => !string.IsNullOrEmpty(modList?.Readme)) .ObserveOnGuiThread()); - OpenReadmeCommand = ReactiveCommand.Create( - execute: () => this.ModList?.OpenReadme(), - canExecute: this.WhenAny(x => x.ModList) - .Select(modList => !string.IsNullOrEmpty(modList?.Readme)) - .ObserveOnGuiThread()); - - OpenLogsCommand = ReactiveCommand.Create( - execute: () => Utils.OpenFolder(Consts.LogsFolder)); - VisitModListWebsiteCommand = ReactiveCommand.Create( - execute: () => + OpenLogsCommand = ReactiveCommand.Create( + execute: () => UIUtils.OpenFolder(Consts.LogsFolder)); + VisitModListWebsiteCommand = ReactiveCommand.Create( + execute: () => + { + UIUtils.OpenWebsite(ModList.Website); + return Unit.Default; + }, + canExecute: this.WhenAny(x => x.ModList.Website) + .Select(x => x != null) + .ObserveOnGuiThread()); + + _progressTitle = this.WhenAnyValue( + x => x.Installing, + x => x.StartedInstallation, + x => x.Completed, + selector: (installing, started, completed) => { - Utils.OpenWebsite(ModList.Website); - return Unit.Default; - }, - canExecute: this.WhenAny(x => x.ModList.Website) - .Select(x => x != null) - .ObserveOnGuiThread()); - - _progressTitle = this.WhenAnyValue( - x => x.Installing, - x => x.StartedInstallation, - x => x.Completed, - selector: (installing, started, completed) => + if (installing) { - if (installing) - { - return "Installing"; - } - else if (started) - { - if (completed == null) return "Installing"; - return completed.Value.Succeeded ? "Installed" : "Failed"; - } - else - { - return "Awaiting Input"; - } - }) - .ToGuiProperty(this, nameof(ProgressTitle)); + return "Installing"; + } + else if (started) + { + if (completed == null) return "Installing"; + return completed.Value.Succeeded ? "Installed" : "Failed"; + } + else + { + return "Awaiting Input"; + } + }) + .ToGuiProperty(this, nameof(ProgressTitle)); - UIUtils.BindCpuStatus( + UIUtils.BindCpuStatus( this.WhenAny(x => x.Installer.ActiveInstallation) .SelectMany(c => c?.QueueStatus ?? Observable.Empty()), StatusList) - .DisposeWith(CompositeDisposable); + .DisposeWith(CompositeDisposable); - BeginCommand = ReactiveCommand.CreateFromTask( - canExecute: this.WhenAny(x => x.Installer.CanInstall) - .Select(err => err.Succeeded), - execute: async () => + BeginCommand = ReactiveCommand.CreateFromTask( + canExecute: this.WhenAny(x => x.Installer.CanInstall) + .Select(err => err.Succeeded), + execute: async () => + { + try { + UIUtils.Log($"Starting to install {ModList.Name}"); + IsBackEnabledSubject.OnNext(false); + var success = await this.Installer.Install(); + Completed = ErrorResponse.Create(success); try { - Utils.Log($"Starting to install {ModList.Name}"); - IsBackEnabledSubject.OnNext(false); - var success = await this.Installer.Install(); - Completed = ErrorResponse.Create(success); - try - { - this.ModList?.OpenReadme(); - } - catch (Exception ex) - { - Utils.Error(ex); - } + this.ModList?.OpenReadme(); } catch (Exception ex) { - Utils.Error(ex, $"Encountered error, can't continue"); - while (ex.InnerException != null) ex = ex.InnerException; - Completed = ErrorResponse.Fail(ex); + UIUtils.Error(ex); } - finally - { - IsBackEnabledSubject.OnNext(true); - } - }); - - // When sub installer begins an install, mark state variable - BeginCommand.StartingExecution() - .Subscribe(_ => + } + catch (Exception ex) { - StartedInstallation = true; + Utils.Error(ex, $"Encountered error, can't continue"); + while (ex.InnerException != null) ex = ex.InnerException; + Completed = ErrorResponse.Fail(ex); + } + finally + { + IsBackEnabledSubject.OnNext(true); + } + }); + + // When sub installer begins an install, mark state variable + BeginCommand.StartingExecution() + .Subscribe(_ => + { + StartedInstallation = true; + }) + .DisposeWith(CompositeDisposable); + + // Listen for user interventions, and compile a dynamic list of all unhandled ones + var activeInterventions = this.WhenAny(x => x.Installer.ActiveInstallation) + .WithLatestFrom( + this.WhenAny(x => x.Installer), + (activeInstall, installer) => + { + if (activeInstall == null) return Observable.Empty>(); + return activeInstall.LogMessages + .WhereCastable() + .ToObservableChangeSet() + .AutoRefresh(i => i.Handled) + .Filter(i => !i.Handled) + .Transform(x => installer.InterventionConverter(x)); }) - .DisposeWith(CompositeDisposable); + .Switch() + .AsObservableList(); - // Listen for user interventions, and compile a dynamic list of all unhandled ones - var activeInterventions = this.WhenAny(x => x.Installer.ActiveInstallation) - .WithLatestFrom( - this.WhenAny(x => x.Installer), - (activeInstall, installer) => - { - if (activeInstall == null) return Observable.Empty>(); - return activeInstall.LogMessages - .WhereCastable() - .ToObservableChangeSet() - .AutoRefresh(i => i.Handled) - .Filter(i => !i.Handled) - .Transform(x => installer.InterventionConverter(x)); - }) - .Switch() - .AsObservableList(); + // Find the top intervention /w no CPU ID to be marked as "global" + _ActiveGlobalUserIntervention = activeInterventions.Connect() + .Filter(x => x.CpuID == WorkQueue.UnassignedCpuId) + .QueryWhenChanged(query => query.FirstOrDefault()) + .ToGuiProperty(this, nameof(ActiveGlobalUserIntervention)); - // Find the top intervention /w no CPU ID to be marked as "global" - _ActiveGlobalUserIntervention = activeInterventions.Connect() - .Filter(x => x.CpuID == WorkQueue.UnassignedCpuId) - .QueryWhenChanged(query => query.FirstOrDefault()) - .ToGuiProperty(this, nameof(ActiveGlobalUserIntervention)); + CloseWhenCompleteCommand = ReactiveCommand.CreateFromTask( + canExecute: this.WhenAny(x => x.Completed) + .Select(x => x != null), + execute: async () => + { + await MWVM.ShutdownApplication(); + }); - CloseWhenCompleteCommand = ReactiveCommand.CreateFromTask( - canExecute: this.WhenAny(x => x.Completed) + GoToInstallCommand = ReactiveCommand.Create( + canExecute: Observable.CombineLatest( + this.WhenAny(x => x.Completed) .Select(x => x != null), - execute: async () => - { - await MWVM.ShutdownApplication(); - }); + this.WhenAny(x => x.Installer.SupportsAfterInstallNavigation), + resultSelector: (complete, supports) => complete && supports), + execute: () => + { + Installer.AfterInstallNavigation(); + }); - GoToInstallCommand = ReactiveCommand.Create( - canExecute: Observable.CombineLatest( - this.WhenAny(x => x.Completed) - .Select(x => x != null), - this.WhenAny(x => x.Installer.SupportsAfterInstallNavigation), - resultSelector: (complete, supports) => complete && supports), - execute: () => - { - Installer.AfterInstallNavigation(); - }); - - _CurrentCpuCount = this.WhenAny(x => x.Installer.ActiveInstallation.Queue.CurrentCpuCount) - .Switch() - .ToGuiProperty(this, nameof(CurrentCpuCount)); - } + _CurrentCpuCount = this.WhenAny(x => x.Installer.ActiveInstallation.Queue.CurrentCpuCount) + .Switch() + .ToGuiProperty(this, nameof(CurrentCpuCount)); } -} +} \ No newline at end of file diff --git a/Wabbajack.Lib/Consts.cs b/Wabbajack.Lib/Consts.cs index bd35899d..10fbe8a0 100644 --- a/Wabbajack.Lib/Consts.cs +++ b/Wabbajack.Lib/Consts.cs @@ -6,4 +6,5 @@ public static class Consts { public static string AppName = "Wabbajack"; public static Uri WabbajackBuildServerUri => new("https://build.wabbajack.org"); + public static Version CurrentMinimumWabbajackVersion { get; set; } = Version.Parse("2.3.0.0"); } \ No newline at end of file