From 5f969a00dfbce3c29645b20e041168c0e9ff0c92 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge <tbaldridge@gmail.com> Date: Sat, 4 Jan 2020 22:38:08 -0700 Subject: [PATCH] Reworked the LL downloader to abstract commonly used items in an abstract class --- .../AbstractNeedsLoginDownloader.cs | 142 ++++++++++++++++++ .../Downloaders/LoversLabDownloader.cs | 108 ++----------- Wabbajack.Lib/Wabbajack.Lib.csproj | 1 + .../View Models/UserInterventionHandlers.cs | 4 +- 4 files changed, 161 insertions(+), 94 deletions(-) create mode 100644 Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs diff --git a/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs new file mode 100644 index 00000000..61a83a81 --- /dev/null +++ b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using ReactiveUI; +using Wabbajack.Common; +using Wabbajack.Common.StatusFeed; +using Wabbajack.Lib.LibCefHelpers; +using Wabbajack.Lib.WebAutomation; + +namespace Wabbajack.Lib.Downloaders +{ + public abstract class AbstractNeedsLoginDownloader : INeedsLogin + { + private readonly Uri _loginUri; + private readonly string _encryptedKeyName; + private readonly string _cookieDomain; + private readonly string _cookieName; + protected HttpClient AuthedClient; + + /// <summary> + /// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log + /// in via a browser + /// </summary> + /// <param name="loginUri">The URI to preset for logging in</param> + /// <param name="encryptedKeyName">The name of the encrypted JSON key in which to store cookies</param> + /// <param name="cookieDomain">The cookie domain to scan</param> + /// <param name="cookieName">The cookie name to wait for</param> + public AbstractNeedsLoginDownloader(Uri loginUri, + string encryptedKeyName, + string cookieDomain, + string cookieName) + { + _loginUri = loginUri; + _encryptedKeyName = encryptedKeyName; + _cookieDomain = cookieDomain; + _cookieName = cookieName; + + TriggerLogin = ReactiveCommand.CreateFromTask( + execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestSiteLogin(this)).Task), + canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); + ClearLogin = ReactiveCommand.Create( + execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson(_encryptedKeyName)), + canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler)); + } + + public ICommand TriggerLogin { get; } + public ICommand ClearLogin { get; } + public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName); + public abstract string SiteName { get; } + public virtual string MetaInfo { get; } + public abstract Uri SiteURL { get; } + public virtual Uri IconUri { get; } + + protected virtual async Task WhileWaiting(IWebDriver browser) + { + } + + public async Task<Helpers.Cookie[]> GetAndCacheCookies(IWebDriver browser, Action<string> updateStatus, CancellationToken cancel) + { + updateStatus($"Please Log Into {SiteName}"); + await browser.NavigateTo(_loginUri); + var cookies = new Helpers.Cookie[0]; + while (true) + { + cancel.ThrowIfCancellationRequested(); + await WhileWaiting(browser); + cookies = (await browser.GetCookies(_cookieDomain)); + if (cookies.FirstOrDefault(c => c.Name == _cookieName) != null) + break; + await Task.Delay(500, cancel); + } + + cookies.ToEcryptedJson(_encryptedKeyName); + + return cookies; + } + + public async Task<HttpClient> GetAuthedClient() + { + Helpers.Cookie[] cookies; + try + { + cookies = Utils.FromEncryptedJson<Helpers.Cookie[]>(_encryptedKeyName); + if (cookies != null) + return Helpers.GetClient(cookies, SiteURL.ToString()); + } + catch (FileNotFoundException) { } + + cookies = await Utils.Log(new RequestSiteLogin(this)).Task; + return Helpers.GetClient(cookies, SiteURL.ToString()); + } + + public async Task Prepare() + { + AuthedClient = (await GetAuthedClient()) ?? throw new NotLoggedInError(this); + } + + public class NotLoggedInError : Exception + { + public AbstractNeedsLoginDownloader Downloader { get; } + public NotLoggedInError(AbstractNeedsLoginDownloader downloader) : base( + $"Not logged into {downloader.SiteName}, can't continue") + { + Downloader = downloader; + } + } + + + public class RequestSiteLogin : AUserIntervention + { + public AbstractNeedsLoginDownloader Downloader { get; } + public RequestSiteLogin(AbstractNeedsLoginDownloader downloader) + { + Downloader = downloader; + } + public override string ShortDescription => $"Getting {Downloader.SiteName} Login"; + public override string ExtendedDescription { get; } + + private readonly TaskCompletionSource<Helpers.Cookie[]> _source = new TaskCompletionSource<Helpers.Cookie[]>(); + public Task<Helpers.Cookie[]> Task => _source.Task; + + public void Resume(Helpers.Cookie[] cookies) + { + Handled = true; + _source.SetResult(cookies); + } + + public override void Cancel() + { + Handled = true; + _source.TrySetCanceled(); + } + } + } + + +} diff --git a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs index 1802b605..87d4edf4 100644 --- a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs +++ b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using System.Web; using System.Windows.Input; +using CefSharp; using ReactiveUI; using Wabbajack.Common; using Wabbajack.Lib.LibCefHelpers; @@ -20,32 +21,17 @@ using File = Alphaleonis.Win32.Filesystem.File; namespace Wabbajack.Lib.Downloaders { - public class LoversLabDownloader : IDownloader, INeedsLogin + public class LoversLabDownloader : AbstractNeedsLoginDownloader, IDownloader { - internal HttpClient _authedClient; - - #region INeedsDownload - - public ICommand TriggerLogin { get; } - public ICommand ClearLogin { get; } - public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable("loverslabcookies"); - public string SiteName => "Lovers Lab"; - public string MetaInfo => ""; - public Uri SiteURL => new Uri("https://loverslab.com"); - public Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico"); - - + public override string SiteName => "Lovers Lab"; + public override Uri SiteURL => new Uri("https://loverslab.com"); + public override Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico"); #endregion - public LoversLabDownloader() + public LoversLabDownloader() : base(new Uri("https://www.loverslab.com/login"), + "loverslabcookies", "loverslab.com", "ips4_member_id") { - TriggerLogin = ReactiveCommand.CreateFromTask( - execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestLoversLabLogin()).Task), - canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); - ClearLogin = ReactiveCommand.Create( - execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson("loverslabcookies")), - canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler)); } @@ -62,58 +48,17 @@ namespace Wabbajack.Lib.Downloaders FileName = file }; } - - public async Task Prepare() + protected override async Task WhileWaiting(IWebDriver browser) { - _authedClient = (await GetAuthedClient()) ?? throw new Exception("not logged into LL, TODO"); - } - - public static async Task<Helpers.Cookie[]> GetAndCacheLoversLabCookies(IWebDriver browser, Action<string> updateStatus, CancellationToken cancel) - { - updateStatus("Please Log Into Lovers Lab"); - await browser.NavigateTo(new Uri("https://www.loverslab.com/login")); - async Task<bool> CleanAds() - { - try - { - await browser.EvaluateJavaScript( - "document.querySelectorAll(\".ll_adblock\").forEach(function (itm) { itm.innerHTML = \"\";});"); - } - catch (Exception ex) - { - Utils.Error(ex); - } - return false; - } - var cookies = new Helpers.Cookie[0]; - while (true) - { - cancel.ThrowIfCancellationRequested(); - await CleanAds(); - cookies = (await browser.GetCookies("loverslab.com")); - if (cookies.FirstOrDefault(c => c.Name == "ips4_member_id") != null) - break; - await Task.Delay(500, cancel); - } - - cookies.ToEcryptedJson("loverslabcookies"); - - return cookies; - } - - public async Task<HttpClient> GetAuthedClient() - { - Helpers.Cookie[] cookies; try { - cookies = Utils.FromEncryptedJson<Helpers.Cookie[]>("loverslabcookies"); - if (cookies != null) - return Helpers.GetClient(cookies, "https://www.loverslab.com"); + await browser.EvaluateJavaScript( + "document.querySelectorAll(\".ll_adblock\").forEach(function (itm) { itm.innerHTML = \"\";});"); + } + catch (Exception ex) + { + Utils.Error(ex); } - catch (FileNotFoundException) { } - - cookies = await Utils.Log(new RequestLoversLabLogin()).Task; - return Helpers.GetClient(cookies, "https://www.loverslab.com"); } public class State : AbstractDownloadState @@ -141,7 +86,7 @@ namespace Wabbajack.Lib.Downloaders { var result = DownloadDispatcher.GetInstance<LoversLabDownloader>(); TOP: - var html = await result._authedClient.GetStringAsync( + var html = await result.AuthedClient.GetStringAsync( $"https://www.loverslab.com/files/file/{FileName}/?do=download&r={FileID}"); var pattern = new Regex("(?<=csrfKey=).*(?=[&\"\'])"); @@ -153,7 +98,7 @@ namespace Wabbajack.Lib.Downloaders var url = $"https://www.loverslab.com/files/file/{FileName}/?do=download&r={FileID}&confirm=1&t=1&csrfKey={csrfKey}"; - var streamResult = await result._authedClient.GetAsync(url); + var streamResult = await result.AuthedClient.GetAsync(url); if (streamResult.StatusCode != HttpStatusCode.OK) { Utils.Error(new InvalidOperationException(), $"LoversLab servers reported an error for file: {FileID}"); @@ -208,25 +153,4 @@ namespace Wabbajack.Lib.Downloaders } } - - public class RequestLoversLabLogin : AUserIntervention - { - public override string ShortDescription => "Getting LoversLab information"; - public override string ExtendedDescription { get; } - - private readonly TaskCompletionSource<Helpers.Cookie[]> _source = new TaskCompletionSource<Helpers.Cookie[]>(); - public Task<Helpers.Cookie[]> Task => _source.Task; - - public void Resume(Helpers.Cookie[] cookies) - { - Handled = true; - _source.SetResult(cookies); - } - - public override void Cancel() - { - Handled = true; - _source.TrySetCanceled(); - } - } } diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj index a78d73b3..93863e14 100644 --- a/Wabbajack.Lib/Wabbajack.Lib.csproj +++ b/Wabbajack.Lib/Wabbajack.Lib.csproj @@ -123,6 +123,7 @@ <Compile Include="CompilationSteps\IStackStep.cs" /> <Compile Include="CompilationSteps\PatchStockESMs.cs" /> <Compile Include="CompilationSteps\Serialization.cs" /> + <Compile Include="Downloaders\AbstractNeedsLoginDownloader.cs" /> <Compile Include="Downloaders\GameFileSourceDownloader.cs" /> <Compile Include="Downloaders\INeedsLogin.cs" /> <Compile Include="Downloaders\LoversLabDownloader.cs" /> diff --git a/Wabbajack/View Models/UserInterventionHandlers.cs b/Wabbajack/View Models/UserInterventionHandlers.cs index 7a2ef7cd..c88fa4e4 100644 --- a/Wabbajack/View Models/UserInterventionHandlers.cs +++ b/Wabbajack/View Models/UserInterventionHandlers.cs @@ -66,11 +66,11 @@ namespace Wabbajack c.Resume(key); }); break; - case RequestLoversLabLogin c: + case AbstractNeedsLoginDownloader.RequestSiteLogin c: await WrapBrowserJob(msg, async (vm, cancel) => { await vm.Driver.WaitForInitialized(); - var data = await LoversLabDownloader.GetAndCacheLoversLabCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token); + var data = await c.Downloader.GetAndCacheCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token); c.Resume(data); }); break;