Port ModListVM

This commit is contained in:
Timothy Baldridge 2021-12-26 22:13:28 -07:00
parent 33161f9817
commit 475e44c895
12 changed files with 123 additions and 160 deletions

View File

@ -4,7 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wabbajack" xmlns:local="clr-namespace:Wabbajack"
ShutdownMode="OnExplicitShutdown" ShutdownMode="OnExplicitShutdown"
StartupUri="Views\MainWindow.xaml"> Startup="OnStartup">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View File

@ -1,26 +1,33 @@
using System; using System;
using System.Windows; using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack namespace Wabbajack
{ {
/// <summary> /// <summary>
/// Interaction logic for App.xaml /// Interaction logic for App.xaml
/// </summary> /// </summary>
public partial class App : Application public partial class App
{ {
private readonly ServiceProvider _serviceProvider;
public App() public App()
{ {
Consts.LogsFolder = LauncherUpdater.CommonFolder.Value.Combine("logs"); var services = new ServiceCollection();
Consts.LogsFolder.CreateDirectory(); ConfigureServices(services);
_serviceProvider = services.BuildServiceProvider();
LoggingSettings.LogToFile = true; }
Utils.InitializeLogging().Wait(); private void ConfigureServices(ServiceCollection services)
{
CLIOld.ParseOptions(Environment.GetCommandLineArgs()); services.AddOSIntegrated();
if (CLIArguments.Help) services.AddSingleton<MainWindow>();
CLIOld.DisplayHelpText(); }
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = _serviceProvider.GetService<MainWindow>();
mainWindow!.Show();
} }
} }
} }

View File

@ -1,4 +1,10 @@
using Wabbajack.Compiler; using System;
using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Wabbajack.Compiler;
using Wabbajack.DTOs.JsonConverters; using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Paths; using Wabbajack.Paths;

View File

