From e0a91036d00425f90c2eca52df1dc5296932b4eb Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Tue, 17 Dec 2019 21:10:38 -0600 Subject: [PATCH 01/14] Fixes for progress bars starting at 100% --- Wabbajack.Lib/Extensions/ReactiveUIExt.cs | 9 ++++ Wabbajack/View Models/Compilers/CompilerVM.cs | 39 ++++++++-------- .../View Models/Installers/InstallerVM.cs | 45 +++++++++---------- Wabbajack/Views/Common/TopProgressView.xaml | 2 +- Wabbajack/Views/Compilers/CompilerView.xaml | 4 +- .../Views/Installers/InstallationView.xaml | 10 ++--- 6 files changed, 58 insertions(+), 51 deletions(-) diff --git a/Wabbajack.Lib/Extensions/ReactiveUIExt.cs b/Wabbajack.Lib/Extensions/ReactiveUIExt.cs index 87a783cd..c18543da 100644 --- a/Wabbajack.Lib/Extensions/ReactiveUIExt.cs +++ b/Wabbajack.Lib/Extensions/ReactiveUIExt.cs @@ -44,6 +44,15 @@ namespace Wabbajack .Unit(); } + public static IObservable EndingExecution(this IReactiveCommand cmd) + { + return cmd.IsExecuting + .DistinctUntilChanged() + .Pairwise() + .Where(x => x.Previous && !x.Current) + .Unit(); + } + /// These snippets were provided by RolandPheasant (author of DynamicData) /// They'll be going into the official library at some point, but are here for now. #region Dynamic Data EnsureUniqueChanges diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs index b6c6d9ad..cdb5ee66 100644 --- a/Wabbajack/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/CompilerVM.cs @@ -52,14 +52,11 @@ namespace Wabbajack private readonly ObservableAsPropertyHelper _ActiveGlobalUserIntervention; public IUserIntervention ActiveGlobalUserIntervention => _ActiveGlobalUserIntervention.Value; - private readonly ObservableAsPropertyHelper _Completed; - public bool Completed => _Completed.Value; - - /// - /// Tracks whether compilation has begun - /// [Reactive] - public bool CompilationMode { get; set; } + public bool StartedCompilation { get; set; } + + [Reactive] + public bool Completed { get; set; } public CompilerVM(MainWindowVM mainWindowVM) { @@ -137,7 +134,8 @@ namespace Wabbajack execute: () => { mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM; - CompilationMode = false; + StartedCompilation = false; + Completed = false; }, canExecute: this.WhenAny(x => x.Compiling) .Select(x => !x)); @@ -166,15 +164,6 @@ namespace Wabbajack .Subscribe() .DisposeWith(CompositeDisposable); - _Completed = Observable.CombineLatest( - this.WhenAny(x => x.Compiling), - this.WhenAny(x => x.CompilationMode), - resultSelector: (installing, installingMode) => - { - return installingMode && !installing; - }) - .ToProperty(this, nameof(Completed)); - _percentCompleted = this.WhenAny(x => x.Compiler.ActiveCompilation) .StartWith(default(ACompiler)) .CombineLatest( @@ -185,19 +174,29 @@ namespace Wabbajack { return Observable.Return(completed ? 1f : 0f); } - return compiler.PercentCompleted; + return compiler.PercentCompleted.StartWith(0); }) .Switch() .Debounce(TimeSpan.FromMilliseconds(25)) .ToProperty(this, nameof(PercentCompleted)); - // When sub compiler begins an install, mark state variable + // When sub compiler begins a compile, mark state variable this.WhenAny(x => x.Compiler.BeginCommand) .Select(x => x?.StartingExecution() ?? Observable.Empty()) .Switch() .Subscribe(_ => { - CompilationMode = true; + StartedCompilation = true; + }) + .DisposeWith(CompositeDisposable); + + // When sub compiler finishes a compile, mark state variable + this.WhenAny(x => x.Compiler.BeginCommand) + .Select(x => x?.EndingExecution() ?? Observable.Empty()) + .Switch() + .Subscribe(_ => + { + Completed = true; }) .DisposeWith(CompositeDisposable); diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index 29d9648d..677f1f86 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -46,11 +46,11 @@ namespace Wabbajack private readonly ObservableAsPropertyHelper _installing; public bool Installing => _installing.Value; - /// - /// Tracks whether installation has begun - /// [Reactive] - public bool InstallingMode { get; set; } + public bool StartedInstallation { get; set; } + + [Reactive] + public bool Completed { get; set; } private readonly ObservableAsPropertyHelper _image; public ImageSource Image => _image.Value; @@ -82,9 +82,6 @@ namespace Wabbajack private readonly ObservableAsPropertyHelper _ActiveGlobalUserIntervention; public IUserIntervention ActiveGlobalUserIntervention => _ActiveGlobalUserIntervention.Value; - private readonly ObservableAsPropertyHelper _Completed; - public bool Completed => _Completed.Value; - // Command properties public IReactiveCommand ShowReportCommand { get; } public IReactiveCommand OpenReadmeCommand { get; } @@ -163,7 +160,7 @@ namespace Wabbajack .Select(modList => modList?.ReportHTML) .ToProperty(this, nameof(HTMLReport)); _installing = this.WhenAny(x => x.Installer.ActiveInstallation) - .Select(compilation => compilation != null) + .Select(i => i != null) .ObserveOnGuiThread() .ToProperty(this, nameof(Installing)); _TargetManager = this.WhenAny(x => x.ModList) @@ -182,21 +179,13 @@ namespace Wabbajack BackCommand = ReactiveCommand.Create( execute: () => { - InstallingMode = false; + StartedInstallation = false; + Completed = false; mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM; }, canExecute: this.WhenAny(x => x.Installing) .Select(x => !x)); - _Completed = Observable.CombineLatest( - this.WhenAny(x => x.Installing), - this.WhenAny(x => x.InstallingMode), - resultSelector: (installing, installingMode) => - { - return installingMode && !installing; - }) - .ToProperty(this, nameof(Completed)); - _percentCompleted = this.WhenAny(x => x.Installer.ActiveInstallation) .StartWith(default(AInstaller)) .CombineLatest( @@ -207,7 +196,7 @@ namespace Wabbajack { return Observable.Return(completed ? 1f : 0f); } - return installer.PercentCompleted; + return installer.PercentCompleted.StartWith(0f); }) .Switch() .Debounce(TimeSpan.FromMilliseconds(25)) @@ -285,11 +274,11 @@ namespace Wabbajack _progressTitle = Observable.CombineLatest( this.WhenAny(x => x.Installing), - this.WhenAny(x => x.InstallingMode), - resultSelector: (installing, mode) => + this.WhenAny(x => x.StartedInstallation), + resultSelector: (installing, started) => { if (!installing) return "Configuring"; - return mode ? "Installing" : "Installed"; + return started ? "Installing" : "Installed"; }) .ToProperty(this, nameof(ProgressTitle)); @@ -323,7 +312,17 @@ namespace Wabbajack .Switch() .Subscribe(_ => { - InstallingMode = true; + StartedInstallation = true; + }) + .DisposeWith(CompositeDisposable); + + // When sub installer ends an install, mark state variable + this.WhenAny(x => x.Installer.BeginCommand) + .Select(x => x?.EndingExecution() ?? Observable.Empty()) + .Switch() + .Subscribe(_ => + { + Completed = true; }) .DisposeWith(CompositeDisposable); diff --git a/Wabbajack/Views/Common/TopProgressView.xaml b/Wabbajack/Views/Common/TopProgressView.xaml index e83cd94b..b7515ecb 100644 --- a/Wabbajack/Views/Common/TopProgressView.xaml +++ b/Wabbajack/Views/Common/TopProgressView.xaml @@ -65,7 +65,7 @@ x:Name="LargeProgressBar" Grid.Column="0" Grid.ColumnSpan="4" - Background="#88121212" + Background="#AA121212" BorderThickness="0" Maximum="1" Opacity="{Binding ProgressOpacityPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" diff --git a/Wabbajack/Views/Compilers/CompilerView.xaml b/Wabbajack/Views/Compilers/CompilerView.xaml index 9f8a634b..3f435e55 100644 --- a/Wabbajack/Views/Compilers/CompilerView.xaml +++ b/Wabbajack/Views/Compilers/CompilerView.xaml @@ -174,7 +174,7 @@ Margin="35,0,35,0" VerticalAlignment="Center" ClipToBounds="False" - Visibility="{Binding CompilationMode, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}"> + Visibility="{Binding StartedCompilation, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}"> @@ -240,7 +240,7 @@ Grid.Column="0" Grid.ColumnSpan="5" Margin="5" - Visibility="{Binding CompilationMode, Converter={StaticResource bool2VisibilityConverter}, FallbackValue=Hidden}"> + Visibility="{Binding StartedCompilation, Converter={StaticResource bool2VisibilityConverter}, FallbackValue=Hidden}"> diff --git a/Wabbajack/Views/Installers/InstallationView.xaml b/Wabbajack/Views/Installers/InstallationView.xaml index 022e1b1f..b666ac4c 100644 --- a/Wabbajack/Views/Installers/InstallationView.xaml +++ b/Wabbajack/Views/Installers/InstallationView.xaml @@ -206,7 +206,7 @@ ToolTip="Pause Installation" Margin="0,0,0,5" Width="50" - Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}}"> + Visibility="{Binding StartedInstallation, Converter={StaticResource bool2VisibilityConverter}}"> @@ -214,7 +214,7 @@ ToolTip="Stop Installation" Margin="0,0,0,5" Width="50" - Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}}" > + Visibility="{Binding StartedInstallation, Converter={StaticResource bool2VisibilityConverter}}" > + Visibility="{Binding StartedInstallation, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}"> @@ -339,7 +339,7 @@ Margin="5" VerticalAlignment="Center" Background="Transparent" - Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}"> + Visibility="{Binding StartedInstallation, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}"> @@ -394,7 +394,7 @@ + Visibility="{Binding StartedInstallation, Converter={StaticResource bool2VisibilityConverter}, FallbackValue=Hidden}"> From b0bff6e121ba7227a3e366aabdc63a3f0c3d4b83 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Tue, 17 Dec 2019 21:18:33 -0600 Subject: [PATCH 02/14] Missing ExtractLibs awaits --- Wabbajack.Lib/LibCefHelpers/Init.cs | 7 +++--- Wabbajack.Test/ACompilerTest.cs | 4 ++-- Wabbajack.Test/DownloaderTests.cs | 4 ++-- Wabbajack/App.xaml.cs | 3 +-- Wabbajack/Views/MainWindow.xaml.cs | 34 +++++++++++++++++------------ 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Wabbajack.Lib/LibCefHelpers/Init.cs b/Wabbajack.Lib/LibCefHelpers/Init.cs index ff75da3f..a5885917 100644 --- a/Wabbajack.Lib/LibCefHelpers/Init.cs +++ b/Wabbajack.Lib/LibCefHelpers/Init.cs @@ -19,7 +19,7 @@ namespace Wabbajack.Lib.LibCefHelpers /// /// We bundle the cef libs inside the .exe, we need to extract them before loading any wpf code that requires them /// - public static void ExtractLibs() + public static async Task ExtractLibs() { if (File.Exists("cefglue.7z") && File.Exists("libcef.dll")) return; @@ -30,8 +30,9 @@ namespace Wabbajack.Lib.LibCefHelpers Utils.Log("Extracting libCef files"); } using (var wq = new WorkQueue(1)) - FileExtractor.ExtractAll(wq, "cefglue.7z", "."); - + { + await FileExtractor.ExtractAll(wq, "cefglue.7z", "."); + } } public static HttpClient GetClient(IEnumerable cookies, string referer) { diff --git a/Wabbajack.Test/ACompilerTest.cs b/Wabbajack.Test/ACompilerTest.cs index a0dcd949..9671c281 100644 --- a/Wabbajack.Test/ACompilerTest.cs +++ b/Wabbajack.Test/ACompilerTest.cs @@ -13,9 +13,9 @@ namespace Wabbajack.Test protected TestUtils utils { get; set; } [TestInitialize] - public void TestInitialize() + public async Task TestInitialize() { - Helpers.ExtractLibs(); + await Helpers.ExtractLibs(); Consts.TestMode = true; utils = new TestUtils(); diff --git a/Wabbajack.Test/DownloaderTests.cs b/Wabbajack.Test/DownloaderTests.cs index 961c7338..f72cb650 100644 --- a/Wabbajack.Test/DownloaderTests.cs +++ b/Wabbajack.Test/DownloaderTests.cs @@ -24,9 +24,9 @@ namespace Wabbajack.Test public TestContext TestContext { get; set; } [TestInitialize] - public void Setup() + public async Task Setup() { - Helpers.ExtractLibs(); + await Helpers.ExtractLibs(); Utils.LogMessages.OfType().Subscribe(onNext: msg => TestContext.WriteLine(msg.ShortDescription)); Utils.LogMessages.OfType().Subscribe(msg => TestContext.WriteLine("ERROR: User intervetion required: " + msg.ShortDescription)); diff --git a/Wabbajack/App.xaml.cs b/Wabbajack/App.xaml.cs index 2fd65720..1daefbf8 100644 --- a/Wabbajack/App.xaml.cs +++ b/Wabbajack/App.xaml.cs @@ -4,7 +4,6 @@ using System.Reflection; using System.Windows; using MahApps.Metro; using Wabbajack.Common; -using Wabbajack.Lib.LibCefHelpers; namespace Wabbajack { @@ -15,7 +14,7 @@ namespace Wabbajack { public App() { - Helpers.ExtractLibs(); + // Do initialization in MainWindow ctor } } } diff --git a/Wabbajack/Views/MainWindow.xaml.cs b/Wabbajack/Views/MainWindow.xaml.cs index 9c9139aa..6c977944 100644 --- a/Wabbajack/Views/MainWindow.xaml.cs +++ b/Wabbajack/Views/MainWindow.xaml.cs @@ -1,8 +1,10 @@ using System; using System.ComponentModel; +using System.Threading.Tasks; using System.Windows; using MahApps.Metro.Controls; using Wabbajack.Common; +using Wabbajack.Lib.LibCefHelpers; using Application = System.Windows.Application; using Utils = Wabbajack.Common.Utils; @@ -25,22 +27,26 @@ namespace Wabbajack Wabbajack.Common.Utils.Error(((Exception)e.ExceptionObject), "Uncaught error"); }; - var appPath = System.Reflection.Assembly.GetExecutingAssembly().Location; - try - { - if (!ExtensionManager.IsAssociated() || ExtensionManager.NeedsUpdating(appPath)) - { - ExtensionManager.Associate(appPath); - } - } - catch (Exception e) - { - Utils.Log($"ExtensionManager had an exception:\n{e}"); - } - - Wabbajack.Common.Utils.Log($"Wabbajack Build - {ThisAssembly.Git.Sha}"); + // Run some init tasks in background + Task.Run(async () => + { + await Helpers.ExtractLibs(); + var appPath = System.Reflection.Assembly.GetExecutingAssembly().Location; + try + { + if (!ExtensionManager.IsAssociated() || ExtensionManager.NeedsUpdating(appPath)) + { + ExtensionManager.Associate(appPath); + } + } + catch (Exception e) + { + Utils.Log($"ExtensionManager had an exception:\n{e}"); + } + }).FireAndForget(); + // Load settings string[] args = Environment.GetCommandLineArgs(); if ((args.Length > 1 && args[1] == "nosettings") From 1673f8a55566ce8433b90c903c6ed55df5877f92 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Wed, 18 Dec 2019 19:09:45 -0600 Subject: [PATCH 03/14] LibCef extraction init awaits and improvements --- Wabbajack.Lib/LibCefHelpers/Init.cs | 17 +++++++++++---- Wabbajack.Test/ACompilerTest.cs | 2 +- Wabbajack.Test/DownloaderTests.cs | 2 +- Wabbajack/Util/AsyncLazy.cs | 21 +++++++++++++++++++ Wabbajack/View Models/MainWindowVM.cs | 2 -- .../View Models/UserInterventionHandlers.cs | 2 +- Wabbajack/View Models/WebBrowserVM.cs | 10 ++++++++- Wabbajack/Views/MainWindow.xaml.cs | 2 +- Wabbajack/Wabbajack.csproj | 1 + 9 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 Wabbajack/Util/AsyncLazy.cs diff --git a/Wabbajack.Lib/LibCefHelpers/Init.cs b/Wabbajack.Lib/LibCefHelpers/Init.cs index a5885917..63f3a54c 100644 --- a/Wabbajack.Lib/LibCefHelpers/Init.cs +++ b/Wabbajack.Lib/LibCefHelpers/Init.cs @@ -15,11 +15,12 @@ namespace Wabbajack.Lib.LibCefHelpers { public static class Helpers { + private static readonly Task _initTask; /// /// We bundle the cef libs inside the .exe, we need to extract them before loading any wpf code that requires them /// - public static async Task ExtractLibs() + private static async Task ExtractLibs() { if (File.Exists("cefglue.7z") && File.Exists("libcef.dll")) return; @@ -34,6 +35,17 @@ namespace Wabbajack.Lib.LibCefHelpers await FileExtractor.ExtractAll(wq, "cefglue.7z", "."); } } + + static Helpers() + { + _initTask = Task.Run(ExtractLibs); + } + + public static Task Initialize() + { + return _initTask; + } + public static HttpClient GetClient(IEnumerable cookies, string referer) { var container = ToCookieContainer(cookies); @@ -66,7 +78,6 @@ namespace Wabbajack.Lib.LibCefHelpers return (await visitor.Task).Where(c => c.Domain.EndsWith(domainEnding)).ToArray(); } - private class CookieVisitor : CefCookieVisitor { TaskCompletionSource> _source = new TaskCompletionSource>(); @@ -93,8 +104,6 @@ namespace Wabbajack.Lib.LibCefHelpers if (disposing) _source.SetResult(Cookies); } - - } public class Cookie diff --git a/Wabbajack.Test/ACompilerTest.cs b/Wabbajack.Test/ACompilerTest.cs index 9671c281..875e29ec 100644 --- a/Wabbajack.Test/ACompilerTest.cs +++ b/Wabbajack.Test/ACompilerTest.cs @@ -15,7 +15,7 @@ namespace Wabbajack.Test [TestInitialize] public async Task TestInitialize() { - await Helpers.ExtractLibs(); + await Helpers.Initialize(); Consts.TestMode = true; utils = new TestUtils(); diff --git a/Wabbajack.Test/DownloaderTests.cs b/Wabbajack.Test/DownloaderTests.cs index f72cb650..676a3ccf 100644 --- a/Wabbajack.Test/DownloaderTests.cs +++ b/Wabbajack.Test/DownloaderTests.cs @@ -26,7 +26,7 @@ namespace Wabbajack.Test [TestInitialize] public async Task Setup() { - await Helpers.ExtractLibs(); + await Helpers.Initialize(); Utils.LogMessages.OfType().Subscribe(onNext: msg => TestContext.WriteLine(msg.ShortDescription)); Utils.LogMessages.OfType().Subscribe(msg => TestContext.WriteLine("ERROR: User intervetion required: " + msg.ShortDescription)); diff --git a/Wabbajack/Util/AsyncLazy.cs b/Wabbajack/Util/AsyncLazy.cs new file mode 100644 index 00000000..69488c28 --- /dev/null +++ b/Wabbajack/Util/AsyncLazy.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Wabbajack +{ + public class AsyncLazy : Lazy> + { + public AsyncLazy(Func valueFactory) : + base(() => Task.Factory.StartNew(valueFactory)) + { + } + + public AsyncLazy(Func> taskFactory) : + base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap()) + { + } + } +} diff --git a/Wabbajack/View Models/MainWindowVM.cs b/Wabbajack/View Models/MainWindowVM.cs index 0f70c6a4..b96eaf5b 100644 --- a/Wabbajack/View Models/MainWindowVM.cs +++ b/Wabbajack/View Models/MainWindowVM.cs @@ -36,7 +36,6 @@ namespace Wabbajack public readonly Lazy Installer; public readonly Lazy Gallery; public readonly ModeSelectionVM ModeSelectionVM; - public readonly WebBrowserVM WebBrowserVM; public readonly UserInterventionHandlers UserInterventionHandlers; public Dispatcher ViewDispatcher { get; set; } @@ -52,7 +51,6 @@ namespace Wabbajack Compiler = new Lazy(() => new CompilerVM(this)); Gallery = new Lazy(() => new ModListGalleryVM(this)); ModeSelectionVM = new ModeSelectionVM(this); - WebBrowserVM = new WebBrowserVM(); UserInterventionHandlers = new UserInterventionHandlers(this); // Set up logging diff --git a/Wabbajack/View Models/UserInterventionHandlers.cs b/Wabbajack/View Models/UserInterventionHandlers.cs index bcc5d941..dab21d07 100644 --- a/Wabbajack/View Models/UserInterventionHandlers.cs +++ b/Wabbajack/View Models/UserInterventionHandlers.cs @@ -26,7 +26,7 @@ namespace Wabbajack { CancellationTokenSource cancel = new CancellationTokenSource(); var oldPane = MainWindow.ActivePane; - var vm = new WebBrowserVM(); + var vm = await WebBrowserVM.GetNew(); MainWindow.ActivePane = vm; vm.BackCommand = ReactiveCommand.Create(() => { diff --git a/Wabbajack/View Models/WebBrowserVM.cs b/Wabbajack/View Models/WebBrowserVM.cs index 064c1ec3..73813748 100644 --- a/Wabbajack/View Models/WebBrowserVM.cs +++ b/Wabbajack/View Models/WebBrowserVM.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.Lib; +using Wabbajack.Lib.LibCefHelpers; using Xilium.CefGlue.WPF; namespace Wabbajack @@ -20,10 +21,17 @@ namespace Wabbajack [Reactive] public IReactiveCommand BackCommand { get; set; } - public WebBrowserVM(string url = "http://www.wabbajack.org") + private WebBrowserVM(string url = "http://www.wabbajack.org") { Browser.Address = url; Instructions = "Wabbajack Web Browser"; } + + public static async Task GetNew(string url = "http://www.wabbajack.org") + { + // Make sure libraries are extracted first + await Helpers.Initialize(); + return new WebBrowserVM(url); + } } } diff --git a/Wabbajack/Views/MainWindow.xaml.cs b/Wabbajack/Views/MainWindow.xaml.cs index 6c977944..53486d64 100644 --- a/Wabbajack/Views/MainWindow.xaml.cs +++ b/Wabbajack/Views/MainWindow.xaml.cs @@ -32,7 +32,7 @@ namespace Wabbajack // Run some init tasks in background Task.Run(async () => { - await Helpers.ExtractLibs(); + await Helpers.Initialize(); var appPath = System.Reflection.Assembly.GetExecutingAssembly().Location; try { diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj index a83c2590..786f43c7 100644 --- a/Wabbajack/Wabbajack.csproj +++ b/Wabbajack/Wabbajack.csproj @@ -176,6 +176,7 @@ UnderMaintenanceOverlay.xaml + CompilationCompleteView.xaml From 5f7188d53d0f7accf768806dc1b830cc9901d638 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Tue, 17 Dec 2019 21:25:55 -0600 Subject: [PATCH 04/14] Paranoia logic to bring window to the front after loading --- Wabbajack/Views/MainWindow.xaml.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Wabbajack/Views/MainWindow.xaml.cs b/Wabbajack/Views/MainWindow.xaml.cs index 53486d64..b05bc691 100644 --- a/Wabbajack/Views/MainWindow.xaml.cs +++ b/Wabbajack/Views/MainWindow.xaml.cs @@ -64,6 +64,18 @@ namespace Wabbajack // Set datacontext _mwvm = new MainWindowVM(this, _settings); DataContext = _mwvm; + + // Bring window to the front if it isn't already + this.Initialized += (s, e) => + { + this.Activate(); + this.Topmost = true; + this.Focus(); + }; + this.ContentRendered += (s, e) => + { + this.Topmost = false; + }; } public void Init(MainWindowVM vm, MainSettings settings) From f4f9272858f6333d1fcda66365f1e9e2fba40de6 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Wed, 18 Dec 2019 19:14:21 -0600 Subject: [PATCH 05/14] BeginCommands refactored --- Wabbajack/View Models/Compilers/CompilerVM.cs | 26 +++- .../View Models/Compilers/ISubCompilerVM.cs | 8 +- .../View Models/Compilers/MO2CompilerVM.cs | 118 ++++++++---------- .../View Models/Compilers/VortexCompilerVM.cs | 111 +++++++--------- .../View Models/Installers/ISubInstallerVM.cs | 3 +- .../View Models/Installers/InstallerVM.cs | 27 +++- .../View Models/Installers/MO2InstallerVM.cs | 88 +++++-------- .../Installers/VortexInstallerVM.cs | 90 +++++-------- Wabbajack/Views/Compilers/CompilerView.xaml | 2 +- .../Views/Installers/InstallationView.xaml | 2 +- 10 files changed, 217 insertions(+), 258 deletions(-) diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs index cdb5ee66..8f54455f 100644 --- a/Wabbajack/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/CompilerVM.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Windows.Input; using System.Windows.Media.Imaging; using Wabbajack.Common; using Wabbajack.Common.StatusFeed; @@ -46,6 +47,7 @@ namespace Wabbajack public IReactiveCommand BackCommand { get; } public IReactiveCommand GoToModlistCommand { get; } public IReactiveCommand CloseWhenCompleteCommand { get; } + public IReactiveCommand BeginCommand { get; } public FilePickerVM OutputLocation { get; } @@ -180,10 +182,24 @@ namespace Wabbajack .Debounce(TimeSpan.FromMilliseconds(25)) .ToProperty(this, nameof(PercentCompleted)); + BeginCommand = ReactiveCommand.CreateFromTask( + canExecute: this.WhenAny(x => x.Compiler.CanCompile) + .Switch(), + execute: async () => + { + try + { + await this.Compiler.Compile(); + } + catch (Exception ex) + { + while (ex.InnerException != null) ex = ex.InnerException; + Utils.Error(ex, $"Compiler error"); + } + }); + // When sub compiler begins a compile, mark state variable - this.WhenAny(x => x.Compiler.BeginCommand) - .Select(x => x?.StartingExecution() ?? Observable.Empty()) - .Switch() + BeginCommand.StartingExecution() .Subscribe(_ => { StartedCompilation = true; @@ -191,9 +207,7 @@ namespace Wabbajack .DisposeWith(CompositeDisposable); // When sub compiler finishes a compile, mark state variable - this.WhenAny(x => x.Compiler.BeginCommand) - .Select(x => x?.EndingExecution() ?? Observable.Empty()) - .Switch() + BeginCommand.EndingExecution() .Subscribe(_ => { Completed = true; diff --git a/Wabbajack/View Models/Compilers/ISubCompilerVM.cs b/Wabbajack/View Models/Compilers/ISubCompilerVM.cs index 2a3fd08a..eadbf82d 100644 --- a/Wabbajack/View Models/Compilers/ISubCompilerVM.cs +++ b/Wabbajack/View Models/Compilers/ISubCompilerVM.cs @@ -1,4 +1,6 @@ -using ReactiveUI; +using System; +using System.Threading.Tasks; +using ReactiveUI; using Wabbajack.Common; using Wabbajack.Lib; @@ -6,10 +8,10 @@ namespace Wabbajack { public interface ISubCompilerVM { - IReactiveCommand BeginCommand { get; } ACompiler ActiveCompilation { get; } - ModlistSettingsEditorVM ModlistSettings { get; } void Unload(); + IObservable CanCompile { get; } + Task Compile(); } } diff --git a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs index 6c4ca364..7b5971dc 100644 --- a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Threading.Tasks; using Wabbajack.Common; using Wabbajack.Lib; @@ -38,6 +39,8 @@ namespace Wabbajack [Reactive] public StatusUpdateTracker StatusTracker { get; private set; } + public IObservable CanCompile { get; } + public MO2CompilerVM(CompilerVM parent) { Parent = parent; @@ -93,8 +96,8 @@ namespace Wabbajack // Load custom modlist settings per MO2 profile _modlistSettings = Observable.CombineLatest( - this.WhenAny(x => x.ModListLocation.ErrorState), - this.WhenAny(x => x.ModListLocation.TargetPath), + (this).WhenAny(x => x.ModListLocation.ErrorState), + (this).WhenAny(x => x.ModListLocation.TargetPath), resultSelector: (state, path) => (State: state, Path: path)) // A short throttle is a quick hack to make the above changes "atomic" .Throttle(TimeSpan.FromMilliseconds(25)) @@ -119,67 +122,16 @@ namespace Wabbajack .ObserveOnGuiThread() .ToProperty(this, nameof(ModlistSettings)); - // Wire start command - BeginCommand = ReactiveCommand.CreateFromTask( - canExecute: Observable.CombineLatest( - this.WhenAny(x => x.ModListLocation.InError), - this.WhenAny(x => x.DownloadLocation.InError), - parent.WhenAny(x => x.OutputLocation.InError), - this.WhenAny(x => x.ModlistSettings) - .Select(x => x?.InError ?? Observable.Return(false)) - .Switch(), - resultSelector: (ml, down, output, modlistSettings) => !ml && !down && !output && !modlistSettings) - .ObserveOnGuiThread(), - execute: async () => - { - try - { - string outputFile; - if (string.IsNullOrWhiteSpace(parent.OutputLocation.TargetPath)) - { - outputFile = MOProfile + ExtensionManager.Extension; - } - else - { - outputFile = Path.Combine(parent.OutputLocation.TargetPath, MOProfile + ExtensionManager.Extension); - } - ActiveCompilation = new MO2Compiler( - mo2Folder: Mo2Folder, - mo2Profile: MOProfile, - outputFile: outputFile) - { - ModListName = ModlistSettings.ModListName, - ModListAuthor = ModlistSettings.AuthorText, - ModListDescription = ModlistSettings.Description, - ModListImage = ModlistSettings.ImagePath.TargetPath, - ModListWebsite = ModlistSettings.Website, - ModListReadme = ModlistSettings.ReadMeText.TargetPath, - }; - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Error(ex, $"Compiler error"); - return; - } - - try - { - await ActiveCompilation.Begin(); - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Error(ex, $"Compiler error"); - } - finally - { - StatusTracker = null; - ActiveCompilation.Dispose(); - ActiveCompilation = null; - } - - }); + CanCompile = Observable.CombineLatest( + this.WhenAny(x => x.ModListLocation.InError), + this.WhenAny(x => x.DownloadLocation.InError), + parent.WhenAny(x => x.OutputLocation.InError), + this.WhenAny(x => x.ModlistSettings) + .Select(x => x?.InError ?? Observable.Return(false)) + .Switch(), + resultSelector: (ml, down, output, modlistSettings) => !ml && !down && !output && !modlistSettings) + .Publish() + .RefCount(); // Load settings _settings = parent.MWVM.Settings.Compiler.MO2Compilation; @@ -193,11 +145,11 @@ namespace Wabbajack .DisposeWith(CompositeDisposable); // If Mo2 folder changes and download location is empty, set it for convenience - this.WhenAny(x => x.Mo2Folder) + (this).WhenAny(x => x.Mo2Folder) .DelayInitial(TimeSpan.FromMilliseconds(100)) .Where(x => Directory.Exists(x)) .FilterSwitch( - this.WhenAny(x => x.DownloadLocation.Exists) + (this).WhenAny(x => x.DownloadLocation.Exists) .Invert()) // A skip is needed to ignore the initial signal when the FilterSwitch turns on .Skip(1) @@ -214,5 +166,41 @@ namespace Wabbajack _settings.LastCompiledProfileLocation = ModListLocation.TargetPath; ModlistSettings?.Save(); } + + public async Task Compile() + { + string outputFile; + if (string.IsNullOrWhiteSpace(Parent.OutputLocation.TargetPath)) + { + outputFile = MOProfile + ExtensionManager.Extension; + } + else + { + outputFile = Path.Combine(Parent.OutputLocation.TargetPath, MOProfile + ExtensionManager.Extension); + } + + try + { + ActiveCompilation = new MO2Compiler( + mo2Folder: Mo2Folder, + mo2Profile: MOProfile, + outputFile: outputFile) + { + ModListName = ModlistSettings.ModListName, + ModListAuthor = ModlistSettings.AuthorText, + ModListDescription = ModlistSettings.Description, + ModListImage = ModlistSettings.ImagePath.TargetPath, + ModListWebsite = ModlistSettings.Website, + ModListReadme = ModlistSettings.ReadMeText.TargetPath, + }; + await ActiveCompilation.Begin(); + } + finally + { + StatusTracker = null; + ActiveCompilation.Dispose(); + ActiveCompilation = null; + } + } } } diff --git a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs index d8e60219..ca1399eb 100644 --- a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs +++ b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs @@ -54,6 +54,8 @@ namespace Wabbajack [Reactive] public StatusUpdateTracker StatusTracker { get; private set; } + public IObservable CanCompile { get; } + public VortexCompilerVM(CompilerVM parent) { Parent = parent; @@ -77,7 +79,7 @@ namespace Wabbajack }; // Load custom ModList settings when game type changes - _modListSettings = this.WhenAny(x => x.SelectedGame) + _modListSettings = (this).WhenAny(x => x.SelectedGame) .Select(game => { if (game == null) return null; @@ -97,67 +99,16 @@ namespace Wabbajack .ObserveOnGuiThread() .ToProperty(this, nameof(ModlistSettings)); - // Wire start command - BeginCommand = ReactiveCommand.CreateFromTask( - canExecute: Observable.CombineLatest( - this.WhenAny(x => x.GameLocation.InError), - this.WhenAny(x => x.DownloadsLocation.InError), - this.WhenAny(x => x.StagingLocation.InError), - this.WhenAny(x => x.ModlistSettings) - .Select(x => x?.InError ?? Observable.Return(false)) - .Switch(), - (g, d, s, ml) => !g && !d && !s && !ml) - .ObserveOnGuiThread(), - execute: async () => - { - try - { - string outputFile = $"{ModlistSettings.ModListName}{ExtensionManager.Extension}"; - if (!string.IsNullOrWhiteSpace(parent.OutputLocation.TargetPath)) - { - outputFile = Path.Combine(parent.OutputLocation.TargetPath, outputFile); - } - ActiveCompilation = new VortexCompiler( - game: SelectedGame.Game, - gamePath: GameLocation.TargetPath, - vortexFolder: VortexCompiler.TypicalVortexFolder(), - downloadsFolder: DownloadsLocation.TargetPath, - stagingFolder: StagingLocation.TargetPath, - outputFile: outputFile) - { - ModListName = ModlistSettings.ModListName, - ModListAuthor = ModlistSettings.AuthorText, - ModListDescription = ModlistSettings.Description, - ModListImage = ModlistSettings.ImagePath.TargetPath, - ModListWebsite = ModlistSettings.Website, - ModListReadme = ModlistSettings.ReadMeText.TargetPath, - }; - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Error(ex, $"Compiler error"); - return; - } - await Task.Run(async () => - { - try - { - await ActiveCompilation.Begin(); - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Error(ex, $"Compiler error"); - } - finally - { - StatusTracker = null; - ActiveCompilation.Dispose(); - ActiveCompilation = null; - } - }); - }); + CanCompile = Observable.CombineLatest( + this.WhenAny(x => x.GameLocation.InError), + this.WhenAny(x => x.DownloadsLocation.InError), + this.WhenAny(x => x.StagingLocation.InError), + this.WhenAny(x => x.ModlistSettings) + .Select(x => x?.InError ?? Observable.Return(false)) + .Switch(), + (g, d, s, ml) => !g && !d && !s && !ml) + .Publish() + .RefCount(); // Load settings _settings = parent.MWVM.Settings.Compiler.VortexCompilation; @@ -167,7 +118,7 @@ namespace Wabbajack .DisposeWith(CompositeDisposable); // Load custom game settings when game type changes - this.WhenAny(x => x.SelectedGame) + (this).WhenAny(x => x.SelectedGame) .Select(game => _settings.ModlistSettings.TryCreate(game.Game)) .Pairwise() .Subscribe(pair => @@ -230,5 +181,39 @@ namespace Wabbajack var gogGame = GOGHandler.Instance.Games.FirstOrDefault(g => g.Game.HasValue && g.Game == SelectedGame.Game); GameLocation.TargetPath = gogGame?.Path; } + + public async Task Compile() + { + string outputFile = $"{ModlistSettings.ModListName}{ExtensionManager.Extension}"; + if (!string.IsNullOrWhiteSpace(Parent.OutputLocation.TargetPath)) + { + outputFile = Path.Combine(Parent.OutputLocation.TargetPath, outputFile); + } + try + { + ActiveCompilation = new VortexCompiler( + game: SelectedGame.Game, + gamePath: GameLocation.TargetPath, + vortexFolder: VortexCompiler.TypicalVortexFolder(), + downloadsFolder: DownloadsLocation.TargetPath, + stagingFolder: StagingLocation.TargetPath, + outputFile: outputFile) + { + ModListName = ModlistSettings.ModListName, + ModListAuthor = ModlistSettings.AuthorText, + ModListDescription = ModlistSettings.Description, + ModListImage = ModlistSettings.ImagePath.TargetPath, + ModListWebsite = ModlistSettings.Website, + ModListReadme = ModlistSettings.ReadMeText.TargetPath, + }; + await ActiveCompilation.Begin(); + } + finally + { + StatusTracker = null; + ActiveCompilation.Dispose(); + ActiveCompilation = null; + } + } } } diff --git a/Wabbajack/View Models/Installers/ISubInstallerVM.cs b/Wabbajack/View Models/Installers/ISubInstallerVM.cs index 249fcc7a..208a20d0 100644 --- a/Wabbajack/View Models/Installers/ISubInstallerVM.cs +++ b/Wabbajack/View Models/Installers/ISubInstallerVM.cs @@ -12,11 +12,12 @@ namespace Wabbajack public interface ISubInstallerVM { InstallerVM Parent { get; } - IReactiveCommand BeginCommand { get; } AInstaller ActiveInstallation { get; } void Unload(); bool SupportsAfterInstallNavigation { get; } void AfterInstallNavigation(); int ConfigVisualVerticalOffset { get; } + IObservable CanInstall { get; } + Task Install(); } } diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index 677f1f86..bf648767 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -89,6 +89,7 @@ namespace Wabbajack public IReactiveCommand BackCommand { get; } public IReactiveCommand CloseWhenCompleteCommand { get; } public IReactiveCommand GoToInstallCommand { get; } + public IReactiveCommand BeginCommand { get; } public InstallerVM(MainWindowVM mainWindowVM) { @@ -306,10 +307,26 @@ namespace Wabbajack .Subscribe() .DisposeWith(CompositeDisposable); + BeginCommand = ReactiveCommand.CreateFromTask( + canExecute: this.WhenAny(x => x.Installer.CanInstall) + .Switch(), + execute: async () => + { + try + { + await this.Installer.Install(); + } + catch (Exception ex) + { + while (ex.InnerException != null) ex = ex.InnerException; + Utils.Log(ex.StackTrace); + Utils.Log(ex.ToString()); + Utils.Log($"{ex.Message} - Can't continue"); + } + }); + // When sub installer begins an install, mark state variable - this.WhenAny(x => x.Installer.BeginCommand) - .Select(x => x?.StartingExecution() ?? Observable.Empty()) - .Switch() + BeginCommand.StartingExecution() .Subscribe(_ => { StartedInstallation = true; @@ -317,9 +334,7 @@ namespace Wabbajack .DisposeWith(CompositeDisposable); // When sub installer ends an install, mark state variable - this.WhenAny(x => x.Installer.BeginCommand) - .Select(x => x?.EndingExecution() ?? Observable.Empty()) - .Switch() + BeginCommand.EndingExecution() .Subscribe(_ => { Completed = true; diff --git a/Wabbajack/View Models/Installers/MO2InstallerVM.cs b/Wabbajack/View Models/Installers/MO2InstallerVM.cs index 7ec60ec4..4981517c 100644 --- a/Wabbajack/View Models/Installers/MO2InstallerVM.cs +++ b/Wabbajack/View Models/Installers/MO2InstallerVM.cs @@ -18,7 +18,7 @@ namespace Wabbajack { public InstallerVM Parent { get; } - public IReactiveCommand BeginCommand { get; } + public IObservable CanInstall { get; } [Reactive] public AInstaller ActiveInstallation { get; private set; } @@ -58,61 +58,13 @@ namespace Wabbajack DownloadLocation.AdditionalError = this.WhenAny(x => x.DownloadLocation.TargetPath) .Select(x => Utils.IsDirectoryPathValid(x)); - BeginCommand = ReactiveCommand.CreateFromTask( - canExecute: Observable.CombineLatest( - this.WhenAny(x => x.Location.InError), - this.WhenAny(x => x.DownloadLocation.InError), - installerVM.WhenAny(x => x.ModListLocation.InError), - resultSelector: (loc, modlist, download) => - { - return !loc && !download && !modlist; - }) - .ObserveOnGuiThread(), - execute: async () => + CanInstall = Observable.CombineLatest( + this.WhenAny(x => x.Location.InError), + this.WhenAny(x => x.DownloadLocation.InError), + installerVM.WhenAny(x => x.ModListLocation.InError), + resultSelector: (loc, modlist, download) => { - AInstaller installer; - - try - { - installer = new MO2Installer( - archive: installerVM.ModListLocation.TargetPath, - modList: installerVM.ModList.SourceModList, - outputFolder: Location.TargetPath, - downloadFolder: DownloadLocation.TargetPath); - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Log(ex.StackTrace); - Utils.Log(ex.ToString()); - Utils.Log($"{ex.Message} - Can't continue"); - ActiveInstallation = null; - return; - } - - await Task.Run(async () => - { - IDisposable subscription = null; - try - { - var workTask = installer.Begin(); - ActiveInstallation = installer; - await workTask; - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Log(ex.StackTrace); - Utils.Log(ex.ToString()); - Utils.Log($"{ex.Message} - Can't continue"); - } - finally - { - // Dispose of CPU tracking systems - subscription?.Dispose(); - ActiveInstallation = null; - } - }); + return !loc && !download && !modlist; }); // Have Installation location updates modify the downloads location if empty @@ -131,7 +83,7 @@ namespace Wabbajack _CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath) .Select(path => path == null ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path)) .ToProperty(this, nameof(CurrentSettings)); - this.WhenAny(x => x.CurrentSettings) + (this).WhenAny(x => x.CurrentSettings) .Pairwise() .Subscribe(settingsPair => { @@ -184,5 +136,29 @@ namespace Wabbajack { Process.Start("explorer.exe", Location.TargetPath); } + + public async Task Install() + { + var installer = new MO2Installer( + archive: Parent.ModListLocation.TargetPath, + modList: Parent.ModList.SourceModList, + outputFolder: Location.TargetPath, + downloadFolder: DownloadLocation.TargetPath); + + await Task.Run(async () => + { + try + { + var workTask = installer.Begin(); + ActiveInstallation = installer; + await workTask; + return ErrorResponse.Success; + } + finally + { + ActiveInstallation = null; + } + }); + } } } diff --git a/Wabbajack/View Models/Installers/VortexInstallerVM.cs b/Wabbajack/View Models/Installers/VortexInstallerVM.cs index 0932b3c6..efc91e02 100644 --- a/Wabbajack/View Models/Installers/VortexInstallerVM.cs +++ b/Wabbajack/View Models/Installers/VortexInstallerVM.cs @@ -15,8 +15,6 @@ namespace Wabbajack { public InstallerVM Parent { get; } - public IReactiveCommand BeginCommand { get; } - [Reactive] public AInstaller ActiveInstallation { get; private set; } @@ -33,6 +31,8 @@ namespace Wabbajack public int ConfigVisualVerticalOffset => 0; + public IObservable CanInstall { get; } + public VortexInstallerVM(InstallerVM installerVM) { Parent = installerVM; @@ -40,60 +40,11 @@ namespace Wabbajack _TargetGame = installerVM.WhenAny(x => x.ModList.SourceModList.GameType) .ToProperty(this, nameof(TargetGame)); - BeginCommand = ReactiveCommand.CreateFromTask( - canExecute: Observable.CombineLatest( - this.WhenAny(x => x.TargetGame) - .Select(game => VortexCompiler.IsActiveVortexGame(game)), - installerVM.WhenAny(x => x.ModListLocation.InError), - resultSelector: (isVortexGame, modListErr) => isVortexGame && !modListErr), - execute: async () => - { - AInstaller installer; - - try - { - var download = VortexCompiler.RetrieveDownloadLocation(TargetGame); - var staging = VortexCompiler.RetrieveStagingLocation(TargetGame); - installer = new VortexInstaller( - archive: installerVM.ModListLocation.TargetPath, - modList: installerVM.ModList.SourceModList, - outputFolder: staging, - downloadFolder: download); - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Log(ex.StackTrace); - Utils.Log(ex.ToString()); - Utils.Log($"{ex.Message} - Can't continue"); - ActiveInstallation = null; - return; - } - - await Task.Run(async () => - { - IDisposable subscription = null; - try - { - var workTask = installer.Begin(); - ActiveInstallation = installer; - await workTask; - } - catch (Exception ex) - { - while (ex.InnerException != null) ex = ex.InnerException; - Utils.Log(ex.StackTrace); - Utils.Log(ex.ToString()); - Utils.Log($"{ex.Message} - Can't continue"); - } - finally - { - // Dispose of CPU tracking systems - subscription?.Dispose(); - ActiveInstallation = null; - } - }); - }); + CanInstall = Observable.CombineLatest( + this.WhenAny(x => x.TargetGame) + .Select(game => VortexCompiler.IsActiveVortexGame(game)), + installerVM.WhenAny(x => x.ModListLocation.InError), + resultSelector: (isVortexGame, modListErr) => isVortexGame && !modListErr); } public void Unload() @@ -104,5 +55,32 @@ namespace Wabbajack { throw new NotImplementedException(); } + + public async Task Install() + { + AInstaller installer; + + var download = VortexCompiler.RetrieveDownloadLocation(TargetGame); + var staging = VortexCompiler.RetrieveStagingLocation(TargetGame); + installer = new VortexInstaller( + archive: Parent.ModListLocation.TargetPath, + modList: Parent.ModList.SourceModList, + outputFolder: staging, + downloadFolder: download); + + await Task.Run(async () => + { + try + { + var workTask = installer.Begin(); + ActiveInstallation = installer; + await workTask; + } + finally + { + ActiveInstallation = null; + } + }); + } } } diff --git a/Wabbajack/Views/Compilers/CompilerView.xaml b/Wabbajack/Views/Compilers/CompilerView.xaml index 3f435e55..3eeaf5b1 100644 --- a/Wabbajack/Views/Compilers/CompilerView.xaml +++ b/Wabbajack/Views/Compilers/CompilerView.xaml @@ -232,7 +232,7 @@ Grid.Row="0" Grid.RowSpan="3" Grid.Column="5" - Command="{Binding Compiler.BeginCommand}" /> + Command="{Binding BeginCommand}" /> + Command="{Binding BeginCommand, Mode=OneWay}" /> Date: Wed, 18 Dec 2019 22:44:43 -0600 Subject: [PATCH 06/14] AttentionBorder view --- Wabbajack/Themes/Styles.xaml | 55 ---- Wabbajack/Views/Common/AttentionBorder.xaml | 68 +++++ .../Views/Common/AttentionBorder.xaml.cs | 36 +++ .../Compilers/CompilationCompleteView.xaml | 202 +++++++------- Wabbajack/Views/Compilers/CompilerView.xaml | 15 +- .../Installers/InstallationCompleteView.xaml | 250 +++++++++--------- .../Views/Installers/InstallationView.xaml | 17 +- Wabbajack/Wabbajack.csproj | 7 + 8 files changed, 354 insertions(+), 296 deletions(-) create mode 100644 Wabbajack/Views/Common/AttentionBorder.xaml create mode 100644 Wabbajack/Views/Common/AttentionBorder.xaml.cs diff --git a/Wabbajack/Themes/Styles.xaml b/Wabbajack/Themes/Styles.xaml index acd0827f..13314fef 100644 --- a/Wabbajack/Themes/Styles.xaml +++ b/Wabbajack/Themes/Styles.xaml @@ -3748,59 +3748,4 @@ diff --git a/Wabbajack/Views/Common/AttentionBorder.xaml b/Wabbajack/Views/Common/AttentionBorder.xaml new file mode 100644 index 00000000..c76d2eae --- /dev/null +++ b/Wabbajack/Views/Common/AttentionBorder.xaml @@ -0,0 +1,68 @@ + + + + + + + + diff --git a/Wabbajack/Views/Common/AttentionBorder.xaml.cs b/Wabbajack/Views/Common/AttentionBorder.xaml.cs new file mode 100644 index 00000000..0670645c --- /dev/null +++ b/Wabbajack/Views/Common/AttentionBorder.xaml.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Wabbajack +{ + /// + /// Interaction logic for AttentionBorder.xaml + /// + public partial class AttentionBorder : UserControl + { + public object DisplayContent + { + get => (object)GetValue(DisplayContentProperty); + set => SetValue(DisplayContentProperty, value); + } + public static readonly DependencyProperty DisplayContentProperty = DependencyProperty.Register(nameof(DisplayContent), typeof(object), typeof(AttentionBorder), + new FrameworkPropertyMetadata(default(object))); + + public AttentionBorder() + { + InitializeComponent(); + } + } +} diff --git a/Wabbajack/Views/Compilers/CompilationCompleteView.xaml b/Wabbajack/Views/Compilers/CompilationCompleteView.xaml index 8df53c90..d1cda2f3 100644 --- a/Wabbajack/Views/Compilers/CompilationCompleteView.xaml +++ b/Wabbajack/Views/Compilers/CompilationCompleteView.xaml @@ -9,93 +9,94 @@ d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - + + + - - + + - - - - - - - - - - + + + + + - - - - - - - - - + + + - - + + diff --git a/Wabbajack/Views/Compilers/CompilerView.xaml b/Wabbajack/Views/Compilers/CompilerView.xaml index 3eeaf5b1..73938b5b 100644 --- a/Wabbajack/Views/Compilers/CompilerView.xaml +++ b/Wabbajack/Views/Compilers/CompilerView.xaml @@ -251,14 +251,13 @@ Grid.Column="2" ProgressPercent="{Binding PercentCompleted, Mode=OneWay}" Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsNotNullVisibilityConverter}, ConverterParameter=False}" /> - - - - - + + + + + + + diff --git a/Wabbajack/Views/Installers/InstallationCompleteView.xaml b/Wabbajack/Views/Installers/InstallationCompleteView.xaml index aaeaef4e..341d4ebd 100644 --- a/Wabbajack/Views/Installers/InstallationCompleteView.xaml +++ b/Wabbajack/Views/Installers/InstallationCompleteView.xaml @@ -9,136 +9,138 @@ d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - + + + - - + + - - - - - - - - - - + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + + Grid.Column="2" + VerticalAlignment="Center" + Background="Transparent"> + + + + + + + + + + + + + + + - - + + diff --git a/Wabbajack/Views/Installers/InstallationView.xaml b/Wabbajack/Views/Installers/InstallationView.xaml index 3957f43e..83e6f2f6 100644 --- a/Wabbajack/Views/Installers/InstallationView.xaml +++ b/Wabbajack/Views/Installers/InstallationView.xaml @@ -405,15 +405,14 @@ Grid.Column="2" ProgressPercent="{Binding PercentCompleted, Mode=OneWay}" Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsNotNullVisibilityConverter}, ConverterParameter=False}" /> - - - - - - + + + + + + + + diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj index 786f43c7..e587a2f6 100644 --- a/Wabbajack/Wabbajack.csproj +++ b/Wabbajack/Wabbajack.csproj @@ -172,6 +172,9 @@ MSBuild:Compile Designer + + AttentionBorder.xaml + UnderMaintenanceOverlay.xaml @@ -274,6 +277,10 @@ WebBrowserView.xaml + + Designer + MSBuild:Compile + Designer MSBuild:Compile From 5a8c19fbae98e3c5ae54f3bdda3b950b0e324b04 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Wed, 18 Dec 2019 23:22:39 -0600 Subject: [PATCH 07/14] Failure compiles/installs show red --- Wabbajack/View Models/Compilers/CompilerVM.cs | 22 ++++----- .../View Models/Installers/InstallerVM.cs | 22 ++++----- Wabbajack/Views/Common/AttentionBorder.xaml | 48 +++++++++++++++++++ .../Views/Common/AttentionBorder.xaml.cs | 8 ++++ .../Compilers/CompilationCompleteView.xaml | 15 ++++-- Wabbajack/Views/Compilers/CompilerView.xaml | 2 +- .../Installers/InstallationCompleteView.xaml | 15 ++++-- .../Views/Installers/InstallationView.xaml | 2 +- 8 files changed, 100 insertions(+), 34 deletions(-) diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs index 8f54455f..136cc742 100644 --- a/Wabbajack/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/CompilerVM.cs @@ -58,7 +58,7 @@ namespace Wabbajack public bool StartedCompilation { get; set; } [Reactive] - public bool Completed { get; set; } + public ErrorResponse? Completed { get; set; } public CompilerVM(MainWindowVM mainWindowVM) { @@ -137,7 +137,7 @@ namespace Wabbajack { mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM; StartedCompilation = false; - Completed = false; + Completed = null; }, canExecute: this.WhenAny(x => x.Compiling) .Select(x => !x)); @@ -174,7 +174,7 @@ namespace Wabbajack { if (compiler == null) { - return Observable.Return(completed ? 1f : 0f); + return Observable.Return(completed != null ? 1f : 0f); } return compiler.PercentCompleted.StartWith(0); }) @@ -190,9 +190,11 @@ namespace Wabbajack try { await this.Compiler.Compile(); + Completed = ErrorResponse.Success; } catch (Exception ex) { + Completed = ErrorResponse.Fail(ex); while (ex.InnerException != null) ex = ex.InnerException; Utils.Error(ex, $"Compiler error"); } @@ -206,14 +208,6 @@ namespace Wabbajack }) .DisposeWith(CompositeDisposable); - // When sub compiler finishes a compile, mark state variable - BeginCommand.EndingExecution() - .Subscribe(_ => - { - Completed = true; - }) - .DisposeWith(CompositeDisposable); - // Listen for user interventions, and compile a dynamic list of all unhandled ones var activeInterventions = this.WhenAny(x => x.Compiler.ActiveCompilation) .SelectMany(c => c?.LogMessages ?? Observable.Empty()) @@ -231,14 +225,16 @@ namespace Wabbajack .ToProperty(this, nameof(ActiveGlobalUserIntervention)); CloseWhenCompleteCommand = ReactiveCommand.Create( - canExecute: this.WhenAny(x => x.Completed), + canExecute: this.WhenAny(x => x.Completed) + .Select(x => x != null), execute: () => { MWVM.ShutdownApplication(); }); GoToModlistCommand = ReactiveCommand.Create( - canExecute: this.WhenAny(x => x.Completed), + canExecute: this.WhenAny(x => x.Completed) + .Select(x => x != null), execute: () => { if (string.IsNullOrWhiteSpace(OutputLocation.TargetPath)) diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index bf648767..6108ecc5 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -50,7 +50,7 @@ namespace Wabbajack public bool StartedInstallation { get; set; } [Reactive] - public bool Completed { get; set; } + public ErrorResponse? Completed { get; set; } private readonly ObservableAsPropertyHelper _image; public ImageSource Image => _image.Value; @@ -181,7 +181,7 @@ namespace Wabbajack execute: () => { StartedInstallation = false; - Completed = false; + Completed = null; mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM; }, canExecute: this.WhenAny(x => x.Installing) @@ -195,7 +195,7 @@ namespace Wabbajack { if (installer == null) { - return Observable.Return(completed ? 1f : 0f); + return Observable.Return(completed != null ? 1f : 0f); } return installer.PercentCompleted.StartWith(0f); }) @@ -315,6 +315,7 @@ namespace Wabbajack try { await this.Installer.Install(); + Completed = ErrorResponse.Success; } catch (Exception ex) { @@ -322,6 +323,7 @@ namespace Wabbajack Utils.Log(ex.StackTrace); Utils.Log(ex.ToString()); Utils.Log($"{ex.Message} - Can't continue"); + Completed = ErrorResponse.Fail(ex); } }); @@ -333,14 +335,6 @@ namespace Wabbajack }) .DisposeWith(CompositeDisposable); - // When sub installer ends an install, mark state variable - BeginCommand.EndingExecution() - .Subscribe(_ => - { - Completed = true; - }) - .DisposeWith(CompositeDisposable); - // Listen for user interventions, and compile a dynamic list of all unhandled ones var activeInterventions = this.WhenAny(x => x.Installer.ActiveInstallation) .SelectMany(c => c?.LogMessages ?? Observable.Empty()) @@ -358,7 +352,8 @@ namespace Wabbajack .ToProperty(this, nameof(ActiveGlobalUserIntervention)); CloseWhenCompleteCommand = ReactiveCommand.Create( - canExecute: this.WhenAny(x => x.Completed), + canExecute: this.WhenAny(x => x.Completed) + .Select(x => x != null), execute: () => { MWVM.ShutdownApplication(); @@ -366,7 +361,8 @@ namespace Wabbajack GoToInstallCommand = ReactiveCommand.Create( canExecute: Observable.CombineLatest( - this.WhenAny(x => x.Completed), + this.WhenAny(x => x.Completed) + .Select(x => x != null), this.WhenAny(x => x.Installer.SupportsAfterInstallNavigation), resultSelector: (complete, supports) => complete && supports), execute: () => diff --git a/Wabbajack/Views/Common/AttentionBorder.xaml b/Wabbajack/Views/Common/AttentionBorder.xaml index c76d2eae..b1ba1137 100644 --- a/Wabbajack/Views/Common/AttentionBorder.xaml +++ b/Wabbajack/Views/Common/AttentionBorder.xaml @@ -18,6 +18,7 @@ + @@ -60,6 +61,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Wabbajack/Views/Common/AttentionBorder.xaml.cs b/Wabbajack/Views/Common/AttentionBorder.xaml.cs index 0670645c..c43909ab 100644 --- a/Wabbajack/Views/Common/AttentionBorder.xaml.cs +++ b/Wabbajack/Views/Common/AttentionBorder.xaml.cs @@ -28,6 +28,14 @@ namespace Wabbajack public static readonly DependencyProperty DisplayContentProperty = DependencyProperty.Register(nameof(DisplayContent), typeof(object), typeof(AttentionBorder), new FrameworkPropertyMetadata(default(object))); + public bool Failure + { + get => (bool)GetValue(FailureProperty); + set => SetValue(FailureProperty, value); + } + public static readonly DependencyProperty FailureProperty = DependencyProperty.Register(nameof(Failure), typeof(bool), typeof(AttentionBorder), + new FrameworkPropertyMetadata(default(bool))); + public AttentionBorder() { InitializeComponent(); diff --git a/Wabbajack/Views/Compilers/CompilationCompleteView.xaml b/Wabbajack/Views/Compilers/CompilationCompleteView.xaml index d1cda2f3..3c69841f 100644 --- a/Wabbajack/Views/Compilers/CompilationCompleteView.xaml +++ b/Wabbajack/Views/Compilers/CompilationCompleteView.xaml @@ -9,7 +9,7 @@ d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> - + @@ -29,11 +29,20 @@ VerticalAlignment="Bottom" FontFamily="Lucida Sans" FontSize="22" - FontWeight="Black" - Text="Compilation Complete"> + FontWeight="Black"> + + + - + diff --git a/Wabbajack/Views/Installers/InstallationCompleteView.xaml b/Wabbajack/Views/Installers/InstallationCompleteView.xaml index 341d4ebd..26edba90 100644 --- a/Wabbajack/Views/Installers/InstallationCompleteView.xaml +++ b/Wabbajack/Views/Installers/InstallationCompleteView.xaml @@ -9,7 +9,7 @@ d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> - + @@ -30,11 +30,20 @@ VerticalAlignment="Bottom" FontFamily="Lucida Sans" FontSize="22" - FontWeight="Black" - Text="Installation Complete"> + FontWeight="Black"> + + + - + From a4a149d01c366f2b4a2bc00280a284e2ab066304 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Thu, 19 Dec 2019 22:07:53 -0600 Subject: [PATCH 08/14] Added catch to ModListMetadataVM's needs download check Was failing to calculate hash while file was locked during download --- Wabbajack/View Models/ModListMetadataVM.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Wabbajack/View Models/ModListMetadataVM.cs b/Wabbajack/View Models/ModListMetadataVM.cs index a719600f..d4ea773e 100644 --- a/Wabbajack/View Models/ModListMetadataVM.cs +++ b/Wabbajack/View Models/ModListMetadataVM.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -76,7 +76,17 @@ namespace Wabbajack _Exists = Observable.Interval(TimeSpan.FromSeconds(0.5)) .Unit() .StartWith(Unit.Default) - .Select(_ => !metadata.NeedsDownload(Location)) + .Select(_ => + { + try + { + return !metadata.NeedsDownload(Location); + } + catch (Exception) + { + return true; + } + }) .ToProperty(this, nameof(Exists)); } From 74bbb5a4ecc2eea3816fb30bb043bb07669b2654 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Thu, 19 Dec 2019 23:09:53 -0600 Subject: [PATCH 09/14] Readme opens after install, and after modlist download --- .../View Models/Installers/InstallerVM.cs | 36 +++++-------------- Wabbajack/View Models/ModListMetadataVM.cs | 32 ++++++++++++----- Wabbajack/View Models/ModListVM.cs | 26 ++++++++++++++ 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index 6108ecc5..94db86eb 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -263,7 +263,7 @@ namespace Wabbajack // Define commands ShowReportCommand = ReactiveCommand.Create(ShowReport); OpenReadmeCommand = ReactiveCommand.Create( - execute: OpenReadmeWindow, + execute: () => this.ModList?.OpenReadmeWindow(), canExecute: this.WhenAny(x => x.ModList) .Select(modList => !string.IsNullOrEmpty(modList?.Readme)) .ObserveOnGuiThread()); @@ -316,6 +316,14 @@ namespace Wabbajack { await this.Installer.Install(); Completed = ErrorResponse.Success; + try + { + this.ModList?.OpenReadmeWindow(); + } + catch (Exception ex) + { + Utils.Error(ex); + } } catch (Exception ex) { @@ -377,31 +385,5 @@ namespace Wabbajack File.WriteAllText(file, HTMLReport); Process.Start(file); } - - private void OpenReadmeWindow() - { - if (string.IsNullOrEmpty(ModList.Readme)) return; - using (var fs = new FileStream(ModListLocation.TargetPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var ar = new ZipArchive(fs, ZipArchiveMode.Read)) - using (var ms = new MemoryStream()) - { - var entry = ar.GetEntry(ModList.Readme); - if (entry == null) - { - Utils.Log($"Tried to open a non-existant readme: {ModList.Readme}"); - return; - } - using (var e = entry.Open()) - { - e.CopyTo(ms); - } - ms.Seek(0, SeekOrigin.Begin); - using (var reader = new StreamReader(ms)) - { - var viewer = new TextViewer(reader.ReadToEnd(), ModList.Name); - viewer.Show(); - } - } - } } } diff --git a/Wabbajack/View Models/ModListMetadataVM.cs b/Wabbajack/View Models/ModListMetadataVM.cs index d4ea773e..90424b99 100644 --- a/Wabbajack/View Models/ModListMetadataVM.cs +++ b/Wabbajack/View Models/ModListMetadataVM.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -44,7 +44,7 @@ namespace Wabbajack Metadata = metadata; IsBroken = metadata.ValidationSummary.HasFailures; OpenWebsiteCommand = ReactiveCommand.Create(() => Process.Start($"https://www.wabbajack.org/modlist/{Metadata.Links.MachineURL}")); - ExecuteCommand = ReactiveCommand.CreateFromObservable( + ExecuteCommand = ReactiveCommand.CreateFromObservable( canExecute: this.WhenAny(x => x.IsBroken).Select(x => !x), execute: (unit) => Observable.Return(unit) @@ -63,15 +63,31 @@ namespace Wabbajack } return exists; }) + .Where(exists => exists) // Do any install page swap over on GUI thread .ObserveOnGuiThread() - .Do(exists => + .Select(_ => { - if (exists) - { - _parent.MWVM.OpenInstaller(Path.GetFullPath(Location)); - } - })); + _parent.MWVM.OpenInstaller(Path.GetFullPath(Location)); + + // Wait for modlist member to be filled, then open its readme + return _parent.MWVM.Installer.Value.WhenAny(x => x.ModList) + .NotNull() + .Take(1) + .Do(modList => + { + try + { + modList.OpenReadmeWindow(); + } + catch (Exception ex) + { + Utils.Error(ex); + } + }); + }) + .Switch() + .Unit()); _Exists = Observable.Interval(TimeSpan.FromSeconds(0.5)) .Unit() diff --git a/Wabbajack/View Models/ModListVM.cs b/Wabbajack/View Models/ModListVM.cs index 5c3d2a02..603041c2 100644 --- a/Wabbajack/View Models/ModListVM.cs +++ b/Wabbajack/View Models/ModListVM.cs @@ -82,5 +82,31 @@ namespace Wabbajack .Replay(1) .RefCount(); } + + public void OpenReadmeWindow() + { + if (string.IsNullOrEmpty(Readme)) return; + using (var fs = new FileStream(ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var ar = new ZipArchive(fs, ZipArchiveMode.Read)) + using (var ms = new MemoryStream()) + { + var entry = ar.GetEntry(Readme); + if (entry == null) + { + Utils.Log($"Tried to open a non-existant readme: {Readme}"); + return; + } + using (var e = entry.Open()) + { + e.CopyTo(ms); + } + ms.Seek(0, SeekOrigin.Begin); + using (var reader = new StreamReader(ms)) + { + var viewer = new TextViewer(reader.ReadToEnd(), Name); + viewer.Show(); + } + } + } } } From f26747bfe5d0c1c2574e4fd476f05cfb16ce8e7d Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Fri, 20 Dec 2019 01:14:43 -0600 Subject: [PATCH 10/14] Readme can be website now --- Wabbajack.Lib/ACompiler.cs | 3 + Wabbajack.Lib/Data.cs | 7 +- Wabbajack/Settings.cs | 1 + .../View Models/Compilers/MO2CompilerVM.cs | 3 +- .../Compilers/ModlistSettingsEditorVM.cs | 46 +++++++++++-- .../View Models/Compilers/VortexCompilerVM.cs | 3 +- Wabbajack/View Models/ModListVM.cs | 42 +++++++----- Wabbajack/Views/Compilers/CompilerView.xaml | 67 +++++++++++++++++-- 8 files changed, 140 insertions(+), 32 deletions(-) diff --git a/Wabbajack.Lib/ACompiler.cs b/Wabbajack.Lib/ACompiler.cs index eecc34f1..b11208d2 100644 --- a/Wabbajack.Lib/ACompiler.cs +++ b/Wabbajack.Lib/ACompiler.cs @@ -22,6 +22,7 @@ namespace Wabbajack.Lib public abstract class ACompiler : ABatchProcessor { public string ModListName, ModListAuthor, ModListDescription, ModListImage, ModListWebsite, ModListReadme; + public bool ReadmeIsWebsite; public string WabbajackVersion; protected static string _vfsCacheName = "vfs_compile_cache.bin"; @@ -94,6 +95,8 @@ namespace Wabbajack.Lib ModList.Readme = $"readme{readme.Extension}"; } + ModList.ReadmeIsWebsite = ReadmeIsWebsite; + //ModList.ToJSON(Path.Combine(ModListOutputFolder, "modlist.json")); ModList.ToCERAS(Path.Combine(ModListOutputFolder, "modlist"), ref CerasConfig.Config); diff --git a/Wabbajack.Lib/Data.cs b/Wabbajack.Lib/Data.cs index 2a7f1674..e438eadc 100644 --- a/Wabbajack.Lib/Data.cs +++ b/Wabbajack.Lib/Data.cs @@ -87,10 +87,15 @@ namespace Wabbajack.Lib public string Website; /// - /// Hash of the readme + /// readme path or website /// public string Readme; + /// + /// Whether readme is a website + /// + public bool ReadmeIsWebsite; + /// /// Content Report in HTML form /// diff --git a/Wabbajack/Settings.cs b/Wabbajack/Settings.cs index ceae3308..f6111ebd 100644 --- a/Wabbajack/Settings.cs +++ b/Wabbajack/Settings.cs @@ -75,6 +75,7 @@ namespace Wabbajack public string Author { get; set; } public string Description { get; set; } public string Website { get; set; } + public bool ReadmeIsWebsite { get; set; } public string Readme { get; set; } public string SplashScreen { get; set; } } diff --git a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs index 7b5971dc..55576556 100644 --- a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs @@ -191,7 +191,8 @@ namespace Wabbajack ModListDescription = ModlistSettings.Description, ModListImage = ModlistSettings.ImagePath.TargetPath, ModListWebsite = ModlistSettings.Website, - ModListReadme = ModlistSettings.ReadMeText.TargetPath, + ModListReadme = ModlistSettings.ReadmeIsWebsite ? ModlistSettings.ReadmeWebsite : ModlistSettings.ReadmeFilePath.TargetPath, + ReadmeIsWebsite = ModlistSettings.ReadmeIsWebsite, }; await ActiveCompilation.Begin(); } diff --git a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs index 426f7c07..3e91c002 100644 --- a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs +++ b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs @@ -1,7 +1,9 @@ using System; using System.Reactive.Linq; +using System.Windows.Input; using DynamicData; using Microsoft.WindowsAPICodePack.Dialogs; +using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.Lib; @@ -22,13 +24,22 @@ namespace Wabbajack public FilePickerVM ImagePath { get; } - public FilePickerVM ReadMeText { get; } + public FilePickerVM ReadmeFilePath { get; } + + [Reactive] + public string ReadmeWebsite { get; set; } [Reactive] public string Website { get; set; } + [Reactive] + public bool ReadmeIsWebsite { get; set; } + public IObservable InError { get; } + public ICommand SwapToTextReadmeCommand { get; } + public ICommand SwapToWebsiteReadmeCommand { get; } + public ModlistSettingsEditorVM(CompilationModlistSettings settings) { this._settings = settings; @@ -38,19 +49,24 @@ namespace Wabbajack PathType = FilePickerVM.PathTypeOptions.File, }; ImagePath.Filters.Add(new CommonFileDialogFilter("Banner image", "*.png")); - ReadMeText = new FilePickerVM() + ReadmeFilePath = new FilePickerVM() { PathType = FilePickerVM.PathTypeOptions.File, ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty, }; - ReadMeText.Filters.Add(new CommonFileDialogFilter("Text", "*.txt")); + ReadmeFilePath.Filters.Add(new CommonFileDialogFilter("Text", "*.txt")); + ReadmeFilePath.Filters.Add(new CommonFileDialogFilter("HTML File", "*.html")); InError = Observable.CombineLatest( this.WhenAny(x => x.ImagePath.ErrorState).Select(err => err.Failed), - this.WhenAny(x => x.ReadMeText.ErrorState).Select(err => err.Failed), - resultSelector: (img, readme) => img || readme) + this.WhenAny(x => x.ReadmeFilePath.ErrorState).Select(err => err.Failed), + this.WhenAny(x => x.ReadmeIsWebsite), + resultSelector: (img, readme, isWebsite) => img || (readme && isWebsite)) .Publish() .RefCount(); + + SwapToTextReadmeCommand = ReactiveCommand.Create(() => ReadmeIsWebsite = false); + SwapToWebsiteReadmeCommand = ReactiveCommand.Create(() => ReadmeIsWebsite = true); } public void Init() @@ -61,7 +77,15 @@ namespace Wabbajack ModListName = _settings.ModListName; } Description = _settings.Description; - ReadMeText.TargetPath = _settings.Readme; + ReadmeIsWebsite = _settings.ReadmeIsWebsite; + if (ReadmeIsWebsite) + { + ReadmeWebsite = _settings.Readme; + } + else + { + ReadmeFilePath.TargetPath = _settings.Readme; + } ImagePath.TargetPath = _settings.SplashScreen; Website = _settings.Website; } @@ -71,7 +95,15 @@ namespace Wabbajack _settings.Author = AuthorText; _settings.ModListName = ModListName; _settings.Description = Description; - _settings.Readme = ReadMeText.TargetPath; + _settings.ReadmeIsWebsite = ReadmeIsWebsite; + if (ReadmeIsWebsite) + { + _settings.Readme = ReadmeWebsite; + } + else + { + _settings.Readme = ReadmeFilePath.TargetPath; + } _settings.SplashScreen = ImagePath.TargetPath; _settings.Website = Website; } diff --git a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs index ca1399eb..2e9136d2 100644 --- a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs +++ b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs @@ -204,7 +204,8 @@ namespace Wabbajack ModListDescription = ModlistSettings.Description, ModListImage = ModlistSettings.ImagePath.TargetPath, ModListWebsite = ModlistSettings.Website, - ModListReadme = ModlistSettings.ReadMeText.TargetPath, + ModListReadme = ModlistSettings.ReadmeIsWebsite ? ModlistSettings.ReadmeWebsite : ModlistSettings.ReadmeFilePath.TargetPath, + ReadmeIsWebsite = ModlistSettings.ReadmeIsWebsite, }; await ActiveCompilation.Begin(); } diff --git a/Wabbajack/View Models/ModListVM.cs b/Wabbajack/View Models/ModListVM.cs index 603041c2..184d3d6b 100644 --- a/Wabbajack/View Models/ModListVM.cs +++ b/Wabbajack/View Models/ModListVM.cs @@ -1,5 +1,6 @@ using ReactiveUI; using System; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Reactive; @@ -86,25 +87,32 @@ namespace Wabbajack public void OpenReadmeWindow() { if (string.IsNullOrEmpty(Readme)) return; - using (var fs = new FileStream(ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var ar = new ZipArchive(fs, ZipArchiveMode.Read)) - using (var ms = new MemoryStream()) + if (SourceModList.ReadmeIsWebsite) { - var entry = ar.GetEntry(Readme); - if (entry == null) + Process.Start(Readme); + } + else + { + using (var fs = new FileStream(ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var ar = new ZipArchive(fs, ZipArchiveMode.Read)) + using (var ms = new MemoryStream()) { - Utils.Log($"Tried to open a non-existant readme: {Readme}"); - return; - } - using (var e = entry.Open()) - { - e.CopyTo(ms); - } - ms.Seek(0, SeekOrigin.Begin); - using (var reader = new StreamReader(ms)) - { - var viewer = new TextViewer(reader.ReadToEnd(), Name); - viewer.Show(); + var entry = ar.GetEntry(Readme); + if (entry == null) + { + Utils.Log($"Tried to open a non-existant readme: {Readme}"); + return; + } + using (var e = entry.Open()) + { + e.CopyTo(ms); + } + ms.Seek(0, SeekOrigin.Begin); + using (var reader = new StreamReader(ms)) + { + var viewer = new TextViewer(reader.ReadToEnd(), Name); + viewer.Show(); + } } } } diff --git a/Wabbajack/Views/Compilers/CompilerView.xaml b/Wabbajack/Views/Compilers/CompilerView.xaml index 92f37313..409b9104 100644 --- a/Wabbajack/Views/Compilers/CompilerView.xaml +++ b/Wabbajack/Views/Compilers/CompilerView.xaml @@ -150,12 +150,69 @@ - + + + + + + + + + + + Date: Fri, 20 Dec 2019 14:01:01 -0600 Subject: [PATCH 11/14] Ceras version tolerance enabled --- Wabbajack.Common/Utils.cs | 4 ++-- Wabbajack.Lib/ACompiler.cs | 2 +- Wabbajack.Lib/AInstaller.cs | 2 +- Wabbajack.Lib/CerasConfig.cs | 44 ++++++++++++++++++++---------------- Wabbajack.Lib/Data.cs | 9 ++++---- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs index 73d2e57d..63a99525 100644 --- a/Wabbajack.Common/Utils.cs +++ b/Wabbajack.Common/Utils.cs @@ -310,7 +310,7 @@ namespace Wabbajack.Common return new DynamicIniData(new FileIniDataParser().ReadData(new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(file))))); } - public static void ToCERAS(this T obj, string filename, ref SerializerConfig config) + public static void ToCERAS(this T obj, string filename, SerializerConfig config) { var ceras = new CerasSerializer(config); byte[] buffer = null; @@ -324,7 +324,7 @@ namespace Wabbajack.Common } } - public static T FromCERAS(this Stream data, ref SerializerConfig config) + public static T FromCERAS(this Stream data, SerializerConfig config) { var ceras = new CerasSerializer(config); byte[] bytes = data.ReadAll(); diff --git a/Wabbajack.Lib/ACompiler.cs b/Wabbajack.Lib/ACompiler.cs index b11208d2..91867405 100644 --- a/Wabbajack.Lib/ACompiler.cs +++ b/Wabbajack.Lib/ACompiler.cs @@ -98,7 +98,7 @@ namespace Wabbajack.Lib ModList.ReadmeIsWebsite = ReadmeIsWebsite; //ModList.ToJSON(Path.Combine(ModListOutputFolder, "modlist.json")); - ModList.ToCERAS(Path.Combine(ModListOutputFolder, "modlist"), ref CerasConfig.Config); + ModList.ToCERAS(Path.Combine(ModListOutputFolder, "modlist"), CerasConfig.Config); if (File.Exists(ModListOutputFile)) File.Delete(ModListOutputFile); diff --git a/Wabbajack.Lib/AInstaller.cs b/Wabbajack.Lib/AInstaller.cs index 23dfd790..b87f9058 100644 --- a/Wabbajack.Lib/AInstaller.cs +++ b/Wabbajack.Lib/AInstaller.cs @@ -78,7 +78,7 @@ namespace Wabbajack.Lib return e.FromJSON(); } using (var e = entry.Open()) - return e.FromCERAS(ref CerasConfig.Config); + return e.FromCERAS(CerasConfig.Config); } } diff --git a/Wabbajack.Lib/CerasConfig.cs b/Wabbajack.Lib/CerasConfig.cs index 290bc843..3997c8bf 100644 --- a/Wabbajack.Lib/CerasConfig.cs +++ b/Wabbajack.Lib/CerasConfig.cs @@ -8,25 +8,31 @@ namespace Wabbajack.Lib { public class CerasConfig { - public static SerializerConfig Config = new SerializerConfig - { - KnownTypes = - { - typeof(ModList), typeof(Game), typeof(Directive), typeof(IgnoredDirectly), - typeof(NoMatch), typeof(InlineFile), typeof(PropertyType), typeof(CleanedESM), - typeof(RemappedInlineFile), typeof(FromArchive), typeof(CreateBSA), typeof(PatchedFromArchive), - typeof(SourcePatch), typeof(MergedPatch), typeof(Archive), typeof(IndexedArchive), typeof(IndexedEntry), - typeof(IndexedArchiveEntry), typeof(BSAIndexedEntry), typeof(VirtualFile), - typeof(ArchiveStateObject), typeof(FileStateObject), typeof(IDownloader), - typeof(IUrlDownloader), typeof(AbstractDownloadState), typeof(ManualDownloader.State), - typeof(DropboxDownloader), typeof(GoogleDriveDownloader.State), typeof(HTTPDownloader.State), - typeof(MegaDownloader.State), typeof(ModDBDownloader.State), typeof(NexusDownloader.State), - typeof(BSAStateObject), typeof(BSAFileStateObject), typeof(BA2StateObject), typeof(BA2DX10EntryState), - typeof(BA2FileEntryState), typeof(MediaFireDownloader.State), typeof(ArchiveMeta), - typeof(PropertyFile), typeof(SteamMeta), typeof(SteamWorkshopDownloader), typeof(SteamWorkshopDownloader.State), - typeof(LoversLabDownloader.State), typeof(GameFileSourceDownloader.State) + public static readonly SerializerConfig Config; - } - }; + static CerasConfig() + { + Config = new SerializerConfig + { + KnownTypes = + { + typeof(ModList), typeof(Game), typeof(Directive), typeof(IgnoredDirectly), + typeof(NoMatch), typeof(InlineFile), typeof(PropertyType), typeof(CleanedESM), + typeof(RemappedInlineFile), typeof(FromArchive), typeof(CreateBSA), typeof(PatchedFromArchive), + typeof(SourcePatch), typeof(MergedPatch), typeof(Archive), typeof(IndexedArchive), typeof(IndexedEntry), + typeof(IndexedArchiveEntry), typeof(BSAIndexedEntry), typeof(VirtualFile), + typeof(ArchiveStateObject), typeof(FileStateObject), typeof(IDownloader), + typeof(IUrlDownloader), typeof(AbstractDownloadState), typeof(ManualDownloader.State), + typeof(DropboxDownloader), typeof(GoogleDriveDownloader.State), typeof(HTTPDownloader.State), + typeof(MegaDownloader.State), typeof(ModDBDownloader.State), typeof(NexusDownloader.State), + typeof(BSAStateObject), typeof(BSAFileStateObject), typeof(BA2StateObject), typeof(BA2DX10EntryState), + typeof(BA2FileEntryState), typeof(MediaFireDownloader.State), typeof(ArchiveMeta), + typeof(PropertyFile), typeof(SteamMeta), typeof(SteamWorkshopDownloader), typeof(SteamWorkshopDownloader.State), + typeof(LoversLabDownloader.State), typeof(GameFileSourceDownloader.State) + + }, + }; + Config.VersionTolerance.Mode = VersionToleranceMode.Standard; + } } } diff --git a/Wabbajack.Lib/Data.cs b/Wabbajack.Lib/Data.cs index e438eadc..df21b4ee 100644 --- a/Wabbajack.Lib/Data.cs +++ b/Wabbajack.Lib/Data.cs @@ -91,11 +91,6 @@ namespace Wabbajack.Lib /// public string Readme; - /// - /// Whether readme is a website - /// - public bool ReadmeIsWebsite; - /// /// Content Report in HTML form /// @@ -118,6 +113,10 @@ namespace Wabbajack.Lib .Take(Environment.ProcessorCount) .Sum(a => a.Size) * 2; + /// + /// Whether readme is a website + /// + public bool ReadmeIsWebsite; } public class Directive From caffb6e0321e6cd8d087ca185279ee14b01e2feb Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Fri, 20 Dec 2019 14:12:01 -0600 Subject: [PATCH 12/14] Boolean inverse bugfix --- Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs index 3e91c002..0f487134 100644 --- a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs +++ b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs @@ -61,7 +61,7 @@ namespace Wabbajack this.WhenAny(x => x.ImagePath.ErrorState).Select(err => err.Failed), this.WhenAny(x => x.ReadmeFilePath.ErrorState).Select(err => err.Failed), this.WhenAny(x => x.ReadmeIsWebsite), - resultSelector: (img, readme, isWebsite) => img || (readme && isWebsite)) + resultSelector: (img, readme, isWebsite) => img || (readme && !isWebsite)) .Publish() .RefCount(); From 7c88b4ea12bfe13ffc224ed8f3efe02e3351d719 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Fri, 20 Dec 2019 16:06:20 -0600 Subject: [PATCH 13/14] Blocks installations into folders /w .wabbajack file --- Wabbajack.CacheServer/ListValidationService.cs | 2 +- Wabbajack.Lib/MO2Installer.cs | 15 +++++++++++++++ .../View Models/Installers/MO2InstallerVM.cs | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Wabbajack.CacheServer/ListValidationService.cs b/Wabbajack.CacheServer/ListValidationService.cs index 7e10f6f8..7dd8690c 100644 --- a/Wabbajack.CacheServer/ListValidationService.cs +++ b/Wabbajack.CacheServer/ListValidationService.cs @@ -144,7 +144,7 @@ namespace Wabbajack.CacheServer { foreach (var list in modlists) { - var modlist_path = Path.Combine(Consts.ModListDownloadFolder, list.Links.MachineURL + ".wabbajack"); + var modlist_path = Path.Combine(Consts.ModListDownloadFolder, list.Links.MachineURL + ExtensionManager.Extension); if (list.NeedsDownload(modlist_path)) { diff --git a/Wabbajack.Lib/MO2Installer.cs b/Wabbajack.Lib/MO2Installer.cs index aa20008d..0f943b93 100644 --- a/Wabbajack.Lib/MO2Installer.cs +++ b/Wabbajack.Lib/MO2Installer.cs @@ -318,5 +318,20 @@ namespace Wabbajack.Lib File.WriteAllText(Path.Combine(OutputFolder, directive.To), data); } + + public static IErrorResponse CheckValidInstallPath(string path) + { + var ret = Utils.IsDirectoryPathValid(path); + if (!ret.Succeeded) return ret; + foreach (var file in Directory.EnumerateFiles(path, DirectoryEnumerationOptions.Recursive)) + { + if (!File.Exists(file)) continue; + if (System.IO.Path.GetExtension(file).Equals(ExtensionManager.Extension)) + { + return ErrorResponse.Fail($"Cannot install into a folder with a wabbajack modlist inside of it."); + } + } + return ErrorResponse.Success; + } } } diff --git a/Wabbajack/View Models/Installers/MO2InstallerVM.cs b/Wabbajack/View Models/Installers/MO2InstallerVM.cs index 4981517c..c0572a81 100644 --- a/Wabbajack/View Models/Installers/MO2InstallerVM.cs +++ b/Wabbajack/View Models/Installers/MO2InstallerVM.cs @@ -48,7 +48,7 @@ namespace Wabbajack PromptTitle = "Select Installation Directory", }; Location.AdditionalError = this.WhenAny(x => x.Location.TargetPath) - .Select(x => Utils.IsDirectoryPathValid(x)); + .Select(x => MO2Installer.CheckValidInstallPath(x)); DownloadLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.Off, From 124f9e1d88f4d640d3ae5080f1818f612f6a3c6f Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Fri, 20 Dec 2019 16:31:35 -0600 Subject: [PATCH 14/14] Blocks installations installed into non-empty folder w/o MO2 files --- Wabbajack.Lib/MO2Installer.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Wabbajack.Lib/MO2Installer.cs b/Wabbajack.Lib/MO2Installer.cs index 0f943b93..df88d02b 100644 --- a/Wabbajack.Lib/MO2Installer.cs +++ b/Wabbajack.Lib/MO2Installer.cs @@ -323,6 +323,10 @@ namespace Wabbajack.Lib { var ret = Utils.IsDirectoryPathValid(path); if (!ret.Succeeded) return ret; + + if (!Directory.Exists(path)) return ErrorResponse.Success; + + // Check folder does not have a wabbajack modlist foreach (var file in Directory.EnumerateFiles(path, DirectoryEnumerationOptions.Recursive)) { if (!File.Exists(file)) continue; @@ -331,6 +335,23 @@ namespace Wabbajack.Lib return ErrorResponse.Fail($"Cannot install into a folder with a wabbajack modlist inside of it."); } } + + // Check folder is either empty, or a likely valid previous install + if (!Directory.IsEmpty(path)) + { + // Some probably naive check, but should be a good starting point to improve later + if (!Directory.EnumerateFiles(path).Any(file => + { + var fileName = Path.GetFileName(file); + if (fileName.Equals("ModOrganizer.exe", StringComparison.OrdinalIgnoreCase)) return true; + if (fileName.Equals("ModOrganizer.ini", StringComparison.OrdinalIgnoreCase)) return true; + return false; + })) + { + return ErrorResponse.Fail($"Cannot install into a non-empty folder that does not look like a previous WJ installation."); + } + } + return ErrorResponse.Success; } }