Implemented manual downloader support

This commit is contained in:
Timothy Baldridge 2022-05-16 16:14:52 -06:00
parent 2ea4eda9b4
commit 7b5b483303
11 changed files with 177 additions and 12 deletions

View File

@ -8,6 +8,8 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using Wabbajack.DTOs;
using Wabbajack.DTOs.Interventions;
using Wabbajack.Interventions;
using Wabbajack.LoginManagers;
using Wabbajack.Models;
using Wabbajack.Services.OSIntegrated;
@ -46,6 +48,7 @@ namespace Wabbajack
services.AddOSIntegrated();
services.AddSingleton<CefService>();
services.AddSingleton<IUserInterventionHandler, InteventionHandler>();
services.AddTransient<MainWindow>();
services.AddTransient<MainWindowVM>();
@ -73,6 +76,7 @@ namespace Wabbajack
services.AddAllSingleton<INeedsLogin, LoversLabLoginManager>();
services.AddAllSingleton<INeedsLogin, NexusLoginManager>();
services.AddAllSingleton<INeedsLogin, VectorPlexusLoginManager>();
services.AddSingleton<ManualDownloadHandler>();
return services;
}

View File

@ -0,0 +1,21 @@
using Microsoft.Extensions.Logging;
using ReactiveUI;
using Wabbajack.DTOs.Interventions;
namespace Wabbajack.Interventions;
public class InteventionHandler : IUserInterventionHandler
{
private readonly ILogger<InteventionHandler> _logger;
public InteventionHandler(ILogger<InteventionHandler> logger)
{
_logger = logger;
}
public void Raise(IUserIntervention intervention)
{
// Recast these or they won't be properly handled by the message bus
if (intervention is ManualDownload md)
MessageBus.Current.SendMessage(md);
}
}

View File

@ -7,6 +7,7 @@ using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
@ -15,6 +16,7 @@ using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.Logins;
using Wabbajack.Messages;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.UserIntervention;
namespace Wabbajack.LoginManagers;
@ -23,6 +25,7 @@ public class LoversLabLoginManager : ViewModel, INeedsLogin
private readonly ILogger<LoversLabLoginManager> _logger;
private readonly ITokenProvider<LoversLabLoginState> _token;
private readonly IUserInterventionHandler _handler;
private readonly IServiceProvider _serviceProvider;
public string SiteName { get; } = "Lovers Lab";
public ICommand TriggerLogin { get; set; }
@ -33,10 +36,11 @@ public class LoversLabLoginManager : ViewModel, INeedsLogin
[Reactive]
public bool HaveLogin { get; set; }
public LoversLabLoginManager(ILogger<LoversLabLoginManager> logger, ITokenProvider<LoversLabLoginState> token)
public LoversLabLoginManager(ILogger<LoversLabLoginManager> logger, ITokenProvider<LoversLabLoginState> token, IServiceProvider serviceProvider)
{
_logger = logger;
_token = token;
_serviceProvider = serviceProvider;
RefreshTokenState();
ClearLogin = ReactiveCommand.CreateFromTask(async () =>
@ -52,8 +56,8 @@ public class LoversLabLoginManager : ViewModel, INeedsLogin
TriggerLogin = ReactiveCommand.CreateFromTask(async () =>
{
_logger.LogInformation("Logging into {SiteName}", SiteName);
await LoversLabLogin.Send();
RefreshTokenState();
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<LoversLabLoginHandler>()));
}, this.WhenAnyValue(v => v.HaveLogin).Select(v => !v));
}

View File