@ -3,6 +3,7 @@ using DynamicData.Binding;
using Microsoft.WindowsAPICodePack.Dialogs; using Microsoft.WindowsAPICodePack.Dialogs;
using ReactiveUI; using ReactiveUI;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Reactive.Linq; using System.Reactive.Linq;
@ -13,6 +14,8 @@ using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib.Extensions;
using Wabbajack.Paths;
namespace Wabbajack namespace Wabbajack
{ {
@ -49,6 +52,24 @@ namespace Wabbajack
return false; return false;
} }
} }
public static void OpenWebsite(Uri url)
{
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}")
{
CreateNoWindow = true,
});
}
public static void OpenFolder(AbsolutePath path)
{
Process.Start(new ProcessStartInfo(AbsolutePath.WindowsFolder.Combine("explorer.exe").ToString(), path.ToString())
{
CreateNoWindow = true,
});
}
public static AbsolutePath OpenFileDialog(string filter, string initialDirectory = null) public static AbsolutePath OpenFileDialog(string filter, string initialDirectory = null)
{ {

View File

@ -27,6 +27,7 @@ using Wabbajack.Lib.Extensions;
using Wabbajack.Lib.Interventions; using Wabbajack.Lib.Interventions;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
using Wabbajack.View_Models;
namespace Wabbajack namespace Wabbajack
{ {

View File

@ -4,25 +4,36 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Microsoft.Extensions.Logging;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.DTOs;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Installer;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Lib.ModListRegistry; using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Consts = Wabbajack.Lib.Consts;
namespace Wabbajack namespace Wabbajack
{ {
public class ModListVM : ViewModel public class ModListVM : ViewModel
{ {
private readonly DTOSerializer _dtos;
private readonly ILogger<ModListVM> _logger;
public ModList SourceModList { get; private set; } public ModList SourceModList { get; private set; }
public ModlistMetadata SourceModListMetadata { get; private set; } public ModlistMetadata SourceModListMetadata { get; private set; }
public Exception Error { get; }
[Reactive]
public Exception Error { get; set; }
public AbsolutePath ModListPath { get; } public AbsolutePath ModListPath { get; }
public string Name => SourceModList?.Name; public string Name => SourceModList?.Name;
public string Readme => SourceModList?.Readme; public string Readme => SourceModList?.Readme;
public string Author => SourceModList?.Author; public string Author => SourceModList?.Author;
public string Description => SourceModList?.Description; public string Description => SourceModList?.Description;
public Uri Website => SourceModList?.Website; public Uri Website => SourceModList?.Website;
public ModManager ModManager => SourceModList?.ModManager ?? ModManager.MO2;
public Version Version => SourceModList?.Version; public Version Version => SourceModList?.Version;
public Version WabbajackVersion => SourceModList?.WabbajackVersion; public Version WabbajackVersion => SourceModList?.WabbajackVersion;
public bool IsNSFW => SourceModList?.IsNSFW ?? false; public bool IsNSFW => SourceModList?.IsNSFW ?? false;
@ -32,30 +43,37 @@ namespace Wabbajack
// and the cached image will automatically be released when the last interested party is gone. // and the cached image will automatically be released when the last interested party is gone.
public IObservable<BitmapImage> ImageObservable { get; } public IObservable<BitmapImage> ImageObservable { get; }
public ModListVM(AbsolutePath modListPath) public ModListVM(ILogger<ModListVM> logger, AbsolutePath modListPath, DTOSerializer dtos)
{ {
_dtos = dtos;
_logger = logger;
ModListPath = modListPath; ModListPath = modListPath;
try
Task.Run(async () =>
{ {
SourceModList = AInstaller.LoadFromFile(modListPath); try
var metadataPath = modListPath.WithExtension(Consts.ModlistMetadataExtension);
if (metadataPath.Exists)
{ {
try SourceModList = await StandardInstaller.LoadFromFile(_dtos, modListPath);
var metadataPath = modListPath.WithExtension(Ext.ModlistMetadataExtension);
if (metadataPath.FileExists())
{ {
SourceModListMetadata = metadataPath.FromJson<ModlistMetadata>(); try
} {
catch (Exception) SourceModListMetadata = await metadataPath.FromJson<ModlistMetadata>();
{ }
SourceModListMetadata = null; catch (Exception)
{
SourceModListMetadata = null;
}
} }
} }
} catch (Exception ex)
catch (Exception ex) {
{ Error = ex;
Error = ex; _logger.LogError(ex, "Exception while loading the modlist!");
Utils.Error(ex, "Exception while loading the modlist!"); }
} });
ImageObservable = Observable.Return(Unit.Default) ImageObservable = Observable.Return(Unit.Default)
// Download and retrieve bytes on background thread // Download and retrieve bytes on background thread
@ -64,7 +82,7 @@ namespace Wabbajack
{ {
try try
{ {
await using var fs = await ModListPath.OpenShared(); await using var fs = ModListPath.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
using var ar = new ZipArchive(fs, ZipArchiveMode.Read); using var ar = new ZipArchive(fs, ZipArchiveMode.Read);
var ms = new MemoryStream(); var ms = new MemoryStream();
var entry = ar.GetEntry("modlist-image.png"); var entry = ar.GetEntry("modlist-image.png");
@ -75,7 +93,7 @@ namespace Wabbajack
} }
catch (Exception ex) catch (Exception ex)
{ {
Utils.Error(ex, $"Exception while caching Mod List image {Name}"); _logger.LogError(ex, "Exception while caching Mod List image {Name}", Name);
return default(MemoryStream); return default(MemoryStream);
} }
}) })
@ -90,7 +108,7 @@ namespace Wabbajack
} }
catch (Exception ex) catch (Exception ex)
{ {
Utils.Error(ex, $"Exception while caching Mod List image {Name}"); _logger.LogError(ex, "Exception while caching Mod List image {Name}", Name);
return default(BitmapImage); return default(BitmapImage);
} }
}) })
@ -103,7 +121,7 @@ namespace Wabbajack
public void OpenReadme() public void OpenReadme()
{ {
if (string.IsNullOrEmpty(Readme)) return; if (string.IsNullOrEmpty(Readme)) return;
Utils.OpenWebsite(new Uri(Readme)); UIUtils.OpenWebsite(new Uri(Readme));
} }
public override void Dispose() public override void Dispose()

View File

@ -1,21 +1,15 @@
using DynamicData; using System;
using System.Reactive;
using System.Reactive.Linq;
using System.Windows.Media.Imaging;
using DynamicData;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using System;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.DTOs.DownloadStates; using Wabbajack.DTOs.DownloadStates;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.Extensions; using Wabbajack.Lib.Extensions;
namespace Wabbajack namespace Wabbajack.View_Models
{ {
public class SlideShow : ViewModel public class SlideShow : ViewModel
{ {
@ -86,7 +80,7 @@ namespace Wabbajack
if (modList?.SourceModList?.Archives == null) if (modList?.SourceModList?.Archives == null)
{ {
return Observable.Empty<IMetaState>() return Observable.Empty<IMetaState>()
.ToObservableChangeSet(x => x.URL); .ToObservableChangeSet(x => x.LinkUrl);
} }
return modList.SourceModList.Archives return modList.SourceModList.Archives
.Select(m => m.State) .Select(m => m.State)
@ -128,7 +122,7 @@ namespace Wabbajack
VisitURLCommand = ReactiveCommand.Create( VisitURLCommand = ReactiveCommand.Create(
execute: () => execute: () =>
{ {
Utils.OpenWebsite(TargetMod.State.URL); UIUtils.OpenWebsite(TargetMod.State.URL);
return Unit.Default; return Unit.Default;
}, },
canExecute: this.WhenAny(x => x.TargetMod.State.URL) canExecute: this.WhenAny(x => x.TargetMod.State.URL)

View File

@ -1,23 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using System.Windows; using System.Windows;
using System.Windows.Threading; using Microsoft.Extensions.Logging;
using CefSharp;
using ReactiveUI; using ReactiveUI;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.Interventions; using Wabbajack.Lib.Interventions;
using Wabbajack.Lib.LibCefHelpers;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.WebAutomation;
using WebSocketSharp;
namespace Wabbajack namespace Wabbajack
{ {
@ -25,9 +14,11 @@ namespace Wabbajack
{ {
public MainWindowVM MainWindow { get; } public MainWindowVM MainWindow { get; }
private AsyncLock _browserLock = new(); private AsyncLock _browserLock = new();
private readonly ILogger<UserInterventionHandlers> _logger;
public UserInterventionHandlers(MainWindowVM mvm) public UserInterventionHandlers(ILogger<UserInterventionHandlers> logger, MainWindowVM mvm)
{ {
_logger = logger;
MainWindow = mvm; MainWindow = mvm;
} }
@ -36,7 +27,7 @@ namespace Wabbajack
var wait = await _browserLock.WaitAsync(); var wait = await _browserLock.WaitAsync();
var cancel = new CancellationTokenSource(); var cancel = new CancellationTokenSource();
var oldPane = MainWindow.ActivePane; var oldPane = MainWindow.ActivePane;
using var vm = await WebBrowserVM.GetNew(); using var vm = await WebBrowserVM.GetNew(_logger);
MainWindow.NavigateTo(vm); MainWindow.NavigateTo(vm);
vm.BackCommand = ReactiveCommand.Create(() => vm.BackCommand = ReactiveCommand.Create(() =>
{ {
@ -55,7 +46,7 @@ namespace Wabbajack
} }
catch (Exception ex) catch (Exception ex)
{ {
Utils.Error(ex); _logger.LogError(ex, "During Web browser job");
intervention.Cancel(); intervention.Cancel();
} }
finally finally
@ -70,6 +61,7 @@ namespace Wabbajack
{ {
switch (msg) switch (msg)
{ {
/*
case RequestNexusAuthorization c: case RequestNexusAuthorization c:
await WrapBrowserJob(c, async (vm, cancel) => await WrapBrowserJob(c, async (vm, cancel) =>
{ {
@ -100,6 +92,7 @@ namespace Wabbajack
break; break;
*/
case CriticalFailureIntervention c: case CriticalFailureIntervention c:
MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK, MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK,
MessageBoxImage.Error); MessageBoxImage.Error);
@ -112,100 +105,6 @@ namespace Wabbajack
throw new NotImplementedException($"No handler for {msg}"); throw new NotImplementedException($"No handler for {msg}");
} }
} }
private async Task OAuthLogin(RequestOAuthLogin oa, WebBrowserVM vm, CancellationTokenSource cancel)
{
await vm.Driver.WaitForInitialized();
vm.Instructions = $"Please log in and allow Wabbajack to access your {oa.SiteName} account";
var wrapper = new CefSharpWrapper(vm.Browser);
var scopes = string.Join(" ", oa.Scopes);
var state = Guid.NewGuid().ToString();
var oldHandler = Helpers.SchemeHandler;
Helpers.SchemeHandler = (browser, frame, _, request) =>
{
var req = new Uri(request.Url);
Utils.LogStraightToFile($"Got Scheme callback {req}");
var parsed = HttpUtility.ParseQueryString(req.Query);
if (parsed.Contains("state"))
{
if (parsed.Get("state") != state)
{
Utils.Log("Bad OAuth state, state, this shouldn't happen");
oa.Cancel();
return new ResourceHandler();
}
}
if (parsed.Contains("code"))
{
Helpers.SchemeHandler = oldHandler;
oa.Resume(parsed.Get("code"));
}
else
{
oa.Cancel();
}
return new ResourceHandler();
};
await wrapper.NavigateTo(new Uri(oa.AuthorizationEndpoint + $"?response_type=code&client_id={oa.ClientID}&state={state}&scope={scopes}"));
while (!oa.Task.IsCanceled && !oa.Task.IsCompleted && !cancel.IsCancellationRequested)
await Task.Delay(250);
}
private async Task HandleManualDownload(WebBrowserVM vm, CancellationTokenSource cancel, ManuallyDownloadFile manuallyDownloadFile)
{
var browser = new CefSharpWrapper(vm.Browser);
vm.Instructions = $"Please locate and download {manuallyDownloadFile.State.Url}";
var result = new TaskCompletionSource<Uri>();
browser.DownloadHandler = uri =>
{
//var client = Helpers.GetClient(browser.GetCookies("").Result, browser.Location);
result.SetResult(uri);
};
await vm.Driver.WaitForInitialized();
await browser.NavigateTo(new Uri(manuallyDownloadFile.State.Url));
while (!cancel.IsCancellationRequested)
{
if (result.Task.IsCompleted)
{
var cookies = await Helpers.GetCookies();
var referer = browser.Location;
var client = Helpers.GetClient(cookies, referer);
manuallyDownloadFile.Resume(result.Task.Result, client);
break;
}
await Task.Delay(100);
}
}
private async Task HandleManualNexusDownload(WebBrowserVM vm, CancellationTokenSource cancel, ManuallyDownloadNexusFile manuallyDownloadNexusFile)
{
var state = manuallyDownloadNexusFile.State;
var game = state.Game.MetaData();
await vm.Driver.WaitForInitialized();
IWebDriver browser = new CefSharpWrapper(vm.Browser);
vm.Instructions = $"Click the download button to continue (get a NexusMods.com Premium account to automate this)";
browser.DownloadHandler = uri =>
{
manuallyDownloadNexusFile.Resume(uri);
browser.DownloadHandler = null;
};
var url = new Uri(@$"https://www.nexusmods.com/{game.NexusName}/mods/{state.ModID}?tab=files&file_id={state.FileID}");
await browser.NavigateTo(url);
while (!cancel.IsCancellationRequested && !manuallyDownloadNexusFile.Task.IsCompleted) {
await Task.Delay(250);
}
}
} }
} }

View File

@ -1,6 +1,7 @@
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Hashing.xxHash64; using Wabbajack.Hashing.xxHash64;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
@ -14,4 +15,10 @@ public static class AbsolutePathExtensions
await using var fs = path.Open(FileMode.Open); await using var fs = path.Open(FileMode.Open);
return await fs.HashingCopy(Stream.Null, token ?? CancellationToken.None); return await fs.HashingCopy(Stream.Null, token ?? CancellationToken.None);
} }
public static async Task<T> FromJson<T>(this AbsolutePath path, DTOSerializer? dtos = null)
{
await using var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
return await fs.FromJson<T>(dtos);
}
} }

View File

@ -23,4 +23,5 @@ public static class Ext
public static Extension CompilerSettings = new(".compiler_settings"); public static Extension CompilerSettings = new(".compiler_settings");
public static Extension MO2CompilerSettings = new(".mo2_compiler_settings"); public static Extension MO2CompilerSettings = new(".mo2_compiler_settings");
public static Extension Temp = new(".temp"); public static Extension Temp = new(".temp");
public static Extension ModlistMetadataExtension = new(".modlist_metadata");
} }

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Wabbajack.DTOs.JsonConverters;
namespace Wabbajack.Common; namespace Wabbajack.Common;
@ -57,6 +59,11 @@ public static class StreamExtensions
return sr.ReadToEnd(); return sr.ReadToEnd();
} }
public static async Task<T> FromJson<T>(this Stream stream, DTOSerializer? dtos = null)
{
return (await JsonSerializer.DeserializeAsync<T>(stream, dtos?.Options))!;
}
public static async IAsyncEnumerable<string> ReadLinesAsync(this Stream stream) public static async IAsyncEnumerable<string> ReadLinesAsync(this Stream stream)
{ {
using var sr = new StreamReader(stream); using var sr = new StreamReader(stream);

View File

@ -10,4 +10,6 @@ public interface IMetaState
Uri? ImageURL { get; set; } Uri? ImageURL { get; set; }
bool IsNSFW { get; set; } bool IsNSFW { get; set; }
string? Description { get; set; } string? Description { get; set; }
Uri? LinkUrl { get; }
} }