Reworked the LL downloader to abstract commonly used items in an abstract class

This commit is contained in:
Timothy Baldridge 2020-01-04 22:38:08 -07:00
parent 5e03ceda2e
commit 5f969a00df
4 changed files with 161 additions and 94 deletions

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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" />

View File

@ -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;