From 475e44c895917e35a07c01bc46744a2d4ad55aa9 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Sun, 26 Dec 2021 22:13:28 -0700 Subject: [PATCH] Port ModListVM --- Wabbajack.App.Wpf/App.xaml | 2 +- Wabbajack.App.Wpf/App.xaml.cs | 27 ++-- Wabbajack.App.Wpf/Settings.cs | 8 +- Wabbajack.App.Wpf/Util/UIUtils.cs | 21 ++++ .../View Models/Installers/InstallerVM.cs | 1 + Wabbajack.App.Wpf/View Models/ModListVM.cs | 66 ++++++---- Wabbajack.App.Wpf/View Models/SlideShow.cs | 22 ++-- .../View Models/UserInterventionHandlers.cs | 119 ++---------------- Wabbajack.Common/AbsolutePathExtensions.cs | 7 ++ Wabbajack.Common/Ext.cs | 1 + Wabbajack.Common/StreamExtensions.cs | 7 ++ Wabbajack.DTOs/DownloadStates/IMetaState.cs | 2 + 12 files changed, 123 insertions(+), 160 deletions(-) diff --git a/Wabbajack.App.Wpf/App.xaml b/Wabbajack.App.Wpf/App.xaml index 70214666..fa7f54dc 100644 --- a/Wabbajack.App.Wpf/App.xaml +++ b/Wabbajack.App.Wpf/App.xaml @@ -4,7 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Wabbajack" ShutdownMode="OnExplicitShutdown" - StartupUri="Views\MainWindow.xaml"> + Startup="OnStartup"> diff --git a/Wabbajack.App.Wpf/App.xaml.cs b/Wabbajack.App.Wpf/App.xaml.cs index d1593ac8..9948f35f 100644 --- a/Wabbajack.App.Wpf/App.xaml.cs +++ b/Wabbajack.App.Wpf/App.xaml.cs @@ -1,26 +1,33 @@ using System; using System.Windows; +using Microsoft.Extensions.DependencyInjection; using Wabbajack.Common; using Wabbajack.Lib; +using Wabbajack.Services.OSIntegrated; namespace Wabbajack { /// /// Interaction logic for App.xaml /// - public partial class App : Application + public partial class App { + private readonly ServiceProvider _serviceProvider; public App() { - Consts.LogsFolder = LauncherUpdater.CommonFolder.Value.Combine("logs"); - Consts.LogsFolder.CreateDirectory(); - - LoggingSettings.LogToFile = true; - Utils.InitializeLogging().Wait(); - - CLIOld.ParseOptions(Environment.GetCommandLineArgs()); - if (CLIArguments.Help) - CLIOld.DisplayHelpText(); + var services = new ServiceCollection(); + ConfigureServices(services); + _serviceProvider = services.BuildServiceProvider(); + } + private void ConfigureServices(ServiceCollection services) + { + services.AddOSIntegrated(); + services.AddSingleton(); + } + private void OnStartup(object sender, StartupEventArgs e) + { + var mainWindow = _serviceProvider.GetService(); + mainWindow!.Show(); } } } diff --git a/Wabbajack.App.Wpf/Settings.cs b/Wabbajack.App.Wpf/Settings.cs index 52997931..649cade2 100644 --- a/Wabbajack.App.Wpf/Settings.cs +++ b/Wabbajack.App.Wpf/Settings.cs @@ -1,4 +1,10 @@ -using Wabbajack.Compiler; +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Wabbajack.Compiler; using Wabbajack.DTOs.JsonConverters; using Wabbajack.Lib; using Wabbajack.Paths; diff --git a/Wabbajack.App.Wpf/Util/UIUtils.cs b/Wabbajack.App.Wpf/Util/UIUtils.cs index 6d95c0b5..564a718a 100644 --- a/Wabbajack.App.Wpf/Util/UIUtils.cs +++ b/Wabbajack.App.Wpf/Util/UIUtils.cs @@ -3,6 +3,7 @@ using DynamicData.Binding; using Microsoft.WindowsAPICodePack.Dialogs; using ReactiveUI; using System; +using System.Diagnostics; using System.IO; using System.Net.Http; using System.Reactive.Linq; @@ -13,6 +14,8 @@ using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Media.Imaging; using Wabbajack.Common; +using Wabbajack.Lib.Extensions; +using Wabbajack.Paths; namespace Wabbajack { @@ -49,6 +52,24 @@ namespace Wabbajack return false; } } + + + public static void OpenWebsite(Uri url) + { + Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}") + { + CreateNoWindow = true, + }); + } + + public static void OpenFolder(AbsolutePath path) + { + Process.Start(new ProcessStartInfo(AbsolutePath.WindowsFolder.Combine("explorer.exe").ToString(), path.ToString()) + { + CreateNoWindow = true, + }); + } + public static AbsolutePath OpenFileDialog(string filter, string initialDirectory = null) { diff --git a/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs index c0993f71..fe290367 100644 --- a/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs @@ -27,6 +27,7 @@ using Wabbajack.Lib.Extensions; using Wabbajack.Lib.Interventions; using Wabbajack.Paths; using Wabbajack.RateLimiter; +using Wabbajack.View_Models; namespace Wabbajack { diff --git a/Wabbajack.App.Wpf/View Models/ModListVM.cs b/Wabbajack.App.Wpf/View Models/ModListVM.cs index 82c934a3..181447ce 100644 --- a/Wabbajack.App.Wpf/View Models/ModListVM.cs +++ b/Wabbajack.App.Wpf/View Models/ModListVM.cs @@ -4,25 +4,36 @@ using System.IO; using System.IO.Compression; using System.Reactive; using System.Reactive.Linq; +using System.Threading.Tasks; using System.Windows.Media.Imaging; +using Microsoft.Extensions.Logging; +using ReactiveUI.Fody.Helpers; using Wabbajack.Common; +using Wabbajack.DTOs; +using Wabbajack.DTOs.JsonConverters; +using Wabbajack.Installer; using Wabbajack.Lib; -using Wabbajack.Lib.ModListRegistry; +using Wabbajack.Paths; +using Wabbajack.Paths.IO; +using Consts = Wabbajack.Lib.Consts; namespace Wabbajack { public class ModListVM : ViewModel { + private readonly DTOSerializer _dtos; + private readonly ILogger _logger; public ModList SourceModList { get; private set; } public ModlistMetadata SourceModListMetadata { get; private set; } - public Exception Error { get; } + + [Reactive] + public Exception Error { get; set; } public AbsolutePath ModListPath { get; } public string Name => SourceModList?.Name; public string Readme => SourceModList?.Readme; public string Author => SourceModList?.Author; public string Description => SourceModList?.Description; public Uri Website => SourceModList?.Website; - public ModManager ModManager => SourceModList?.ModManager ?? ModManager.MO2; public Version Version => SourceModList?.Version; public Version WabbajackVersion => SourceModList?.WabbajackVersion; public bool IsNSFW => SourceModList?.IsNSFW ?? false; @@ -32,30 +43,37 @@ namespace Wabbajack // and the cached image will automatically be released when the last interested party is gone. public IObservable ImageObservable { get; } - public ModListVM(AbsolutePath modListPath) + public ModListVM(ILogger logger, AbsolutePath modListPath, DTOSerializer dtos) { + _dtos = dtos; + _logger = logger; + ModListPath = modListPath; - try + + Task.Run(async () => { - SourceModList = AInstaller.LoadFromFile(modListPath); - var metadataPath = modListPath.WithExtension(Consts.ModlistMetadataExtension); - if (metadataPath.Exists) + try { - try + SourceModList = await StandardInstaller.LoadFromFile(_dtos, modListPath); + var metadataPath = modListPath.WithExtension(Ext.ModlistMetadataExtension); + if (metadataPath.FileExists()) { - SourceModListMetadata = metadataPath.FromJson(); - } - catch (Exception) - { - SourceModListMetadata = null; + try + { + SourceModListMetadata = await metadataPath.FromJson(); + } + catch (Exception) + { + SourceModListMetadata = null; + } } } - } - catch (Exception ex) - { - Error = ex; - Utils.Error(ex, "Exception while loading the modlist!"); - } + catch (Exception ex) + { + Error = ex; + _logger.LogError(ex, "Exception while loading the modlist!"); + } + }); ImageObservable = Observable.Return(Unit.Default) // Download and retrieve bytes on background thread @@ -64,7 +82,7 @@ namespace Wabbajack { try { - await using var fs = await ModListPath.OpenShared(); + await using var fs = ModListPath.Open(FileMode.Open, FileAccess.Read, FileShare.Read); using var ar = new ZipArchive(fs, ZipArchiveMode.Read); var ms = new MemoryStream(); var entry = ar.GetEntry("modlist-image.png"); @@ -75,7 +93,7 @@ namespace Wabbajack } catch (Exception ex) { - Utils.Error(ex, $"Exception while caching Mod List image {Name}"); + _logger.LogError(ex, "Exception while caching Mod List image {Name}", Name); return default(MemoryStream); } }) @@ -90,7 +108,7 @@ namespace Wabbajack } catch (Exception ex) { - Utils.Error(ex, $"Exception while caching Mod List image {Name}"); + _logger.LogError(ex, "Exception while caching Mod List image {Name}", Name); return default(BitmapImage); } }) @@ -103,7 +121,7 @@ namespace Wabbajack public void OpenReadme() { if (string.IsNullOrEmpty(Readme)) return; - Utils.OpenWebsite(new Uri(Readme)); + UIUtils.OpenWebsite(new Uri(Readme)); } public override void Dispose() diff --git a/Wabbajack.App.Wpf/View Models/SlideShow.cs b/Wabbajack.App.Wpf/View Models/SlideShow.cs index 9926e88b..48d8565e 100644 --- a/Wabbajack.App.Wpf/View Models/SlideShow.cs +++ b/Wabbajack.App.Wpf/View Models/SlideShow.cs @@ -1,21 +1,15 @@ -using DynamicData; +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Windows.Media.Imaging; +using DynamicData; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using System; -using System.Diagnostics; -using System.Linq; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Text.RegularExpressions; -using System.Windows.Media.Imaging; -using Wabbajack.Common; using Wabbajack.DTOs.DownloadStates; using Wabbajack.Lib; -using Wabbajack.Lib.Downloaders; using Wabbajack.Lib.Extensions; -namespace Wabbajack +namespace Wabbajack.View_Models { public class SlideShow : ViewModel { @@ -86,7 +80,7 @@ namespace Wabbajack if (modList?.SourceModList?.Archives == null) { return Observable.Empty() - .ToObservableChangeSet(x => x.URL); + .ToObservableChangeSet(x => x.LinkUrl); } return modList.SourceModList.Archives .Select(m => m.State) @@ -128,7 +122,7 @@ namespace Wabbajack VisitURLCommand = ReactiveCommand.Create( execute: () => { - Utils.OpenWebsite(TargetMod.State.URL); + UIUtils.OpenWebsite(TargetMod.State.URL); return Unit.Default; }, canExecute: this.WhenAny(x => x.TargetMod.State.URL) diff --git a/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs b/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs index 53b20aa8..1699569a 100644 --- a/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs +++ b/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs @@ -1,23 +1,12 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Web; using System.Windows; -using System.Windows.Threading; -using CefSharp; +using Microsoft.Extensions.Logging; using ReactiveUI; using Wabbajack.Common; using Wabbajack.Lib; -using Wabbajack.Lib.Downloaders; using Wabbajack.Lib.Interventions; -using Wabbajack.Lib.LibCefHelpers; -using Wabbajack.Lib.NexusApi; -using Wabbajack.Lib.WebAutomation; -using WebSocketSharp; namespace Wabbajack { @@ -25,9 +14,11 @@ namespace Wabbajack { public MainWindowVM MainWindow { get; } private AsyncLock _browserLock = new(); + private readonly ILogger _logger; - public UserInterventionHandlers(MainWindowVM mvm) + public UserInterventionHandlers(ILogger logger, MainWindowVM mvm) { + _logger = logger; MainWindow = mvm; } @@ -36,7 +27,7 @@ namespace Wabbajack var wait = await _browserLock.WaitAsync(); var cancel = new CancellationTokenSource(); var oldPane = MainWindow.ActivePane; - using var vm = await WebBrowserVM.GetNew(); + using var vm = await WebBrowserVM.GetNew(_logger); MainWindow.NavigateTo(vm); vm.BackCommand = ReactiveCommand.Create(() => { @@ -55,7 +46,7 @@ namespace Wabbajack } catch (Exception ex) { - Utils.Error(ex); + _logger.LogError(ex, "During Web browser job"); intervention.Cancel(); } finally @@ -70,6 +61,7 @@ namespace Wabbajack { switch (msg) { + /* case RequestNexusAuthorization c: await WrapBrowserJob(c, async (vm, cancel) => { @@ -100,6 +92,7 @@ namespace Wabbajack break; + */ case CriticalFailureIntervention c: MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK, MessageBoxImage.Error); @@ -112,100 +105,6 @@ namespace Wabbajack throw new NotImplementedException($"No handler for {msg}"); } } - - private async Task OAuthLogin(RequestOAuthLogin oa, WebBrowserVM vm, CancellationTokenSource cancel) - { - await vm.Driver.WaitForInitialized(); - vm.Instructions = $"Please log in and allow Wabbajack to access your {oa.SiteName} account"; - - var wrapper = new CefSharpWrapper(vm.Browser); - var scopes = string.Join(" ", oa.Scopes); - var state = Guid.NewGuid().ToString(); - - - var oldHandler = Helpers.SchemeHandler; - Helpers.SchemeHandler = (browser, frame, _, request) => - { - var req = new Uri(request.Url); - Utils.LogStraightToFile($"Got Scheme callback {req}"); - var parsed = HttpUtility.ParseQueryString(req.Query); - if (parsed.Contains("state")) - { - if (parsed.Get("state") != state) - { - Utils.Log("Bad OAuth state, state, this shouldn't happen"); - oa.Cancel(); - return new ResourceHandler(); - } - } - if (parsed.Contains("code")) - { - Helpers.SchemeHandler = oldHandler; - oa.Resume(parsed.Get("code")); - } - else - { - oa.Cancel(); - } - return new ResourceHandler(); - }; - - await wrapper.NavigateTo(new Uri(oa.AuthorizationEndpoint + $"?response_type=code&client_id={oa.ClientID}&state={state}&scope={scopes}")); - - while (!oa.Task.IsCanceled && !oa.Task.IsCompleted && !cancel.IsCancellationRequested) - await Task.Delay(250); - } - - private async Task HandleManualDownload(WebBrowserVM vm, CancellationTokenSource cancel, ManuallyDownloadFile manuallyDownloadFile) - { - var browser = new CefSharpWrapper(vm.Browser); - vm.Instructions = $"Please locate and download {manuallyDownloadFile.State.Url}"; - - var result = new TaskCompletionSource(); - - browser.DownloadHandler = uri => - { - //var client = Helpers.GetClient(browser.GetCookies("").Result, browser.Location); - result.SetResult(uri); - }; - - await vm.Driver.WaitForInitialized(); - - await browser.NavigateTo(new Uri(manuallyDownloadFile.State.Url)); - - while (!cancel.IsCancellationRequested) - { - if (result.Task.IsCompleted) - { - var cookies = await Helpers.GetCookies(); - var referer = browser.Location; - var client = Helpers.GetClient(cookies, referer); - manuallyDownloadFile.Resume(result.Task.Result, client); - break; - } - await Task.Delay(100); - } - - } - - private async Task HandleManualNexusDownload(WebBrowserVM vm, CancellationTokenSource cancel, ManuallyDownloadNexusFile manuallyDownloadNexusFile) - { - var state = manuallyDownloadNexusFile.State; - var game = state.Game.MetaData(); - await vm.Driver.WaitForInitialized(); - IWebDriver browser = new CefSharpWrapper(vm.Browser); - vm.Instructions = $"Click the download button to continue (get a NexusMods.com Premium account to automate this)"; - browser.DownloadHandler = uri => - { - manuallyDownloadNexusFile.Resume(uri); - browser.DownloadHandler = null; - }; - var url = new Uri(@$"https://www.nexusmods.com/{game.NexusName}/mods/{state.ModID}?tab=files&file_id={state.FileID}"); - await browser.NavigateTo(url); - - while (!cancel.IsCancellationRequested && !manuallyDownloadNexusFile.Task.IsCompleted) { - await Task.Delay(250); - } - } + } } diff --git a/Wabbajack.Common/AbsolutePathExtensions.cs b/Wabbajack.Common/AbsolutePathExtensions.cs index a73ee6fc..c2b4ff36 100644 --- a/Wabbajack.Common/AbsolutePathExtensions.cs +++ b/Wabbajack.Common/AbsolutePathExtensions.cs @@ -1,6 +1,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Wabbajack.DTOs.JsonConverters; using Wabbajack.Hashing.xxHash64; using Wabbajack.Paths; using Wabbajack.Paths.IO; @@ -14,4 +15,10 @@ public static class AbsolutePathExtensions await using var fs = path.Open(FileMode.Open); return await fs.HashingCopy(Stream.Null, token ?? CancellationToken.None); } + + public static async Task FromJson(this AbsolutePath path, DTOSerializer? dtos = null) + { + await using var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + return await fs.FromJson(dtos); + } } \ No newline at end of file diff --git a/Wabbajack.Common/Ext.cs b/Wabbajack.Common/Ext.cs index 18b8d473..fface41c 100644 --- a/Wabbajack.Common/Ext.cs +++ b/Wabbajack.Common/Ext.cs @@ -23,4 +23,5 @@ public static class Ext public static Extension CompilerSettings = new(".compiler_settings"); public static Extension MO2CompilerSettings = new(".mo2_compiler_settings"); public static Extension Temp = new(".temp"); + public static Extension ModlistMetadataExtension = new(".modlist_metadata"); } \ No newline at end of file diff --git a/Wabbajack.Common/StreamExtensions.cs b/Wabbajack.Common/StreamExtensions.cs index 209fbf28..65beb9cf 100644 --- a/Wabbajack.Common/StreamExtensions.cs +++ b/Wabbajack.Common/StreamExtensions.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Wabbajack.DTOs.JsonConverters; namespace Wabbajack.Common; @@ -57,6 +59,11 @@ public static class StreamExtensions return sr.ReadToEnd(); } + public static async Task FromJson(this Stream stream, DTOSerializer? dtos = null) + { + return (await JsonSerializer.DeserializeAsync(stream, dtos?.Options))!; + } + public static async IAsyncEnumerable ReadLinesAsync(this Stream stream) { using var sr = new StreamReader(stream); diff --git a/Wabbajack.DTOs/DownloadStates/IMetaState.cs b/Wabbajack.DTOs/DownloadStates/IMetaState.cs index 1efafc61..85167116 100644 --- a/Wabbajack.DTOs/DownloadStates/IMetaState.cs +++ b/Wabbajack.DTOs/DownloadStates/IMetaState.cs @@ -10,4 +10,6 @@ public interface IMetaState Uri? ImageURL { get; set; } bool IsNSFW { get; set; } string? Description { get; set; } + + Uri? LinkUrl { get; } } \ No newline at end of file