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:local="clr-namespace:Wabbajack"
ShutdownMode="OnExplicitShutdown"
StartupUri="Views\MainWindow.xaml">
Startup="OnStartup">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>

View File

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

View File

@ -3,6 +3,7 @@ using DynamicData.Binding;
using Microsoft.WindowsAPICodePack.Dialogs;
using ReactiveUI;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Reactive.Linq;
@ -13,6 +14,8 @@ using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Lib.Extensions;
using Wabbajack.Paths;
namespace Wabbajack
{
@ -49,6 +52,24 @@ namespace Wabbajack
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)
{

View File

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

View File

@ -4,25 +4,36 @@ using System.IO;
using System.IO.Compression;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Microsoft.Extensions.Logging;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.DTOs;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Installer;
using Wabbajack.Lib;
using Wabbajack.Lib.ModListRegistry;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Consts = Wabbajack.Lib.Consts;
namespace Wabbajack
{
public class ModListVM : ViewModel
{
private readonly DTOSerializer _dtos;
private readonly ILogger<ModListVM> _logger;
public ModList SourceModList { get; private set; }
public ModlistMetadata SourceModListMetadata { get; private set; }
public Exception Error { get; }
[Reactive]
public Exception Error { get; set; }
public AbsolutePath ModListPath { get; }
public string Name => SourceModList?.Name;
public string Readme => SourceModList?.Readme;
public string Author => SourceModList?.Author;
public string Description => SourceModList?.Description;
public Uri Website => SourceModList?.Website;
public ModManager ModManager => SourceModList?.ModManager ?? ModManager.MO2;
public Version Version => SourceModList?.Version;
public Version WabbajackVersion => SourceModList?.WabbajackVersion;
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.
public IObservable<BitmapImage> ImageObservable { get; }
public ModListVM(AbsolutePath modListPath)
public ModListVM(ILogger<ModListVM> logger, AbsolutePath modListPath, DTOSerializer dtos)
{
_dtos = dtos;
_logger = logger;
ModListPath = modListPath;
try
Task.Run(async () =>
{
SourceModList = AInstaller.LoadFromFile(modListPath);
var metadataPath = modListPath.WithExtension(Consts.ModlistMetadataExtension);
if (metadataPath.Exists)
try
{
try
SourceModList = await StandardInstaller.LoadFromFile(_dtos, modListPath);
var metadataPath = modListPath.WithExtension(Ext.ModlistMetadataExtension);
if (metadataPath.FileExists())
{
SourceModListMetadata = metadataPath.FromJson<ModlistMetadata>();
}
catch (Exception)
{
SourceModListMetadata = null;
try
{
SourceModListMetadata = await metadataPath.FromJson<ModlistMetadata>();
}
catch (Exception)
{
SourceModListMetadata = null;
}
}
}
}
catch (Exception ex)
{
Error = ex;
Utils.Error(ex, "Exception while loading the modlist!");
}
catch (Exception ex)
{
Error = ex;
_logger.LogError(ex, "Exception while loading the modlist!");
}
});
ImageObservable = Observable.Return(Unit.Default)
// Download and retrieve bytes on background thread
@ -64,7 +82,7 @@ namespace Wabbajack
{
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);
var ms = new MemoryStream();
var entry = ar.GetEntry("modlist-image.png");
@ -75,7 +93,7 @@ namespace Wabbajack
}
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);
}
})
@ -90,7 +108,7 @@ namespace Wabbajack
}
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);
}
})
@ -103,7 +121,7 @@ namespace Wabbajack
public void OpenReadme()
{
if (string.IsNullOrEmpty(Readme)) return;
Utils.OpenWebsite(new Uri(Readme));
UIUtils.OpenWebsite(new Uri(Readme));
}
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.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.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.Extensions;
namespace Wabbajack
namespace Wabbajack.View_Models
{
public class SlideShow : ViewModel
{
@ -86,7 +80,7 @@ namespace Wabbajack
if (modList?.SourceModList?.Archives == null)
{
return Observable.Empty<IMetaState>()
.ToObservableChangeSet(x => x.URL);
.ToObservableChangeSet(x => x.LinkUrl);
}
return modList.SourceModList.Archives
.Select(m => m.State)
@ -128,7 +122,7 @@ namespace Wabbajack
VisitURLCommand = ReactiveCommand.Create(
execute: () =>
{
Utils.OpenWebsite(TargetMod.State.URL);
UIUtils.OpenWebsite(TargetMod.State.URL);
return Unit.Default;
},
canExecute: this.WhenAny(x => x.TargetMod.State.URL)

View File

@ -1,23 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Windows;
using System.Windows.Threading;
using CefSharp;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.Interventions;
using Wabbajack.Lib.LibCefHelpers;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.WebAutomation;
using WebSocketSharp;
namespace Wabbajack
{
@ -25,9 +14,11 @@ namespace Wabbajack
{
public MainWindowVM MainWindow { get; }
private AsyncLock _browserLock = new();
private readonly ILogger<UserInterventionHandlers> _logger;
public UserInterventionHandlers(MainWindowVM mvm)
public UserInterventionHandlers(ILogger<UserInterventionHandlers> logger, MainWindowVM mvm)
{
_logger = logger;
MainWindow = mvm;
}
@ -36,7 +27,7 @@ namespace Wabbajack
var wait = await _browserLock.WaitAsync();
var cancel = new CancellationTokenSource();
var oldPane = MainWindow.ActivePane;
using var vm = await WebBrowserVM.GetNew();
using var vm = await WebBrowserVM.GetNew(_logger);
MainWindow.NavigateTo(vm);
vm.BackCommand = ReactiveCommand.Create(() =>
{
@ -55,7 +46,7 @@ namespace Wabbajack
}
catch (Exception ex)
{
Utils.Error(ex);
_logger.LogError(ex, "During Web browser job");
intervention.Cancel();
}
finally
@ -70,6 +61,7 @@ namespace Wabbajack
{
switch (msg)
{
/*
case RequestNexusAuthorization c:
await WrapBrowserJob(c, async (vm, cancel) =>
{
@ -100,6 +92,7 @@ namespace Wabbajack
break;
*/
case CriticalFailureIntervention c:
MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK,
MessageBoxImage.Error);
@ -112,100 +105,6 @@ namespace Wabbajack
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.Threading;
using System.Threading.Tasks;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
@ -14,4 +15,10 @@ public static class AbsolutePathExtensions
await using var fs = path.Open(FileMode.Open);
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 MO2CompilerSettings = new(".mo2_compiler_settings");
public static Extension Temp = new(".temp");
public static Extension ModlistMetadataExtension = new(".modlist_metadata");
}

View File

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.DTOs.JsonConverters;
namespace Wabbajack.Common;
@ -57,6 +59,11 @@ public static class StreamExtensions
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)
{
using var sr = new StreamReader(stream);

View File

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