From 9f87d9191841a39967b929fdb87653ade61515c5 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Mon, 3 Jan 2022 15:46:28 -0700 Subject: [PATCH] Woot, we can log into the Nexus through 3.0 code --- Wabbajack.App.Wpf/App.xaml.cs | 8 ++ Wabbajack.App.Wpf/Consts.cs | 2 + .../Interventions/AUserIntervention.cs | 8 ++ .../Interventions/IUserIntervention.cs | 23 ---- .../Messages/NavigateToGlobal.cs | 3 +- Wabbajack.App.Wpf/Messages/NexusLogin.cs | 24 +++- Wabbajack.App.Wpf/Models/CefService.cs | 70 +++++++++++ Wabbajack.App.Wpf/Models/LoggerProvider.cs | 2 +- .../UserIntervention/NexusLoginHandler.cs | 111 ++++++++++++++++++ .../WebUserInterventionBase.cs | 42 +++++++ .../View Models/Compilers/CompilerVM.cs | 1 + .../View Models/Installers/ISubInstallerVM.cs | 11 +- .../View Models/Installers/MO2InstallerVM.cs | 3 +- Wabbajack.App.Wpf/View Models/MainWindowVM.cs | 42 ++++++- .../ConfirmUpdateOfExistingInstallVM.cs | 7 ++ .../View Models/UserInterventionHandlers.cs | 6 +- Wabbajack.App.Wpf/View Models/WebBrowserVM.cs | 23 ++-- .../WebAutomation/CefSharpWrapper.cs | 1 + Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs | 3 +- Wabbajack.CLI/Verbs/ForceHeal.cs | 5 + .../Interventions/IUserIntervention.cs | 10 ++ .../GithubAuthTokenProvider.cs | 1 + 22 files changed, 355 insertions(+), 51 deletions(-) delete mode 100644 Wabbajack.App.Wpf/Interventions/IUserIntervention.cs create mode 100644 Wabbajack.App.Wpf/Models/CefService.cs create mode 100644 Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs create mode 100644 Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs diff --git a/Wabbajack.App.Wpf/App.xaml.cs b/Wabbajack.App.Wpf/App.xaml.cs index 5c5a60e5..136c8170 100644 --- a/Wabbajack.App.Wpf/App.xaml.cs +++ b/Wabbajack.App.Wpf/App.xaml.cs @@ -15,6 +15,7 @@ using Wabbajack.DTOs; using Wabbajack.LoginManagers; using Wabbajack.Models; using Wabbajack.Services.OSIntegrated; +using Wabbajack.UserIntervention; using Wabbajack.Util; namespace Wabbajack @@ -47,6 +48,9 @@ namespace Wabbajack RxApp.MainThreadScheduler = new DispatcherScheduler(Dispatcher.CurrentDispatcher); services.AddOSIntegrated(); + + services.AddSingleton(); + services.AddTransient(); services.AddTransient(); services.AddSingleton(); @@ -60,6 +64,10 @@ namespace Wabbajack services.AddTransient(); services.AddTransient(); services.AddTransient(); + + services.AddTransient(); + + services.AddTransient(); // Login Managers services.AddAllSingleton(); diff --git a/Wabbajack.App.Wpf/Consts.cs b/Wabbajack.App.Wpf/Consts.cs index 63c0ee42..f93ff405 100644 --- a/Wabbajack.App.Wpf/Consts.cs +++ b/Wabbajack.App.Wpf/Consts.cs @@ -1,5 +1,6 @@ using System; using Wabbajack.Paths; +using Wabbajack.Paths.IO; namespace Wabbajack; @@ -9,6 +10,7 @@ public static class Consts public static Uri WabbajackBuildServerUri => new("https://build.wabbajack.org"); public static Version CurrentMinimumWabbajackVersion { get; set; } = Version.Parse("2.3.0.0"); public static bool UseNetworkWorkaroundMode { get; set; } = false; + public static AbsolutePath CefCacheLocation { get; } = KnownFolders.WabbajackAppLocal.Combine("Cef"); public static byte SettingsVersion = 0; diff --git a/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs b/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs index c07d59fd..f8fd944e 100644 --- a/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs +++ b/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows.Input; using ReactiveUI; using Wabbajack.Common; +using Wabbajack.DTOs.Interventions; using Wabbajack.Interventions; namespace Wabbajack @@ -18,6 +20,12 @@ namespace Wabbajack private bool _handled; public bool Handled { get => _handled; set => this.RaiseAndSetIfChanged(ref _handled, value); } + public CancellationToken Token { get; } + public void SetException(Exception exception) + { + throw new NotImplementedException(); + } + public abstract void Cancel(); public ICommand CancelCommand { get; } diff --git a/Wabbajack.App.Wpf/Interventions/IUserIntervention.cs b/Wabbajack.App.Wpf/Interventions/IUserIntervention.cs deleted file mode 100644 index 3d1f1b08..00000000 --- a/Wabbajack.App.Wpf/Interventions/IUserIntervention.cs +++ /dev/null @@ -1,23 +0,0 @@ -using ReactiveUI; - -namespace Wabbajack.Interventions -{ - /// - /// Defines a message that requires user interaction. The user must perform some action - /// or make a choice. - /// - public interface IUserIntervention : IReactiveObject - { - /// - /// The user didn't make a choice, so this action should be aborted - /// - void Cancel(); - - /// - /// Whether the interaction has been handled and no longer needs attention - /// Note: This needs to be Reactive so that users can monitor its status - /// - bool Handled { get; } - - } -} diff --git a/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs b/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs index 55263b7b..ca0bafe6 100644 --- a/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs +++ b/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs @@ -11,7 +11,8 @@ public class NavigateToGlobal Installer, Settings, Compiler, - ModListContents + ModListContents, + WebBrowser } public ScreenType Screen { get; } diff --git a/Wabbajack.App.Wpf/Messages/NexusLogin.cs b/Wabbajack.App.Wpf/Messages/NexusLogin.cs index e459e2f6..69c37a56 100644 --- a/Wabbajack.App.Wpf/Messages/NexusLogin.cs +++ b/Wabbajack.App.Wpf/Messages/NexusLogin.cs @@ -1,16 +1,28 @@ +using System; +using System.Threading; using System.Threading.Tasks; using ReactiveUI; +using Wabbajack.DTOs.Interventions; using Wabbajack.Networking.Http.Interfaces; namespace Wabbajack.Messages; -public class NexusLogin +public class NexusLogin : IUserIntervention { - private TaskCompletionSource CompletionSource { get; } + private readonly CancellationTokenSource _source; + public TaskCompletionSource CompletionSource { get; } + public CancellationToken Token => _source.Token; + public void SetException(Exception exception) + { + CompletionSource.SetException(exception); + _source.Cancel(); + } public NexusLogin() { CompletionSource = new TaskCompletionSource(); + _source = new CancellationTokenSource(); + } public static Task Send() @@ -19,4 +31,12 @@ public class NexusLogin MessageBus.Current.SendMessage(msg); return msg.CompletionSource.Task; } + + public void Cancel() + { + _source.Cancel(); + CompletionSource.TrySetCanceled(); + } + + public bool Handled => CompletionSource.Task.IsCompleted; } \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Models/CefService.cs b/Wabbajack.App.Wpf/Models/CefService.cs new file mode 100644 index 00000000..f8a91f52 --- /dev/null +++ b/Wabbajack.App.Wpf/Models/CefService.cs @@ -0,0 +1,70 @@ +using System; +using System.Threading.Tasks; +using CefSharp; +using CefSharp.Wpf; +using Microsoft.Extensions.Logging; + +namespace Wabbajack.Models; + +public class CefService +{ + private readonly ILogger _logger; + private bool Inited { get; set; } = false; + + public Func? SchemeHandler { get; set; } + + public CefService(ILogger logger) + { + _logger = logger; + Inited = false; + Init(); + } + + public IWebBrowser CreateBrowser() + { + return new ChromiumWebBrowser(); + } + private void Init() + { + if (Inited || Cef.IsInitialized) return; + Inited = true; + var settings = new CefSettings + { + CachePath = Consts.CefCacheLocation.ToString(), + JavascriptFlags = "--noexpose_wasm" + }; + settings.RegisterScheme(new CefCustomScheme() + { + SchemeName = "wabbajack", + SchemeHandlerFactory = new SchemeHandlerFactor(_logger, this) + }); + + _logger.LogInformation("Initializing Cef"); + if (!Cef.Initialize(settings)) + { + _logger.LogError("Cannot initialize CEF"); + } + } + + private class SchemeHandlerFactor : ISchemeHandlerFactory + { + private readonly ILogger _logger; + private readonly CefService _service; + + internal SchemeHandlerFactor(ILogger logger, CefService service) + { + _logger = logger; + _service = service; + } + + public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) + { + _logger.LogInformation("Scheme handler Got: {Scheme} : {Url}", schemeName, request.Url); + if (_service.SchemeHandler != null && schemeName == "wabbajack") + { + return _service.SchemeHandler!(browser, frame, schemeName, request); + } + return new ResourceHandler(); + } + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Models/LoggerProvider.cs b/Wabbajack.App.Wpf/Models/LoggerProvider.cs index 6bec1224..d2fc7a4a 100644 --- a/Wabbajack.App.Wpf/Models/LoggerProvider.cs +++ b/Wabbajack.App.Wpf/Models/LoggerProvider.cs @@ -103,7 +103,7 @@ public class LoggerProvider : ILoggerProvider public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { - Debug.WriteLine(formatter(state, exception)); + Debug.WriteLine($"{logLevel} - {formatter(state, exception)}"); _provider._messages.OnNext(new LogMessage(DateTime.UtcNow, _provider.NextMessageId(), logLevel, eventId, state, exception, formatter)); } diff --git a/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs new file mode 100644 index 00000000..342732bb --- /dev/null +++ b/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs @@ -0,0 +1,111 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using ReactiveUI; +using Wabbajack.DTOs.Logins; +using Wabbajack.LibCefHelpers; +using Wabbajack.Messages; +using Wabbajack.Networking.Http.Interfaces; +using Wabbajack.Services.OSIntegrated.TokenProviders; + +namespace Wabbajack.UserIntervention; + +public class NexusLoginHandler : WebUserInterventionBase +{ + private readonly ITokenProvider _provider; + + public NexusLoginHandler(ILogger logger, WebBrowserVM browserVM, ITokenProvider provider) : base(logger, browserVM) + { + _provider = provider; + } + public async Task Begin() + { + try + { + Messages.NavigateTo.Send(Browser); + UpdateStatus("Please log into the Nexus"); + await Driver.WaitForInitialized(); + + await NavigateTo(new Uri("https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com")); + + Helpers.Cookie[] cookies = {}; + while (true) + { + cookies = await Driver.GetCookies("nexusmods.com"); + if (cookies.Any(c => c.Name == "member_id")) + break; + Message.Token.ThrowIfCancellationRequested(); + await Task.Delay(500, Message.Token); + } + + + await NavigateTo(new Uri("https://www.nexusmods.com/users/myaccount?tab=api")); + + UpdateStatus("Looking for API Key"); + + var key = ""; + + while (true) + { + try + { + key = await Driver.EvaluateJavaScript( + "document.querySelector(\"input[value=wabbajack]\").parentElement.parentElement.querySelector(\"textarea.application-key\").innerHTML"); + } + catch (Exception) + { + // ignored + } + + if (!string.IsNullOrEmpty(key)) + { + break; + } + + try + { + await Driver.EvaluateJavaScript( + "var found = document.querySelector(\"input[value=wabbajack]\").parentElement.parentElement.querySelector(\"form button[type=submit]\");" + + "found.onclick= function() {return true;};" + + "found.class = \" \"; " + + "found.click();" + + "found.remove(); found = undefined;" + ); + UpdateStatus("Generating API Key, Please Wait..."); + + + } + catch (Exception) + { + // ignored + } + + Message.Token.ThrowIfCancellationRequested(); + await Task.Delay(500, Message.Token); + } + + + await _provider.SetToken(new NexusApiState() + { + ApiKey = key, + Cookies = cookies.Select(c => new Cookie() + { + Domain = c.Domain, + Name = c.Name, + Path = c.Path, + Value = c.Value + }).ToArray() + }); + + ((NexusLogin)Message).CompletionSource.SetResult(); + Messages.NavigateTo.Send(PrevPane); + } + catch (Exception ex) + { + Logger.LogError(ex, "While logging into Nexus Mods"); + Message.SetException(ex); + Messages.NavigateTo.Send(PrevPane); + } + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs b/Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs new file mode 100644 index 00000000..4c6a73f8 --- /dev/null +++ b/Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Wabbajack.DTOs.Interventions; +using Wabbajack.Interventions; +using Wabbajack.WebAutomation; + +namespace Wabbajack.UserIntervention; + +public class WebUserInterventionBase +{ + protected readonly WebBrowserVM Browser; + protected readonly ILogger Logger; + protected IUserIntervention Message; + protected ViewModel PrevPane; + protected IWebDriver Driver; + + public WebUserInterventionBase(ILogger logger, WebBrowserVM browser) + { + Logger = logger; + Browser = browser; + Driver = new CefSharpWrapper(logger, browser.Browser); + } + + public void Configure(ViewModel prevPane, IUserIntervention message) + { + Message = message; + PrevPane = prevPane; + } + + protected void UpdateStatus(string status) + { + Browser.Instructions = status; + } + + protected async Task NavigateTo(Uri uri) + { + await Driver.NavigateTo(uri, Message.Token); + } + +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs b/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs index d5a69548..90b3f013 100644 --- a/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs @@ -12,6 +12,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using DynamicData.Binding; using ReactiveUI.Fody.Helpers; +using Wabbajack.DTOs.Interventions; namespace Wabbajack { diff --git a/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs index 30eb8e5b..8849400a 100644 --- a/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs @@ -1,13 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using ReactiveUI; -using Wabbajack.Common; +using System.Threading.Tasks; using Wabbajack.Installer; -using Wabbajack; -using Wabbajack.Interventions; +using Wabbajack.DTOs.Interventions; namespace Wabbajack { diff --git a/Wabbajack.App.Wpf/View Models/Installers/MO2InstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/MO2InstallerVM.cs index 9fa3e95f..abc93600 100644 --- a/Wabbajack.App.Wpf/View Models/Installers/MO2InstallerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Installers/MO2InstallerVM.cs @@ -13,6 +13,7 @@ using Wabbajack.Common; using Wabbajack.Installer; using Wabbajack; using Wabbajack.DTOs; +using Wabbajack.DTOs.Interventions; using Wabbajack.Interventions; using Wabbajack.Util; @@ -194,7 +195,7 @@ namespace Wabbajack */ return true; } - + public IUserIntervention InterventionConverter(IUserIntervention intervention) { switch (intervention) diff --git a/Wabbajack.App.Wpf/View Models/MainWindowVM.cs b/Wabbajack.App.Wpf/View Models/MainWindowVM.cs index b48b3fa3..421ed5f8 100644 --- a/Wabbajack.App.Wpf/View Models/MainWindowVM.cs +++ b/Wabbajack.App.Wpf/View Models/MainWindowVM.cs @@ -2,6 +2,7 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; @@ -16,10 +17,12 @@ using Wabbajack.Common; using Wabbajack.Downloaders.GameFile; using Wabbajack; using Wabbajack.Interventions; +using Wabbajack.LoginManagers; using Wabbajack.Messages; using Wabbajack.Models; using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Paths; +using Wabbajack.UserIntervention; using Wabbajack.View_Models; namespace Wabbajack @@ -44,12 +47,16 @@ namespace Wabbajack public readonly Lazy SettingsPane; public readonly ModListGalleryVM Gallery; public readonly ModeSelectionVM ModeSelectionVM; + public readonly WebBrowserVM WebBrowserVM; public readonly Lazy ModListContentsVM; public readonly UserInterventionHandlers UserInterventionHandlers; private readonly Client _wjClient; private readonly ILogger _logger; private readonly ResourceMonitor _resourceMonitor; + private List PreviousPanes = new(); + private readonly IServiceProvider _serviceProvider; + public ICommand CopyVersionCommand { get; } public ICommand ShowLoginManagerVM { get; } public ICommand OpenSettingsCommand { get; } @@ -64,11 +71,12 @@ namespace Wabbajack public MainWindowVM(ILogger logger, MainSettings settings, Client wjClient, IServiceProvider serviceProvider, ModeSelectionVM modeSelectionVM, ModListGalleryVM modListGalleryVM, ResourceMonitor resourceMonitor, - InstallerVM installer) + InstallerVM installer, WebBrowserVM webBrowserVM) { _logger = logger; _wjClient = wjClient; _resourceMonitor = resourceMonitor; + _serviceProvider = serviceProvider; ConverterRegistration.Register(); Settings = settings; Installer = installer; @@ -76,12 +84,25 @@ namespace Wabbajack SettingsPane = new Lazy(() => new SettingsVM(serviceProvider.GetRequiredService>(), this, serviceProvider)); Gallery = modListGalleryVM; ModeSelectionVM = modeSelectionVM; + WebBrowserVM = webBrowserVM; ModListContentsVM = new Lazy(() => new ModListContentsVM(serviceProvider.GetRequiredService>(), this)); UserInterventionHandlers = new UserInterventionHandlers(serviceProvider.GetRequiredService>(), this); MessageBus.Current.Listen() .Subscribe(m => HandleNavigateTo(m.Screen)) .DisposeWith(CompositeDisposable); + + MessageBus.Current.Listen() + .Subscribe(m => HandleNavigateTo(m.ViewModel)) + .DisposeWith(CompositeDisposable); + + MessageBus.Current.Listen() + .Subscribe(HandleNavigateBack) + .DisposeWith(CompositeDisposable); + + MessageBus.Current.Listen() + .Subscribe(m => HandleNexusLogin(m)) + .DisposeWith(CompositeDisposable); _resourceMonitor.Updates .Select(r => string.Join(", ", r.Where(r => r.Throughput > 0) @@ -169,6 +190,24 @@ namespace Wabbajack execute: () => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Settings)); } + private void HandleNavigateTo(ViewModel objViewModel) + { + ActivePane = objViewModel; + } + + private void HandleNexusLogin(NexusLogin nexusLogin) + { + var handler = _serviceProvider.GetRequiredService(); + handler.Configure(ActivePane, nexusLogin); + handler.Begin().FireAndForget(); + } + + private void HandleNavigateBack(NavigateBack navigateBack) + { + ActivePane = PreviousPanes.Last(); + PreviousPanes.RemoveAt(PreviousPanes.Count - 1); + } + private void HandleNavigateTo(NavigateToGlobal.ScreenType s) { ActivePane = s switch @@ -181,6 +220,7 @@ namespace Wabbajack }; } + private static bool IsStartingFromModlist(out AbsolutePath modlistPath) { /* TODO diff --git a/Wabbajack.App.Wpf/View Models/UserIntervention/ConfirmUpdateOfExistingInstallVM.cs b/Wabbajack.App.Wpf/View Models/UserIntervention/ConfirmUpdateOfExistingInstallVM.cs index cc46c6f1..ece18fe0 100644 --- a/Wabbajack.App.Wpf/View Models/UserIntervention/ConfirmUpdateOfExistingInstallVM.cs +++ b/Wabbajack.App.Wpf/View Models/UserIntervention/ConfirmUpdateOfExistingInstallVM.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Wabbajack.Common; using Wabbajack; +using Wabbajack.DTOs.Interventions; using Wabbajack.Interventions; namespace Wabbajack @@ -16,6 +18,11 @@ namespace Wabbajack public MO2InstallerVM Installer { get; } public bool Handled => ((IUserIntervention)Source).Handled; + public CancellationToken Token { get; } + public void SetException(Exception exception) + { + throw new NotImplementedException(); + } public int CpuID => 0; diff --git a/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs b/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs index 5bf2e912..19a49a1a 100644 --- a/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs +++ b/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using ReactiveUI; using Wabbajack.Common; using Wabbajack; +using Wabbajack.DTOs.Interventions; using Wabbajack.Interventions; using Wabbajack.Messages; @@ -23,12 +24,13 @@ namespace Wabbajack MainWindow = mvm; } - private async Task WrapBrowserJob(IUserIntervention intervention, Func toDo) + private async Task WrapBrowserJob(IUserIntervention intervention, WebBrowserVM vm, Func toDo) { var wait = await _browserLock.WaitAsync(); var cancel = new CancellationTokenSource(); var oldPane = MainWindow.ActivePane; - using var vm = await WebBrowserVM.GetNew(_logger); + + // TODO: FIX using var vm = await WebBrowserVM.GetNew(_logger); NavigateTo.Send(vm); vm.BackCommand = ReactiveCommand.Create(() => { diff --git a/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs b/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs index 1fe8bfe5..2a1d371e 100644 --- a/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs +++ b/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs @@ -13,19 +13,22 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack; using Wabbajack.LibCefHelpers; +using Wabbajack.Messages; +using Wabbajack.Models; using Wabbajack.WebAutomation; namespace Wabbajack { public class WebBrowserVM : ViewModel, IBackNavigatingVM, IDisposable { - private readonly ILogger _logger; + private readonly ILogger _logger; + private readonly CefService _cefService; [Reactive] public string Instructions { get; set; } - public IWebBrowser Browser { get; } = new ChromiumWebBrowser(); - public CefSharpWrapper Driver => new(_logger, Browser); + public IWebBrowser Browser { get; } + public CefSharpWrapper Driver { get; set; } [Reactive] public ViewModel NavigateBackTarget { get; set; } @@ -36,17 +39,17 @@ namespace Wabbajack public Subject IsBackEnabledSubject { get; } = new Subject(); public IObservable IsBackEnabled { get; } - private WebBrowserVM(ILogger logger, string url = "http://www.wabbajack.org") + public WebBrowserVM(ILogger logger, CefService cefService) { + // CefService is required so that Cef is initalized _logger = logger; - IsBackEnabled = IsBackEnabledSubject.StartWith(true); + _cefService = cefService; Instructions = "Wabbajack Web Browser"; - } + + BackCommand = ReactiveCommand.Create(NavigateBack.Send); + Browser = cefService.CreateBrowser(); + Driver = new CefSharpWrapper(_logger, Browser); - public static async Task GetNew(ILogger logger, string url = "http://www.wabbajack.org") - { - // Make sure libraries are extracted first - return new WebBrowserVM(logger, url); } public override void Dispose() diff --git a/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs b/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs index 21d42ffc..cff52bc4 100644 --- a/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs +++ b/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs @@ -35,6 +35,7 @@ namespace Wabbajack.WebAutomation _browser.LoadingStateChanged -= handler; tcs.SetResult(true); }; + _browser.LoadingStateChanged += handler; _browser.Load(uri.ToString()); token?.Register(() => tcs.TrySetCanceled()); diff --git a/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs b/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs index df106e08..a7c2cae6 100644 --- a/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs +++ b/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs @@ -14,6 +14,7 @@ namespace Wabbajack.WebAutomation Task NavigateTo(Uri uri, CancellationToken? token = null); Task EvaluateJavaScript(string text); Task GetCookies(string domainPrefix); - public Action? DownloadHandler { get; set; } + public Action? DownloadHandler { get; set; } + public Task WaitForInitialized(); } } diff --git a/Wabbajack.CLI/Verbs/ForceHeal.cs b/Wabbajack.CLI/Verbs/ForceHeal.cs index c3fddee9..86af7055 100644 --- a/Wabbajack.CLI/Verbs/ForceHeal.cs +++ b/Wabbajack.CLI/Verbs/ForceHeal.cs @@ -102,6 +102,11 @@ public class ForceHeal : IVerb var ini = meta.LoadIniFile(); var state = await _downloadDispatcher.ResolveArchive(ini["General"].ToDictionary(d => d.KeyName, d => d.Value)); + if (state == null) + { + _logger.LogError("Cannot resolve state from meta for {File}", file); + throw new Exception($"Cannot resolve state from meta for {file}"); + } _logger.LogInformation("Hashing {File}", file.FileName); var hash = await _fileHashCache.FileHashCachedAsync(file, CancellationToken.None); diff --git a/Wabbajack.DTOs/Interventions/IUserIntervention.cs b/Wabbajack.DTOs/Interventions/IUserIntervention.cs index 1d1905cb..8326336c 100644 --- a/Wabbajack.DTOs/Interventions/IUserIntervention.cs +++ b/Wabbajack.DTOs/Interventions/IUserIntervention.cs @@ -1,3 +1,6 @@ +using System; +using System.Threading; + namespace Wabbajack.DTOs.Interventions; /// @@ -15,4 +18,11 @@ public interface IUserIntervention /// Whether the interaction has been handled and no longer needs attention /// bool Handled { get; } + + /// + /// Token that can be used to trigger cancellation when Cancel() is called. + /// + public CancellationToken Token { get; } + + void SetException(Exception exception); } \ No newline at end of file diff --git a/Wabbajack.Networking.GitHub/GithubAuthTokenProvider.cs b/Wabbajack.Networking.GitHub/GithubAuthTokenProvider.cs index b8f84bb0..4f284ac4 100644 --- a/Wabbajack.Networking.GitHub/GithubAuthTokenProvider.cs +++ b/Wabbajack.Networking.GitHub/GithubAuthTokenProvider.cs @@ -8,4 +8,5 @@ public abstract class GithubAuthTokenProvider : ITokenProvider public abstract ValueTask Get(); public abstract ValueTask SetToken(string val); public abstract ValueTask Delete(); + public abstract bool HaveToken(); } \ No newline at end of file