@ -7,6 +7,7 @@ using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
@ -15,6 +16,7 @@ using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.Logins;
using Wabbajack.Messages;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.UserIntervention;
namespace Wabbajack.LoginManagers;
@ -23,6 +25,7 @@ public class VectorPlexusLoginManager : ViewModel, INeedsLogin
private readonly ILogger<VectorPlexusLoginManager> _logger;
private readonly ITokenProvider<VectorPlexusLoginState> _token;
private readonly IUserInterventionHandler _handler;
private readonly IServiceProvider _serviceProvider;
public string SiteName { get; } = "Vector Plexus";
public ICommand TriggerLogin { get; set; }
@ -33,10 +36,11 @@ public class VectorPlexusLoginManager : ViewModel, INeedsLogin
[Reactive]
public bool HaveLogin { get; set; }
public VectorPlexusLoginManager(ILogger<VectorPlexusLoginManager> logger, ITokenProvider<VectorPlexusLoginState> token)
public VectorPlexusLoginManager(ILogger<VectorPlexusLoginManager> logger, ITokenProvider<VectorPlexusLoginState> token, IServiceProvider serviceProvider)
{
_logger = logger;
_token = token;
_serviceProvider = serviceProvider;
RefreshTokenState();
ClearLogin = ReactiveCommand.CreateFromTask(async () =>
@ -52,8 +56,7 @@ public class VectorPlexusLoginManager : ViewModel, INeedsLogin
TriggerLogin = ReactiveCommand.CreateFromTask(async () =>
{
_logger.LogInformation("Logging into {SiteName}", SiteName);
await VectorPlexusLogin.Send();
RefreshTokenState();
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<VectorPlexusLoginHandler>()));
}, this.WhenAnyValue(v => v.HaveLogin).Select(v => !v));
}

View File

@ -0,0 +1,27 @@
using System.Security.Policy;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.DTOs;
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.Interventions;
using Wabbajack.Paths;
namespace Wabbajack.UserIntervention;
public class ManualDownloadHandler : BrowserTabViewModel
{
public ManualDownload Intervention { get; set; }
protected override async Task Run(CancellationToken token)
{
await WaitForReady();
var archive = Intervention.Archive;
var md = Intervention.Archive.State as Manual;
Instructions = string.IsNullOrWhiteSpace(md.Prompt) ? $"Please download {archive.Name}" : md.Prompt;
await NavigateTo(md.Url);
var uri = await WaitForDownloadUri(token);
Intervention.Finish(uri);
}
}

View File

@ -17,7 +17,7 @@ using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.UserIntervention;
public abstract class OAuth2LoginHandler<TIntervention, TLoginType> : WebUserInterventionBase<TIntervention>
public abstract class OAuth2LoginHandler<TIntervention, TLoginType> : BrowserTabViewModel
where TIntervention : IUserIntervention
where TLoginType : OAuth2LoginState, new()
{

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
@ -9,6 +10,7 @@ using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.Logins;
using Wabbajack.Messages;
using Wabbajack.Views;
@ -83,11 +85,38 @@ public abstract class BrowserTabViewModel : ViewModel
public async Task<HtmlDocument> GetDom(CancellationToken token)
{
var v = HttpUtility.UrlDecode("\u003D");
var source = await EvaluateJavaScript("document.body.outerHTML");
var decoded = JsonSerializer.Deserialize<string>(source);
var doc = new HtmlDocument();
doc.LoadHtml(decoded);
return doc;
}
public async Task<ManualDownload.BrowserDownloadState> WaitForDownloadUri(CancellationToken token)
{
var source = new TaskCompletionSource<Uri>();
var referer = _browser.Source;
_browser.CoreWebView2.DownloadStarting += (sender, args) =>
{
try
{
source.SetResult(new Uri(args.DownloadOperation.Uri));
}
catch (Exception ex)
{
source.SetCanceled();
}
args.Handled = true;
args.Cancel = true;
};
var uri = await source.Task.WaitAsync(token);
var cookies = await GetCookies(uri.Host, token);
return new ManualDownload.BrowserDownloadState(uri, cookies, new[]
{
("Referer", referer.ToString())
});
}
}

View File

