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;