@ -17,6 +17,7 @@ using Microsoft.Extensions.Logging;
using Wabbajack.Common;
using Wabbajack.Downloaders.GameFile;
using Wabbajack;
using Wabbajack.DTOs.Interventions;
using Wabbajack.Interventions;
using Wabbajack.LoginManagers;
using Wabbajack.Messages;
@ -112,6 +113,10 @@ namespace Wabbajack
MessageBus.Current.Listen<VectorPlexusLogin>()
.Subscribe(HandleLogin)
.DisposeWith(CompositeDisposable);
MessageBus.Current.Listen<ManualDownload>()
.Subscribe(HandleManualDownload)
.DisposeWith(CompositeDisposable);
_resourceMonitor.Updates
.Select(r => string.Join(", ", r.Where(r => r.Throughput > 0)
@ -178,7 +183,6 @@ namespace Wabbajack
{
var handler = _serviceProvider.GetRequiredService<VectorPlexusLoginHandler>();
handler.RunWrapper(CancellationToken.None).FireAndForget();
}
private void HandleNavigateBack(NavigateBack navigateBack)
@ -186,6 +190,13 @@ namespace Wabbajack
ActivePane = PreviousPanes.Last();
PreviousPanes.RemoveAt(PreviousPanes.Count - 1);
}
private void HandleManualDownload(ManualDownload manualDownload)
{
var handler = _serviceProvider.GetRequiredService<ManualDownloadHandler>();
handler.Intervention = manualDownload;
MessageBus.Current.SendMessage(new OpenBrowserTab(handler));
}
private void HandleNavigateTo(NavigateToGlobal.ScreenType s)
{

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using Wabbajack.DTOs.Logins;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Paths;
namespace Wabbajack.DTOs.Interventions;
public class ManualDownload : AUserIntervention<ManualDownload.BrowserDownloadState>
{
public Archive Archive { get; }
public AbsolutePath OutputPath { get; }
public ManualDownload(Archive archive, AbsolutePath outputPath)
{
Archive = archive;
OutputPath = outputPath;
}
public record BrowserDownloadState(Uri Uri, Cookie[] Cookies, (string Key, string Value)[] Headers)
{
}
}

View File

@ -1,19 +1,57 @@
using Wabbajack.Downloaders.Interfaces;
using Microsoft.Extensions.Logging;
using Wabbajack.Downloaders.Interfaces;
using Wabbajack.DTOs;
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.Validation;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
namespace Wabbajack.Downloaders.Manual;
public class ManualDownloader : ADownloader<DTOs.DownloadStates.Manual>
{
public override Task<Hash> Download(Archive archive, DTOs.DownloadStates.Manual state, AbsolutePath destination, IJob job, CancellationToken token)
private readonly ILogger<ManualDownloader> _logger;
private readonly IUserInterventionHandler _interventionHandler;
private readonly IResource<HttpClient> _limiter;
private readonly HttpClient _client;
public ManualDownloader(ILogger<ManualDownloader> logger, IUserInterventionHandler interventionHandler, HttpClient client)
{
throw new NotImplementedException();
_logger = logger;
_interventionHandler = interventionHandler;
_client = client;
}
public override async Task<Hash> Download(Archive archive, DTOs.DownloadStates.Manual state, AbsolutePath destination, IJob job, CancellationToken token)
{
_logger.LogInformation("Starting manual download of {Url}", state.Url);
var intervention = new ManualDownload(archive, destination);
_interventionHandler.Raise(intervention);
var browserState = await intervention.Task;
var msg = new HttpRequestMessage(HttpMethod.Get, browserState.Uri);
msg.Headers.Add("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36");
msg.Headers.Add("Cookie", string.Join(";", browserState.Cookies.Select(c => $"{c.Name}={c.Value}")));
foreach (var header in browserState.Headers)
{
msg.Headers.Add(header.Key, header.Value);
}
using var response = await _client.SendAsync(msg, token);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(response.ReasonPhrase, null, statusCode:response.StatusCode);
await using var strm = await response.Content.ReadAsStreamAsync(token);
await using var os = destination.Open(FileMode.Create, FileAccess.Write, FileShare.None);
return await strm.HashingCopy(os, token, job);
}
public override async Task<bool> Prepare()
{

View File

@ -10,4 +10,8 @@
<ProjectReference Include="..\Wabbajack.Downloaders.Interfaces\Wabbajack.Downloaders.Interfaces.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2-mauipre.1.22054.8" />
</ItemGroup>
</Project>