diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..f95026326
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+[*.cs]
+
+# CS0618: Type or member is obsolete
+dotnet_diagnostic.CS0618.severity = none
diff --git a/.gitignore b/.gitignore
index cf1eb9bc8..7ee716998 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,4 @@ obj
*.user
.\packages\*
/VERSION.txt
-*.opencover.xml
\ No newline at end of file
+*.opencover.xml
diff --git a/Wabbajack.App.Wpf/App.xaml.cs b/Wabbajack.App.Wpf/App.xaml.cs
index b796692de..d69cfc78f 100644
--- a/Wabbajack.App.Wpf/App.xaml.cs
+++ b/Wabbajack.App.Wpf/App.xaml.cs
@@ -1,4 +1,6 @@
using System;
+using System.Diagnostics;
+using System.IO;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
@@ -26,176 +28,201 @@
using Wabbajack.Util;
using Ext = Wabbajack.Common.Ext;
-namespace Wabbajack
+namespace Wabbajack;
+
+///
+/// Interaction logic for App.xaml
+///
+public partial class App
{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App
- {
- private IHost _host;
+ private IHost _host;
- private void OnStartup(object sender, StartupEventArgs e)
+ private void OnStartup(object sender, StartupEventArgs e)
+ {
+ if (IsAdmin())
{
- if (IsAdmin())
+ var messageBox = MessageBox.Show("Don't run Wabbajack as Admin!", "Error", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly);
+ if (messageBox == MessageBoxResult.OK)
{
- var messageBox = MessageBox.Show("Don't run Wabbajack as Admin!", "Error", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly);
- if (messageBox == MessageBoxResult.OK)
- {
- Environment.Exit(1);
- }
- else
- {
- Environment.Exit(1);
- }
+ Environment.Exit(1);
}
+ else
+ {
+ Environment.Exit(1);
+ }
+ }
- RxApp.MainThreadScheduler = new DispatcherScheduler(Dispatcher.CurrentDispatcher);
- _host = Host.CreateDefaultBuilder(Array.Empty())
- .ConfigureLogging(AddLogging)
- .ConfigureServices((host, services) =>
- {
- ConfigureServices(services);
- })
- .Build();
+ RxApp.MainThreadScheduler = new DispatcherScheduler(Dispatcher.CurrentDispatcher);
+ _host = Host.CreateDefaultBuilder(Array.Empty())
+ .ConfigureLogging(AddLogging)
+ .ConfigureServices((host, services) =>
+ {
+ ConfigureServices(services);
+ })
+ .Build();
+
+ var webview2 = _host.Services.GetRequiredService();
+ var currentDir = (AbsolutePath)Directory.GetCurrentDirectory();
+ var webViewDir = currentDir.Combine("WebView2");
+ if(webViewDir.DirectoryExists())
+ {
+ var logger = _host.Services.GetRequiredService>();
+ logger.LogInformation("Local WebView2 executable folder found. Using folder {0} instead of system binaries!", currentDir.Combine("WebView2"));
+ webview2.CreationProperties = new CoreWebView2CreationProperties() { BrowserExecutableFolder = currentDir.Combine("WebView2").ToString() };
+ }
- var args = e.Args;
+ var args = e.Args;
- RxApp.MainThreadScheduler.Schedule(0, (_, _) =>
+ RxApp.MainThreadScheduler.Schedule(0, (_, _) =>
+ {
+ if (args.Length == 1)
{
- if (args.Length == 1)
- {
- var arg = args[0].ToAbsolutePath();
- if (arg.FileExists() && arg.Extension == Ext.Wabbajack)
- {
- var mainWindow = _host.Services.GetRequiredService();
- mainWindow!.Show();
- return Disposable.Empty;
- }
- } else if (args.Length > 0)
- {
- var builder = _host.Services.GetRequiredService();
- builder.Run(e.Args).ContinueWith(async x =>
- {
- Environment.Exit(await x);
- });
- return Disposable.Empty;
- }
- else
+ var arg = args[0].ToAbsolutePath();
+ if (arg.FileExists() && arg.Extension == Ext.Wabbajack)
{
var mainWindow = _host.Services.GetRequiredService();
mainWindow!.Show();
return Disposable.Empty;
}
-
+ } else if (args.Length > 0)
+ {
+ var builder = _host.Services.GetRequiredService();
+ builder.Run(e.Args).ContinueWith(async x =>
+ {
+ Environment.Exit(await x);
+ });
return Disposable.Empty;
- });
- }
+ }
+ else
+ {
+ var mainWindow = _host.Services.GetRequiredService();
+ mainWindow!.Show();
+ return Disposable.Empty;
+ }
- private static bool IsAdmin()
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return false;
+ return Disposable.Empty;
+ });
+ }
- try
- {
- var identity = WindowsIdentity.GetCurrent();
- var owner = identity.Owner;
- if (owner is not null) return owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
+ protected override void OnExit(ExitEventArgs e)
+ {
+ base.OnExit(e);
+ }
- var principle = new WindowsPrincipal(identity);
- return principle.IsInRole(WindowsBuiltInRole.Administrator);
+ private static bool IsAdmin()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return false;
- }
- catch (Exception)
- {
- return false;
- }
- }
+ try
+ {
+ var identity = WindowsIdentity.GetCurrent();
+ var owner = identity.Owner;
+ if (owner is not null) return owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
+
+ var principle = new WindowsPrincipal(identity);
+ return principle.IsInRole(WindowsBuiltInRole.Administrator);
- private void AddLogging(ILoggingBuilder loggingBuilder)
+ }
+ catch (Exception)
{
- var config = new NLog.Config.LoggingConfiguration();
+ return false;
+ }
+ }
- var logFolder = KnownFolders.LauncherAwarePath.Combine("logs");
- if (!logFolder.DirectoryExists())
- logFolder.CreateDirectory();
+ private void AddLogging(ILoggingBuilder loggingBuilder)
+ {
+ var config = new NLog.Config.LoggingConfiguration();
- var fileTarget = new FileTarget("file")
- {
- FileName = logFolder.Combine("Wabbajack.current.log").ToString(),
- ArchiveFileName = logFolder.Combine("Wabbajack.{##}.log").ToString(),
- ArchiveOldFileOnStartup = true,
- MaxArchiveFiles = 10,
- Layout = "${processtime} [${level:uppercase=true}] (${logger}) ${message:withexception=true}",
- Header = "############ Wabbajack log file - ${longdate} ############"
- };
+ var logFolder = KnownFolders.LauncherAwarePath.Combine("logs");
+ if (!logFolder.DirectoryExists())
+ logFolder.CreateDirectory();
- var consoleTarget = new ConsoleTarget("console");
+ var fileTarget = new FileTarget("file")
+ {
+ FileName = logFolder.Combine("Wabbajack.current.log").ToString(),
+ ArchiveFileName = logFolder.Combine("Wabbajack.{##}.log").ToString(),
+ ArchiveOldFileOnStartup = true,
+ MaxArchiveFiles = 10,
+ Layout = "${processtime} [${level:uppercase=true}] (${logger}) ${message:withexception=true}",
+ Header = "############ Wabbajack log file - ${longdate} ############"
+ };
- var uiTarget = new LogStream
- {
- Name = "ui",
- Layout = "${message:withexception=false}",
- };
+ var consoleTarget = new ConsoleTarget("console");
- loggingBuilder.Services.AddSingleton(uiTarget);
+ var uiTarget = new LogStream
+ {
+ Name = "ui",
+ Layout = "${message:withexception=false}",
+ };
- config.AddRuleForAllLevels(fileTarget);
- config.AddRuleForAllLevels(consoleTarget);
- config.AddRuleForAllLevels(uiTarget);
+ loggingBuilder.Services.AddSingleton(uiTarget);
- loggingBuilder.ClearProviders();
- loggingBuilder.SetMinimumLevel(LogLevel.Information);
- loggingBuilder.AddNLog(config);
- }
+ config.AddRuleForAllLevels(fileTarget);
+ config.AddRuleForAllLevels(consoleTarget);
+ config.AddRuleForAllLevels(uiTarget);
- private static IServiceCollection ConfigureServices(IServiceCollection services)
- {
- services.AddOSIntegrated();
-
- // Orc.FileAssociation
- services.AddSingleton(new ApplicationRegistrationService());
-
- services.AddSingleton();
- services.AddSingleton();
-
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
-
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
-
- // Login Handlers
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
-
- // Login Managers
-
- //Disabled LL because it is currently not used and broken due to the way LL butchers their API
- //services.AddAllSingleton();
- services.AddAllSingleton();
- //Disabled VP due to frequent login issues & because the only file that really got downloaded there has a mirror
- //services.AddAllSingleton();
- services.AddSingleton();
- services.AddSingleton();
-
- // Verbs
- services.AddSingleton();
- services.AddCLIVerbs();
-
- return services;
- }
+ loggingBuilder.ClearProviders();
+ loggingBuilder.AddFilter("System.Net.Http.HttpClient", LogLevel.Warning);
+ loggingBuilder.SetMinimumLevel(LogLevel.Information);
+ loggingBuilder.AddNLog(config);
+ }
+
+ private static IServiceCollection ConfigureServices(IServiceCollection services)
+ {
+ services.AddOSIntegrated();
+
+ // Orc.FileAssociation
+ services.AddSingleton(new ApplicationRegistrationService());
+
+ // Singletons
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ var currentDir = (AbsolutePath)Directory.GetCurrentDirectory();
+ var webViewDir = currentDir.Combine("webview2");
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // ViewModels
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ // Login Handlers
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ // Login Managers
+
+ //Disabled LL because it is currently not used and broken due to the way LL butchers their API
+ //services.AddAllSingleton();
+ services.AddAllSingleton();
+ //Disabled VP due to frequent login issues & because the only file that really got downloaded there has a mirror
+ //services.AddAllSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // Verbs
+ services.AddSingleton();
+ services.AddCLIVerbs();
+
+ return services;
}
}
diff --git a/Wabbajack.App.Wpf/Consts.cs b/Wabbajack.App.Wpf/Consts.cs
index 8f1ada392..108bf0fdc 100644
--- a/Wabbajack.App.Wpf/Consts.cs
+++ b/Wabbajack.App.Wpf/Consts.cs
@@ -1,6 +1,7 @@
using System;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
+using Wabbajack.RateLimiter;
namespace Wabbajack;
@@ -9,6 +10,12 @@ public static class Consts
public static RelativePath MO2IniName = "ModOrganizer.ini".ToRelativePath();
public static string AppName = "Wabbajack";
public static Uri WabbajackBuildServerUri => new("https://build.wabbajack.org");
+ public static Uri WabbajackModlistWizardUri => new("https://wizard.wabbajack.org");
+ public static Uri WabbajackGithubUri => new("https://github.com/wabbajack-tools/wabbajack");
+ public static Uri WabbajackDiscordUri => new("https://discord.gg/wabbajack");
+ public static Uri WabbajackPatreonUri => new("https://www.patreon.com/user?u=11907933");
+ public static Uri WabbajackWikiUri => new("https://wiki.wabbajack.org");
+ public static Uri TlsInfoUri => new("https://www.howsmyssl.com/a/check");
public static Version CurrentMinimumWabbajackVersion { get; set; } = Version.Parse("2.3.0.0");
public static bool UseNetworkWorkaroundMode { get; set; } = false;
public static AbsolutePath CefCacheLocation { get; } = KnownFolders.WabbajackAppLocal.Combine("Cef");
@@ -18,4 +25,5 @@ public static class Consts
public static byte SettingsVersion = 0;
public static RelativePath NativeSettingsJson = "native_settings.json".ToRelativePath();
+ public const string AllSavedCompilerSettingsPaths = "compiler_settings_paths";
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Converters/AbsolutePathToStringConverter.cs b/Wabbajack.App.Wpf/Converters/AbsolutePathToStringConverter.cs
index 753bf0451..89acc813b 100644
--- a/Wabbajack.App.Wpf/Converters/AbsolutePathToStringConverter.cs
+++ b/Wabbajack.App.Wpf/Converters/AbsolutePathToStringConverter.cs
@@ -2,7 +2,6 @@
using System.Globalization;
using System.Windows.Data;
using ReactiveUI;
-using Wabbajack.Common;
using Wabbajack.Paths;
namespace Wabbajack
diff --git a/Wabbajack.App.Wpf/Converters/CommandConverter.cs b/Wabbajack.App.Wpf/Converters/CommandConverter.cs
index 2cee9ae30..da9cc8e69 100644
--- a/Wabbajack.App.Wpf/Converters/CommandConverter.cs
+++ b/Wabbajack.App.Wpf/Converters/CommandConverter.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Input;
using ReactiveUI;
diff --git a/Wabbajack.App.Wpf/Converters/ConverterRegistration.cs b/Wabbajack.App.Wpf/Converters/ConverterRegistration.cs
index 2c961991f..cc5ef5a42 100644
--- a/Wabbajack.App.Wpf/Converters/ConverterRegistration.cs
+++ b/Wabbajack.App.Wpf/Converters/ConverterRegistration.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using ReactiveUI;
+using ReactiveUI;
using Splat;
namespace Wabbajack
diff --git a/Wabbajack.App.Wpf/Converters/IntDownCastConverter.cs b/Wabbajack.App.Wpf/Converters/IntDownCastConverter.cs
index ee8f93269..77812d0a1 100644
--- a/Wabbajack.App.Wpf/Converters/IntDownCastConverter.cs
+++ b/Wabbajack.App.Wpf/Converters/IntDownCastConverter.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Input;
using ReactiveUI;
diff --git a/Wabbajack.App.Wpf/Converters/IsNexusArchiveConverter.cs b/Wabbajack.App.Wpf/Converters/IsNexusArchiveConverter.cs
new file mode 100644
index 000000000..948f9dfa5
--- /dev/null
+++ b/Wabbajack.App.Wpf/Converters/IsNexusArchiveConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using Wabbajack.DTOs;
+using Wabbajack.DTOs.DownloadStates;
+
+namespace Wabbajack
+{
+ public class IsNexusArchiveConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null) return false;
+ return value is Archive a && a.State.GetType() == typeof(Nexus);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Wabbajack.App.Wpf/Converters/IsTypeVisibilityConverter.cs b/Wabbajack.App.Wpf/Converters/IsTypeVisibilityConverter.cs
index b54d5995b..7b228b286 100644
--- a/Wabbajack.App.Wpf/Converters/IsTypeVisibilityConverter.cs
+++ b/Wabbajack.App.Wpf/Converters/IsTypeVisibilityConverter.cs
@@ -1,9 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
diff --git a/Wabbajack.App.Wpf/Converters/NexusArchiveStateConverter.cs b/Wabbajack.App.Wpf/Converters/NexusArchiveStateConverter.cs
new file mode 100644
index 000000000..f25acf9e6
--- /dev/null
+++ b/Wabbajack.App.Wpf/Converters/NexusArchiveStateConverter.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using Wabbajack.Common;
+using Wabbajack.DTOs.DownloadStates;
+
+namespace Wabbajack
+{
+ public class NexusArchiveStateConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if(value is Nexus nexus)
+ {
+ var nexusType = value.GetType();
+ var nexusProperty = nexusType.GetProperty(parameter.ToString());
+ return nexusProperty.GetValue(nexus);
+ }
+ return "";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Wabbajack.App.Wpf/Converters/PercentToDoubleConverter.cs b/Wabbajack.App.Wpf/Converters/PercentToDoubleConverter.cs
index 2eb47d55f..daf3992f0 100644
--- a/Wabbajack.App.Wpf/Converters/PercentToDoubleConverter.cs
+++ b/Wabbajack.App.Wpf/Converters/PercentToDoubleConverter.cs
@@ -1,11 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Input;
using ReactiveUI;
-using Wabbajack.Common;
using Wabbajack.RateLimiter;
namespace Wabbajack
diff --git a/Wabbajack.App.Wpf/Converters/WidthHeightRectConverter.cs b/Wabbajack.App.Wpf/Converters/WidthHeightRectConverter.cs
new file mode 100644
index 000000000..4c8655966
--- /dev/null
+++ b/Wabbajack.App.Wpf/Converters/WidthHeightRectConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Wabbajack
+{
+ public class WidthHeightRectConverter : IMultiValueConverter
+ {
+ public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+ {
+ double rectWidth = 0;
+ double rectHeight = 0;
+ if (values[0] is not null && double.TryParse(values[0].ToString(), out var width))
+ rectWidth = width;
+ else return null;
+ if (values[1] is not null && double.TryParse(values[1].ToString(), out var height))
+ rectHeight = height;
+ else return null;
+ return new Rect(0, 0, rectWidth, rectHeight);
+ }
+ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+ => throw new NotImplementedException();
+ }
+}
diff --git a/Wabbajack.App.Wpf/Extensions/DynamicDataExt.cs b/Wabbajack.App.Wpf/Extensions/DynamicDataExt.cs
index 41561fe76..b36e2e88a 100644
--- a/Wabbajack.App.Wpf/Extensions/DynamicDataExt.cs
+++ b/Wabbajack.App.Wpf/Extensions/DynamicDataExt.cs
@@ -1,9 +1,6 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
-using System.Text;
-using System.Threading.Tasks;
using DynamicData;
namespace Wabbajack
diff --git a/Wabbajack.App.Wpf/Extensions/IViewForExt.cs b/Wabbajack.App.Wpf/Extensions/IViewForExt.cs
index 659187755..fde2fca7c 100644
--- a/Wabbajack.App.Wpf/Extensions/IViewForExt.cs
+++ b/Wabbajack.App.Wpf/Extensions/IViewForExt.cs
@@ -1,9 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Linq.Expressions;
-using System.Text;
-using System.Threading.Tasks;
using ReactiveUI;
namespace Wabbajack
diff --git a/Wabbajack.App.Wpf/Interventions/AErrorMessage.cs b/Wabbajack.App.Wpf/Interventions/AErrorMessage.cs
index 94105a0fe..73cd65654 100644
--- a/Wabbajack.App.Wpf/Interventions/AErrorMessage.cs
+++ b/Wabbajack.App.Wpf/Interventions/AErrorMessage.cs
@@ -1,12 +1,11 @@
using System;
-namespace Wabbajack.Interventions
+namespace Wabbajack.Interventions;
+
+public abstract class AErrorMessage : Exception, IException
{
- public abstract class AErrorMessage : Exception, IException
- {
- public DateTime Timestamp { get; } = DateTime.Now;
- public abstract string ShortDescription { get; }
- public abstract string ExtendedDescription { get; }
- Exception IException.Exception => this;
- }
+ public DateTime Timestamp { get; } = DateTime.Now;
+ public abstract string ShortDescription { get; }
+ public abstract string ExtendedDescription { get; }
+ Exception IException.Exception => this;
}
diff --git a/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs b/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs
index f8fd944e2..2da28b651 100644
--- a/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs
+++ b/Wabbajack.App.Wpf/Interventions/AUserIntervention.cs
@@ -1,37 +1,30 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using System.Threading;
-using System.Threading.Tasks;
using System.Windows.Input;
using ReactiveUI;
-using Wabbajack.Common;
using Wabbajack.DTOs.Interventions;
-using Wabbajack.Interventions;
-namespace Wabbajack
+namespace Wabbajack;
+
+public abstract class AUserIntervention : ReactiveObject, IUserIntervention
{
- public abstract class AUserIntervention : ReactiveObject, IUserIntervention
- {
- public DateTime Timestamp { get; } = DateTime.Now;
- public abstract string ShortDescription { get; }
- public abstract string ExtendedDescription { get; }
+ public DateTime Timestamp { get; } = DateTime.Now;
+ public abstract string ShortDescription { get; }
+ public abstract string ExtendedDescription { get; }
- private bool _handled;
- public bool Handled { get => _handled; set => this.RaiseAndSetIfChanged(ref _handled, value); }
- public CancellationToken Token { get; }
- public void SetException(Exception exception)
- {
- throw new NotImplementedException();
- }
+ private bool _handled;
+ public bool Handled { get => _handled; set => this.RaiseAndSetIfChanged(ref _handled, value); }
+ public CancellationToken Token { get; }
+ public void SetException(Exception exception)
+ {
+ throw new NotImplementedException();
+ }
- public abstract void Cancel();
- public ICommand CancelCommand { get; }
+ public abstract void Cancel();
+ public ICommand CancelCommand { get; }
- public AUserIntervention()
- {
- CancelCommand = ReactiveCommand.Create(() => Cancel());
- }
+ public AUserIntervention()
+ {
+ CancelCommand = ReactiveCommand.Create(() => Cancel());
}
}
diff --git a/Wabbajack.App.Wpf/Interventions/ConfirmationIntervention.cs b/Wabbajack.App.Wpf/Interventions/ConfirmationIntervention.cs
index f0ce10670..0827b9ca4 100644
--- a/Wabbajack.App.Wpf/Interventions/ConfirmationIntervention.cs
+++ b/Wabbajack.App.Wpf/Interventions/ConfirmationIntervention.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using System.Windows.Input;
using ReactiveUI;
diff --git a/Wabbajack.App.Wpf/Interventions/IError.cs b/Wabbajack.App.Wpf/Interventions/IError.cs
index 15c0c443f..f88de312b 100644
--- a/Wabbajack.App.Wpf/Interventions/IError.cs
+++ b/Wabbajack.App.Wpf/Interventions/IError.cs
@@ -1,6 +1,5 @@
-namespace Wabbajack.Interventions
+namespace Wabbajack.Interventions;
+
+public interface IError : IStatusMessage
{
- public interface IError : IStatusMessage
- {
- }
}
diff --git a/Wabbajack.App.Wpf/Interventions/IException.cs b/Wabbajack.App.Wpf/Interventions/IException.cs
index 85d0d2705..2fbee5a5e 100644
--- a/Wabbajack.App.Wpf/Interventions/IException.cs
+++ b/Wabbajack.App.Wpf/Interventions/IException.cs
@@ -1,9 +1,8 @@
using System;
-namespace Wabbajack.Interventions
+namespace Wabbajack.Interventions;
+
+public interface IException : IError
{
- public interface IException : IError
- {
- Exception Exception { get; }
- }
+ Exception Exception { get; }
}
diff --git a/Wabbajack.App.Wpf/Interventions/IStatusMessage.cs b/Wabbajack.App.Wpf/Interventions/IStatusMessage.cs
index 7d01ad50d..2dba5b6a7 100644
--- a/Wabbajack.App.Wpf/Interventions/IStatusMessage.cs
+++ b/Wabbajack.App.Wpf/Interventions/IStatusMessage.cs
@@ -1,11 +1,10 @@
using System;
-namespace Wabbajack.Interventions
+namespace Wabbajack.Interventions;
+
+public interface IStatusMessage
{
- public interface IStatusMessage
- {
- DateTime Timestamp { get; }
- string ShortDescription { get; }
- string ExtendedDescription { get; }
- }
+ DateTime Timestamp { get; }
+ string ShortDescription { get; }
+ string ExtendedDescription { get; }
}
diff --git a/Wabbajack.App.Wpf/Interventions/UserInterventionHandler.cs b/Wabbajack.App.Wpf/Interventions/UserInterventionHandler.cs
index 549ae093d..f57f11132 100644
--- a/Wabbajack.App.Wpf/Interventions/UserInterventionHandler.cs
+++ b/Wabbajack.App.Wpf/Interventions/UserInterventionHandler.cs
@@ -1,6 +1,4 @@
using System;
-using System.Reactive.Disposables;
-using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ReactiveUI;
@@ -10,12 +8,12 @@
namespace Wabbajack.Interventions;
-public class UserIntreventionHandler : IUserInterventionHandler
+public class UserInterventionHandler : IUserInterventionHandler
{
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
- public UserIntreventionHandler(ILogger logger, IServiceProvider serviceProvider)
+ public UserInterventionHandler(ILogger logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
@@ -29,14 +27,14 @@ public void Raise(IUserIntervention intervention)
{
var provider = _serviceProvider.GetRequiredService();
provider.Intervention = md;
- MessageBus.Current.SendMessage(new SpawnBrowserWindow(provider));
+ MessageBus.Current.SendMessage(new ShowBrowserWindow(provider));
break;
}
case ManualBlobDownload bd:
{
var provider = _serviceProvider.GetRequiredService();
provider.Intervention = bd;
- MessageBus.Current.SendMessage(new SpawnBrowserWindow(provider));
+ MessageBus.Current.SendMessage(new ShowBrowserWindow(provider));
break;
}
default:
diff --git a/Wabbajack.App.Wpf/LauncherUpdater.cs b/Wabbajack.App.Wpf/LauncherUpdater.cs
index 96d3fd6be..738e30b8a 100644
--- a/Wabbajack.App.Wpf/LauncherUpdater.cs
+++ b/Wabbajack.App.Wpf/LauncherUpdater.cs
@@ -2,11 +2,9 @@
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
-using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using Microsoft.VisualBasic.CompilerServices;
using Newtonsoft.Json;
using Wabbajack.Common;
using Wabbajack.Downloaders;
@@ -14,160 +12,157 @@
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Networking.Http;
-using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
-using Wabbajack.RateLimiter;
-namespace Wabbajack
+namespace Wabbajack;
+
+public class LauncherUpdater
{
- public class LauncherUpdater
+ private readonly ILogger _logger;
+ private readonly HttpClient _client;
+ private readonly Client _wjclient;
+ private readonly DTOSerializer _dtos;
+
+ private readonly DownloadDispatcher _downloader;
+
+ private static Uri GITHUB_REPO_RELEASES = new("https://api.github.com/repos/wabbajack-tools/wabbajack/releases");
+
+ public LauncherUpdater(ILogger logger, HttpClient client, Client wjclient, DTOSerializer dtos,
+ DownloadDispatcher downloader)
{
- private readonly ILogger _logger;
- private readonly HttpClient _client;
- private readonly Client _wjclient;
- private readonly DTOSerializer _dtos;
+ _logger = logger;
+ _client = client;
+ _wjclient = wjclient;
+ _dtos = dtos;
+ _downloader = downloader;
+ }
- private readonly DownloadDispatcher _downloader;
- private static Uri GITHUB_REPO_RELEASES = new("https://api.github.com/repos/wabbajack-tools/wabbajack/releases");
+ public static Lazy CommonFolder = new (() =>
+ {
+ var entryPoint = KnownFolders.EntryPoint;
- public LauncherUpdater(ILogger logger, HttpClient client, Client wjclient, DTOSerializer dtos,
- DownloadDispatcher downloader)
+ // If we're not in a folder that looks like a version, abort
+ if (!Version.TryParse(entryPoint.FileName.ToString(), out var version))
{
- _logger = logger;
- _client = client;
- _wjclient = wjclient;
- _dtos = dtos;
- _downloader = downloader;
+ return entryPoint;
}
-
- public static Lazy CommonFolder = new (() =>
+ // If we're not in a folder that has Wabbajack.exe in the parent folder, abort
+ if (!entryPoint.Parent.Combine(Consts.AppName).WithExtension(new Extension(".exe")).FileExists())
{
- var entryPoint = KnownFolders.EntryPoint;
-
- // If we're not in a folder that looks like a version, abort
- if (!Version.TryParse(entryPoint.FileName.ToString(), out var version))
- {
- return entryPoint;
- }
+ return entryPoint;
+ }
- // If we're not in a folder that has Wabbajack.exe in the parent folder, abort
- if (!entryPoint.Parent.Combine(Consts.AppName).WithExtension(new Extension(".exe")).FileExists())
- {
- return entryPoint;
- }
+ return entryPoint.Parent;
+ });
- return entryPoint.Parent;
- });
+ public async Task Run()
+ {
- public async Task Run()
+ if (CommonFolder.Value == KnownFolders.EntryPoint)
{
+ _logger.LogInformation("Outside of standard install folder, not updating");
+ return;
+ }
- if (CommonFolder.Value == KnownFolders.EntryPoint)
- {
- _logger.LogInformation("Outside of standard install folder, not updating");
- return;
- }
+ var version = Version.Parse(KnownFolders.EntryPoint.FileName.ToString());
- var version = Version.Parse(KnownFolders.EntryPoint.FileName.ToString());
+ var oldVersions = CommonFolder.Value
+ .EnumerateDirectories()
+ .Select(f => Version.TryParse(f.FileName.ToString(), out var ver) ? (ver, f) : default)
+ .Where(f => f != default)
+ .Where(f => f.ver < version)
+ .Select(f => f!)
+ .OrderByDescending(f => f)
+ .Skip(2)
+ .ToArray();
- var oldVersions = CommonFolder.Value
- .EnumerateDirectories()
- .Select(f => Version.TryParse(f.FileName.ToString(), out var ver) ? (ver, f) : default)
- .Where(f => f != default)
- .Where(f => f.ver < version)
- .Select(f => f!)
- .OrderByDescending(f => f)
- .Skip(2)
- .ToArray();
+ foreach (var (_, path) in oldVersions)
+ {
+ _logger.LogInformation("Deleting old Wabbajack version at: {Path}", path);
+ path.DeleteDirectory();
+ }
- foreach (var (_, path) in oldVersions)
+ var release = (await GetReleases())
+ .Select(release => Version.TryParse(release.Tag, out version) ? (version, release) : default)
+ .Where(r => r != default)
+ .OrderByDescending(r => r.version)
+ .Select(r =>
{
- _logger.LogInformation("Deleting old Wabbajack version at: {Path}", path);
- path.DeleteDirectory();
- }
+ var (version, release) = r;
+ var asset = release.Assets.FirstOrDefault(a => a.Name == "Wabbajack.exe");
+ return asset != default ? (version, release, asset) : default;
+ })
+ .FirstOrDefault();
- var release = (await GetReleases())
- .Select(release => Version.TryParse(release.Tag, out version) ? (version, release) : default)
- .Where(r => r != default)
- .OrderByDescending(r => r.version)
- .Select(r =>
- {
- var (version, release) = r;
- var asset = release.Assets.FirstOrDefault(a => a.Name == "Wabbajack.exe");
- return asset != default ? (version, release, asset) : default;
- })
- .FirstOrDefault();
+ var launcherFolder = KnownFolders.EntryPoint.Parent;
+ var exePath = launcherFolder.Combine("Wabbajack.exe");
- var launcherFolder = KnownFolders.EntryPoint.Parent;
- var exePath = launcherFolder.Combine("Wabbajack.exe");
+ var launcherVersion = FileVersionInfo.GetVersionInfo(exePath.ToString());
- var launcherVersion = FileVersionInfo.GetVersionInfo(exePath.ToString());
+ if (release != default && release.version > Version.Parse(launcherVersion.FileVersion!))
+ {
+ _logger.LogInformation("Updating Launcher from {OldVersion} to {NewVersion}", launcherVersion.FileVersion, release.version);
+ var tempPath = launcherFolder.Combine("Wabbajack.exe.temp");
- if (release != default && release.version > Version.Parse(launcherVersion.FileVersion!))
+ await _downloader.Download(new Archive
{
- _logger.LogInformation("Updating Launcher from {OldVersion} to {NewVersion}", launcherVersion.FileVersion, release.version);
- var tempPath = launcherFolder.Combine("Wabbajack.exe.temp");
-
- await _downloader.Download(new Archive
- {
- State = new Http {Url = release.asset.BrowserDownloadUrl!},
- Name = release.asset.Name,
- Size = release.asset.Size
- }, tempPath, CancellationToken.None);
-
- if (tempPath.Size() != release.asset.Size)
- {
- _logger.LogInformation(
- "Downloaded launcher did not match expected size: {DownloadedSize} expected {ExpectedSize}", tempPath.Size(), release.asset.Size);
- return;
- }
-
- if (exePath.FileExists())
- exePath.Delete();
- await tempPath.MoveToAsync(exePath, true, CancellationToken.None);
-
- _logger.LogInformation("Finished updating wabbajack");
- await _wjclient.SendMetric("updated_launcher", $"{launcherVersion.FileVersion} -> {release.version}");
+ State = new Http {Url = release.asset.BrowserDownloadUrl!},
+ Name = release.asset.Name,
+ Size = release.asset.Size
+ }, tempPath, CancellationToken.None);
+
+ if (tempPath.Size() != release.asset.Size)
+ {
+ _logger.LogInformation(
+ "Downloaded launcher did not match expected size: {DownloadedSize} expected {ExpectedSize}", tempPath.Size(), release.asset.Size);
+ return;
}
- }
- private async Task GetReleases()
- {
- _logger.LogInformation("Getting new Wabbajack version list");
- var msg = MakeMessage(GITHUB_REPO_RELEASES);
- return await _client.GetJsonFromSendAsync(msg, _dtos.Options);
- }
+ if (exePath.FileExists())
+ exePath.Delete();
+ await tempPath.MoveToAsync(exePath, true, CancellationToken.None);
- private HttpRequestMessage MakeMessage(Uri uri)
- {
- var msg = new HttpRequestMessage(HttpMethod.Get, uri);
- msg.AddChromeAgent();
- return msg;
+ _logger.LogInformation("Finished updating wabbajack");
+ await _wjclient.SendMetric("updated_launcher", $"{launcherVersion.FileVersion} -> {release.version}");
}
+ }
+ private async Task GetReleases()
+ {
+ _logger.LogInformation("Getting new Wabbajack version list");
+ var msg = MakeMessage(GITHUB_REPO_RELEASES);
+ return await _client.GetJsonFromSendAsync(msg, _dtos.Options);
+ }
- class Release
- {
- [JsonProperty("tag_name")] public string Tag { get; set; } = "";
+ private HttpRequestMessage MakeMessage(Uri uri)
+ {
+ var msg = new HttpRequestMessage(HttpMethod.Get, uri);
+ msg.AddChromeAgent();
+ return msg;
+ }
- [JsonProperty("assets")] public Asset[] Assets { get; set; } = Array.Empty();
- }
+ class Release
+ {
+ [JsonProperty("tag_name")] public string Tag { get; set; } = "";
- class Asset
- {
- [JsonProperty("browser_download_url")]
- public Uri? BrowserDownloadUrl { get; set; }
+ [JsonProperty("assets")] public Asset[] Assets { get; set; } = Array.Empty();
- [JsonProperty("name")] public string Name { get; set; } = "";
+ }
- [JsonProperty("size")] public long Size { get; set; } = 0;
- }
+ class Asset
+ {
+ [JsonProperty("browser_download_url")]
+ public Uri? BrowserDownloadUrl { get; set; }
+
+ [JsonProperty("name")] public string Name { get; set; } = "";
+
+ [JsonProperty("size")] public long Size { get; set; } = 0;
}
}
diff --git a/Wabbajack.App.Wpf/LoginManagers/INeedsLogin.cs b/Wabbajack.App.Wpf/LoginManagers/INeedsLogin.cs
index aaed7797f..05b2b77c4 100644
--- a/Wabbajack.App.Wpf/LoginManagers/INeedsLogin.cs
+++ b/Wabbajack.App.Wpf/LoginManagers/INeedsLogin.cs
@@ -1,9 +1,6 @@
-
using System;
-using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
-using ReactiveUI;
using Wabbajack.Downloaders.Interfaces;
namespace Wabbajack.LoginManagers;
@@ -13,12 +10,13 @@ public interface INeedsLogin
string SiteName { get; }
ICommand TriggerLogin { get; set; }
ICommand ClearLogin { get; set; }
+ ICommand ToggleLogin { get; set; }
ImageSource Icon { get; set; }
Type LoginFor();
+ public bool LoggedIn { get; set; }
}
public interface ILoginFor : INeedsLogin
where T : IDownloader
{
-
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs
index 8e982af75..4cdd8bbe9 100644
--- a/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs
+++ b/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs
@@ -1,13 +1,8 @@
using System;
-using System.Drawing;
using System.Reactive.Linq;
-using System.Reflection;
-using System.Threading.Tasks;
-using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
-using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ReactiveUI;
@@ -30,6 +25,7 @@ public class LoversLabLoginManager : ViewModel, ILoginFor
public string SiteName { get; } = "Lovers Lab";
public ICommand TriggerLogin { get; set; }
public ICommand ClearLogin { get; set; }
+ public ICommand ToggleLogin { get; set; }
public ImageSource Icon { get; set; }
public Type LoginFor()
@@ -38,7 +34,7 @@ public Type LoginFor()
}
[Reactive]
- public bool HaveLogin { get; set; }
+ public bool LoggedIn { get; set; }
public LoversLabLoginManager(ILogger logger, ITokenProvider token, IServiceProvider serviceProvider)
{
@@ -52,7 +48,7 @@ public LoversLabLoginManager(ILogger logger, ITokenProvid
_logger.LogInformation("Deleting Login information for {SiteName}", SiteName);
await _token.Delete();
RefreshTokenState();
- }, this.WhenAnyValue(v => v.HaveLogin));
+ }, this.WhenAnyValue(v => v.LoggedIn));
Icon = BitmapFrame.Create(
typeof(LoversLabLoginManager).Assembly.GetManifestResourceStream("Wabbajack.App.Wpf.LoginManagers.Icons.lovers_lab.png")!);
@@ -61,20 +57,24 @@ public LoversLabLoginManager(ILogger logger, ITokenProvid
{
_logger.LogInformation("Logging into {SiteName}", SiteName);
StartLogin();
- }, this.WhenAnyValue(v => v.HaveLogin).Select(v => !v));
+ }, this.WhenAnyValue(v => v.LoggedIn).Select(v => !v));
+
+ ToggleLogin = ReactiveCommand.Create(() =>
+ {
+ if (LoggedIn) ClearLogin.Execute(null);
+ else TriggerLogin.Execute(null);
+ });
}
private void StartLogin()
{
- var view = new BrowserWindow(_serviceProvider);
- view.Closed += (sender, args) => { RefreshTokenState(); };
- var provider = _serviceProvider.GetRequiredService();
- view.DataContext = provider;
- view.Show();
+ var handler = _serviceProvider.GetRequiredService();
+ handler.Closed += (sender, args) => { RefreshTokenState(); };
+ ShowBrowserWindow.Send(handler);
}
private void RefreshTokenState()
{
- HaveLogin = _token.HaveToken();
+ LoggedIn = _token.HaveToken();
}
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
index 27ff83543..d1209a652 100644
--- a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
+++ b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
+using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -10,6 +11,7 @@
using ReactiveUI.Fody.Helpers;
using Wabbajack.Downloaders;
using Wabbajack.DTOs.Logins;
+using Wabbajack.Messages;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.UserIntervention;
@@ -24,6 +26,7 @@ public class NexusLoginManager : ViewModel, ILoginFor
public string SiteName { get; } = "Nexus Mods";
public ICommand TriggerLogin { get; set; }
public ICommand ClearLogin { get; set; }
+ public ICommand ToggleLogin { get; set; }
public ImageSource Icon { get; set; }
public Type LoginFor()
@@ -32,30 +35,34 @@ public Type LoginFor()
}
[Reactive]
- public bool HaveLogin { get; set; }
+ public bool LoggedIn { get; set; }
public NexusLoginManager(ILogger logger, ITokenProvider token, IServiceProvider serviceProvider)
{
_logger = logger;
_token = token;
_serviceProvider = serviceProvider;
- Task.Run(async () => await RefreshTokenState());
+ Task.Run(RefreshTokenState);
ClearLogin = ReactiveCommand.CreateFromTask(async () =>
{
_logger.LogInformation("Deleting Login information for {SiteName}", SiteName);
await ClearLoginToken();
- }, this.WhenAnyValue(v => v.HaveLogin));
+ }, this.WhenAnyValue(v => v.LoggedIn));
- Icon = BitmapFrame.Create(
- typeof(NexusLoginManager).Assembly.GetManifestResourceStream("Wabbajack.App.Wpf.LoginManagers.Icons.nexus.png")!);
+ Icon = (DrawingImage)Application.Current.Resources["NexusLogo"];
TriggerLogin = ReactiveCommand.CreateFromTask(async () =>
{
_logger.LogInformation("Logging into {SiteName}", SiteName);
- //MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService()));
StartLogin();
- }, this.WhenAnyValue(v => v.HaveLogin).Select(v => !v));
+ }, this.WhenAnyValue(v => v.LoggedIn).Select(v => !v));
+
+ ToggleLogin = ReactiveCommand.Create(() =>
+ {
+ if (LoggedIn) ClearLogin.Execute(null);
+ else TriggerLogin.Execute(null);
+ });
}
private async Task ClearLoginToken()
@@ -66,17 +73,23 @@ private async Task ClearLoginToken()
private void StartLogin()
{
- var view = new BrowserWindow(_serviceProvider);
- view.Closed += async (sender, args) => { await RefreshTokenState(); };
- var provider = _serviceProvider.GetRequiredService();
- view.DataContext = provider;
- view.Show();
+ var handler = _serviceProvider.GetRequiredService();
+ handler.Closed += async (_, _) => await RefreshTokenState();
+ ShowBrowserWindow.Send(handler);
}
private async Task RefreshTokenState()
{
- var token = await _token.Get();
+ NexusOAuthState token = null;
+ try
+ {
+ token = await _token.Get();
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError("Failed to refresh Nexus token state: {ex}", ex.ToString());
+ }
- HaveLogin = _token.HaveToken() && !(token?.OAuth?.IsExpired ?? true);
+ LoggedIn = _token.HaveToken() && !(token?.OAuth?.IsExpired ?? true);
}
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs
index 62a13e260..7f43150db 100644
--- a/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs
+++ b/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs
@@ -1,9 +1,5 @@
using System;
-using System.Drawing;
using System.Reactive.Linq;
-using System.Reflection;
-using System.Threading.Tasks;
-using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -29,6 +25,7 @@ public class VectorPlexusLoginManager : ViewModel, ILoginFor logger, ITokenProvider token, IServiceProvider serviceProvider)
{
@@ -51,7 +48,7 @@ public VectorPlexusLoginManager(ILogger logger, IToken
_logger.LogInformation("Deleting Login information for {SiteName}", SiteName);
await _token.Delete();
RefreshTokenState();
- }, this.WhenAnyValue(v => v.HaveLogin));
+ }, this.WhenAnyValue(v => v.LoggedIn));
Icon = BitmapFrame.Create(
typeof(VectorPlexusLoginManager).Assembly.GetManifestResourceStream("Wabbajack.App.Wpf.LoginManagers.Icons.vector_plexus.png")!);
@@ -60,22 +57,26 @@ public VectorPlexusLoginManager(ILogger logger, IToken
{
_logger.LogInformation("Logging into {SiteName}", SiteName);
StartLogin();
- }, this.WhenAnyValue(v => v.HaveLogin).Select(v => !v));
+ }, this.WhenAnyValue(v => v.LoggedIn).Select(v => !v));
+
+ ToggleLogin = ReactiveCommand.Create(() =>
+ {
+ if (LoggedIn) ClearLogin.Execute(null);
+ else TriggerLogin.Execute(null);
+ });
}
private void StartLogin()
{
- var view = new BrowserWindow(_serviceProvider);
- view.Closed += (sender, args) => { RefreshTokenState(); };
- var provider = _serviceProvider.GetRequiredService();
- view.DataContext = provider;
- view.Show();
+ var browserView = _serviceProvider.GetRequiredService();
+ browserView.ViewModel.Closed += (_, _) => RefreshTokenState();
+ ShowBrowserWindow.Send(_serviceProvider.GetRequiredService());
}
private void RefreshTokenState()
{
- HaveLogin = _token.HaveToken();
+ LoggedIn = _token.HaveToken();
}
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/MarkupExtensions/EnumMarkupConverter.cs b/Wabbajack.App.Wpf/MarkupExtensions/EnumMarkupConverter.cs
new file mode 100644
index 000000000..f9514f994
--- /dev/null
+++ b/Wabbajack.App.Wpf/MarkupExtensions/EnumMarkupConverter.cs
@@ -0,0 +1,44 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Windows.Markup;
+
+namespace Wabbajack;
+
+public class EnumToItemsSource : MarkupExtension
+{
+ private readonly Type _type;
+
+ public EnumToItemsSource(Type type)
+ {
+ _type = type;
+ }
+ public static string GetEnumDescription(Enum value)
+ {
+ FieldInfo fi = value.GetType().GetField(value.ToString());
+
+ DescriptionAttribute[] attributes = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
+
+ if (attributes != null && attributes.Any())
+ {
+ return attributes.First().Description;
+ }
+
+ return value.ToString();
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ return Enum.GetValues(_type)
+ .Cast()
+ .Select(e =>
+ {
+ return new
+ {
+ Value = e,
+ DisplayName = GetEnumDescription((Enum)e)
+ };
+ });
+ }
+}
diff --git a/Wabbajack.App.Wpf/Messages/ALoginMessage.cs b/Wabbajack.App.Wpf/Messages/ALoginMessage.cs
index 921cf97ba..5ce947184 100644
--- a/Wabbajack.App.Wpf/Messages/ALoginMessage.cs
+++ b/Wabbajack.App.Wpf/Messages/ALoginMessage.cs
@@ -1,7 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using ReactiveUI;
using Wabbajack.DTOs.Interventions;
namespace Wabbajack.Messages;
diff --git a/Wabbajack.App.Wpf/Messages/HideNavigation.cs b/Wabbajack.App.Wpf/Messages/HideNavigation.cs
new file mode 100644
index 000000000..b96bf8a6b
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/HideNavigation.cs
@@ -0,0 +1,16 @@
+using ReactiveUI;
+using Wabbajack.Compiler;
+
+namespace Wabbajack.Messages;
+
+public class HideNavigation
+{
+ public HideNavigation()
+ {
+ }
+
+ public static void Send()
+ {
+ MessageBus.Current.SendMessage(new HideNavigation());
+ }
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Messages/LoadCompilerSettings.cs b/Wabbajack.App.Wpf/Messages/LoadCompilerSettings.cs
new file mode 100644
index 000000000..b255f85e7
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/LoadCompilerSettings.cs
@@ -0,0 +1,18 @@
+using ReactiveUI;
+using Wabbajack.Compiler;
+
+namespace Wabbajack.Messages;
+
+public class LoadCompilerSettings
+{
+ public CompilerSettings CompilerSettings { get; set; }
+ public LoadCompilerSettings(CompilerSettings cs)
+ {
+ CompilerSettings = cs;
+ }
+
+ public static void Send(CompilerSettings cs)
+ {
+ MessageBus.Current.SendMessage(new LoadCompilerSettings(cs));
+ }
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Messages/LoadInfoScreen.cs b/Wabbajack.App.Wpf/Messages/LoadInfoScreen.cs
new file mode 100644
index 000000000..b59bd4d22
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/LoadInfoScreen.cs
@@ -0,0 +1,18 @@
+using ReactiveUI;
+
+namespace Wabbajack.Messages;
+public class LoadInfoScreen
+{
+ public string Info { get; set; }
+ public ViewModel NavigateBackTarget { get; set; }
+ public LoadInfoScreen(string info, ViewModel navigateBackTarget)
+ {
+ Info = info;
+ NavigateBackTarget = navigateBackTarget;
+ }
+ public static void Send(string info, ViewModel navigateBackTarget)
+ {
+ NavigateToGlobal.Send(ScreenType.Info);
+ MessageBus.Current.SendMessage(new LoadInfoScreen(info, navigateBackTarget));
+ }
+}
diff --git a/Wabbajack.App.Wpf/Messages/LoadLastLoadedModlist.cs b/Wabbajack.App.Wpf/Messages/LoadLastLoadedModlist.cs
index 5b2fcb42a..9aac4ceed 100644
--- a/Wabbajack.App.Wpf/Messages/LoadLastLoadedModlist.cs
+++ b/Wabbajack.App.Wpf/Messages/LoadLastLoadedModlist.cs
@@ -1,4 +1,3 @@
-
using ReactiveUI;
namespace Wabbajack.Messages;
diff --git a/Wabbajack.App.Wpf/Messages/LoadModlistForDetails.cs b/Wabbajack.App.Wpf/Messages/LoadModlistForDetails.cs
new file mode 100644
index 000000000..7b20340ee
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/LoadModlistForDetails.cs
@@ -0,0 +1,19 @@
+using ReactiveUI;
+using Wabbajack.DTOs;
+
+namespace Wabbajack.Messages;
+
+public class LoadModlistForDetails
+{
+ public BaseModListMetadataVM MetadataVM { get; }
+
+ public LoadModlistForDetails(BaseModListMetadataVM metadata)
+ {
+ MetadataVM = metadata;
+ }
+
+ public static void Send(BaseModListMetadataVM metadataVM)
+ {
+ MessageBus.Current.SendMessage(new LoadModlistForDetails(metadataVM));
+ }
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Messages/NavigateTo.cs b/Wabbajack.App.Wpf/Messages/NavigateTo.cs
index f9eea96f9..cd0e58905 100644
--- a/Wabbajack.App.Wpf/Messages/NavigateTo.cs
+++ b/Wabbajack.App.Wpf/Messages/NavigateTo.cs
@@ -1,5 +1,4 @@
using ReactiveUI;
-using Wabbajack;
namespace Wabbajack.Messages;
diff --git a/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs b/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs
index ca0bafe6f..636b71464 100644
--- a/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs
+++ b/Wabbajack.App.Wpf/Messages/NavigateToGlobal.cs
@@ -2,18 +2,21 @@
namespace Wabbajack.Messages;
+public enum ScreenType
+{
+ Home,
+ ModListGallery,
+ Installer,
+ Settings,
+ CompilerHome,
+ CompilerMain,
+ ModListDetails,
+ WebBrowser,
+ Info
+}
+
public class NavigateToGlobal
{
- public enum ScreenType
- {
- ModeSelectionView,
- ModListGallery,
- Installer,
- Settings,
- Compiler,
- ModListContents,
- WebBrowser
- }
public ScreenType Screen { get; }
diff --git a/Wabbajack.App.Wpf/Messages/ShowBrowserWindow.cs b/Wabbajack.App.Wpf/Messages/ShowBrowserWindow.cs
new file mode 100644
index 000000000..70f54556a
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/ShowBrowserWindow.cs
@@ -0,0 +1,16 @@
+using ReactiveUI;
+
+namespace Wabbajack.Messages;
+
+public class ShowBrowserWindow
+{
+ public BrowserWindowViewModel ViewModel { get; set; }
+ public ShowBrowserWindow(BrowserWindowViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ }
+ public static void Send(BrowserWindowViewModel viewModel)
+ {
+ MessageBus.Current.SendMessage(new ShowBrowserWindow(viewModel));
+ }
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Messages/ShowFloatingWindow.cs b/Wabbajack.App.Wpf/Messages/ShowFloatingWindow.cs
new file mode 100644
index 000000000..df35a39fa
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/ShowFloatingWindow.cs
@@ -0,0 +1,26 @@
+using ReactiveUI;
+
+namespace Wabbajack.Messages;
+
+public enum FloatingScreenType
+{
+ None,
+ ModListDetails,
+ FileUpload
+}
+
+public class ShowFloatingWindow
+{
+ public FloatingScreenType Screen { get; }
+
+ private ShowFloatingWindow(FloatingScreenType screen)
+ {
+ Screen = screen;
+ }
+
+ public static void Send(FloatingScreenType screen)
+ {
+ MessageBus.Current.SendMessage(new ShowFloatingWindow(screen));
+ }
+
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Messages/ShowNavigation.cs b/Wabbajack.App.Wpf/Messages/ShowNavigation.cs
new file mode 100644
index 000000000..df1148b4a
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/ShowNavigation.cs
@@ -0,0 +1,16 @@
+using ReactiveUI;
+using Wabbajack.Compiler;
+
+namespace Wabbajack.Messages;
+
+public class ShowNavigation
+{
+ public ShowNavigation()
+ {
+ }
+
+ public static void Send()
+ {
+ MessageBus.Current.SendMessage(new ShowNavigation());
+ }
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Messages/SpawnBrowserWindow.cs b/Wabbajack.App.Wpf/Messages/SpawnBrowserWindow.cs
deleted file mode 100644
index 840d54864..000000000
--- a/Wabbajack.App.Wpf/Messages/SpawnBrowserWindow.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Wabbajack.Messages;
-
-public record SpawnBrowserWindow (BrowserWindowViewModel Vm)
-{
-}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Models/LogStream.cs b/Wabbajack.App.Wpf/Models/LogStream.cs
index 5a997c017..44f05964a 100644
--- a/Wabbajack.App.Wpf/Models/LogStream.cs
+++ b/Wabbajack.App.Wpf/Models/LogStream.cs
@@ -1,18 +1,13 @@
using System;
using System.Collections.ObjectModel;
+using System.Globalization;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
-using System.Text;
-using System.Windows.Data;
using DynamicData;
-using DynamicData.Binding;
-using Microsoft.Extensions.Logging;
using NLog;
using NLog.Targets;
using ReactiveUI;
-using Wabbajack.Extensions;
-using LogLevel = NLog.LogLevel;
namespace Wabbajack.Models;
@@ -66,8 +61,9 @@ public interface ILogMessage
long MessageId { get; }
string ShortMessage { get; }
- DateTime TimeStamp { get; }
string LongMessage { get; }
+ DateTime TimeStamp { get; }
+ LogLevel Level { get; }
}
private record LogMessage(LogEventInfo info) : ILogMessage
@@ -75,7 +71,8 @@ private record LogMessage(LogEventInfo info) : ILogMessage
public long MessageId => info.SequenceID;
public string ShortMessage => info.FormattedMessage;
public DateTime TimeStamp => info.TimeStamp;
- public string LongMessage => info.FormattedMessage;
+ public LogLevel Level => info.Level;
+ public string LongMessage => $"[{TimeStamp.ToString("HH:mm:ss")} {info.Level.ToString().ToUpper()}] {info.FormattedMessage}";
}
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Models/ResourceMonitor.cs b/Wabbajack.App.Wpf/Models/ResourceMonitor.cs
index 8b7bf8831..591c93916 100644
--- a/Wabbajack.App.Wpf/Models/ResourceMonitor.cs
+++ b/Wabbajack.App.Wpf/Models/ResourceMonitor.cs
@@ -14,11 +14,11 @@ namespace Wabbajack.Models;
public class ResourceMonitor : IDisposable
{
- private readonly TimeSpan _pollInterval = TimeSpan.FromMilliseconds(250);
+ private readonly TimeSpan _pollInterval = TimeSpan.FromMilliseconds(1000);
private readonly IResource[] _resources;
- private readonly Subject<(string Name, long Througput)[]> _updates = new ();
+ private readonly Subject<(string Name, long Throughput)[]> _updates = new ();
private (string Name, long Throughput)[] _prev;
public IObservable<(string Name, long Throughput)[]> Updates => _updates;
@@ -27,18 +27,17 @@ public class ResourceMonitor : IDisposable
public readonly ReadOnlyObservableCollection _tasksFiltered;
private readonly CompositeDisposable _compositeDisposable;
private readonly ILogger _logger;
+ private DateTime _lastMeasuredDateTime;
public ReadOnlyObservableCollection Tasks => _tasksFiltered;
-
-
-
public ResourceMonitor(ILogger logger, IEnumerable resources)
{
_logger = logger;
_compositeDisposable = new CompositeDisposable();
_resources = resources.ToArray();
+ _lastMeasuredDateTime = DateTime.Now;
_prev = _resources.Select(x => (x.Name, (long)0)).ToArray();
-
+
RxApp.MainThreadScheduler.ScheduleRecurringAction(_pollInterval, Elapsed)
.DisposeWith(_compositeDisposable);
@@ -51,9 +50,10 @@ public ResourceMonitor(ILogger logger, IEnumerable r
private void Elapsed()
{
+ var elapsedTime = DateTime.Now - _lastMeasuredDateTime;
var current = _resources.Select(x => (x.Name, x.StatusReport.Transferred)).ToArray();
var diff = _prev.Zip(current)
- .Select(t => (t.First.Name, (long)((t.Second.Transferred - t.First.Throughput) / _pollInterval.TotalSeconds)))
+ .Select(t => (t.First.Name, (long)((t.Second.Transferred - t.First.Throughput) / elapsedTime.TotalSeconds)))
.ToArray();
_prev = current;
_updates.OnNext(diff);
@@ -61,18 +61,20 @@ private void Elapsed()
_tasks.Edit(l =>
{
var used = new HashSet();
+ var now = DateTime.Now;
foreach (var resource in _resources)
{
foreach (var job in resource.Jobs.Where(j => j.Current > 0))
{
used.Add(job.ID);
var tsk = l.Lookup(job.ID);
+ var jobProgress = job.Size == 0 ? Percent.Zero : Percent.FactoryPutInRange(job.Current, (long)job.Size);
// Update
if (tsk != Optional.None)
{
var t = tsk.Value;
t.Msg = job.Description;
- t.ProgressPercent = job.Size == 0 ? Percent.Zero : Percent.FactoryPutInRange(job.Current, (long)job.Size);
+ t.ProgressPercent = jobProgress;
t.IsWorking = job.Current > 0;
}
@@ -82,9 +84,9 @@ private void Elapsed()
var vm = new CPUDisplayVM
{
ID = job.ID,
- StartTime = DateTime.Now,
+ StartTime = now,
Msg = job.Description,
- ProgressPercent = job.Size == 0 ? Percent.Zero : Percent.FactoryPutInRange(job.Current, (long) job.Size),
+ ProgressPercent = jobProgress,
IsWorking = job.Current > 0,
};
l.AddOrUpdate(vm);
@@ -96,6 +98,7 @@ private void Elapsed()
foreach (var itm in l.Items.Where(v => !used.Contains(v.ID)))
l.Remove(itm);
});
+ _lastMeasuredDateTime = DateTime.Now;
}
public void Dispose()
diff --git a/Wabbajack.App.Wpf/Resources/Fonts/Gabarito-VariableFont_wght-BF651cdf1f55e6c.ttf b/Wabbajack.App.Wpf/Resources/Fonts/Gabarito-VariableFont_wght-BF651cdf1f55e6c.ttf
new file mode 100644
index 000000000..81d33a6b6
Binary files /dev/null and b/Wabbajack.App.Wpf/Resources/Fonts/Gabarito-VariableFont_wght-BF651cdf1f55e6c.ttf differ
diff --git a/Wabbajack.App.Wpf/Resources/libwebp_x64.dll b/Wabbajack.App.Wpf/Resources/libwebp_x64.dll
new file mode 100644
index 000000000..0b2bd2c13
Binary files /dev/null and b/Wabbajack.App.Wpf/Resources/libwebp_x64.dll differ
diff --git a/Wabbajack.App.Wpf/Resources/libwebp_x86.dll b/Wabbajack.App.Wpf/Resources/libwebp_x86.dll
new file mode 100644
index 000000000..62094675e
Binary files /dev/null and b/Wabbajack.App.Wpf/Resources/libwebp_x86.dll differ
diff --git a/Wabbajack.App.Wpf/Settings.cs b/Wabbajack.App.Wpf/Settings.cs
index 629500d6c..d5825acd7 100644
--- a/Wabbajack.App.Wpf/Settings.cs
+++ b/Wabbajack.App.Wpf/Settings.cs
@@ -1,28 +1,107 @@
-using Wabbajack.Downloaders;
+using DynamicData;
+using DynamicData.Binding;
+using ReactiveUI;
+using ReactiveUI.Fody.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Wabbajack.Downloaders;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Paths;
using Wabbajack.RateLimiter;
+using Wabbajack.Services.OSIntegrated;
using Wabbajack.Util;
-namespace Wabbajack
+namespace Wabbajack;
+
+[JsonName("Mo2ModListInstallerSettings")]
+public class Mo2ModlistInstallationSettings
{
- [JsonName("Mo2ModListInstallerSettings")]
- public class Mo2ModlistInstallationSettings
- {
- public AbsolutePath InstallationLocation { get; set; }
- public AbsolutePath DownloadLocation { get; set; }
- public bool AutomaticallyOverrideExistingInstall { get; set; }
+ public AbsolutePath InstallationLocation { get; set; }
+ public AbsolutePath DownloadLocation { get; set; }
+ public bool AutomaticallyOverrideExistingInstall { get; set; }
+}
+
+public class PerformanceSettingVM : ViewModel
+{
+ private readonly ResourceSettingsManager _manager;
+ [Reactive] public string HumanName { get; set; }
+ [Reactive] public long MaxTasks { get; set; }
+ [Reactive] public long MaxThroughput { get; set; }
+ public PerformanceSettingVM(ResourceSettingsManager manager) {
+ _manager = manager;
+
+ this.WhenActivated(disposables =>
+ {
+ this.WhenAnyValue(x => x.MaxTasks, x => x.MaxThroughput)
+ .Throttle(TimeSpan.FromSeconds(0.5))
+ .Subscribe(async mt =>
+ {
+ var setting = new ResourceSettingsManager.ResourceSetting()
+ {
+ MaxTasks = mt.Item1,
+ MaxThroughput = mt.Item2
+ };
+ await manager.SetSetting(HumanName, setting);
+ })
+ .DisposeWith(disposables);
+ });
}
+}
+
+public class PerformanceSettingsVM : ViewModel
+{
- public class PerformanceSettings : ViewModel
+ private readonly ResourceSettingsManager _settingsManager;
+
+ public SourceList _settings = new();
+ public ReadOnlyObservableCollection Settings;
+ [Reactive] public int MaxThreads { get; set; }
+
+ public PerformanceSettingsVM(IResource downloadResources, SystemParametersConstructor systemParams, ResourceSettingsManager manager)
{
- private readonly Configuration.MainSettings _settings;
+ var p = systemParams.Create();
+
+ _settingsManager = manager;
+ MaxThreads = Environment.ProcessorCount;
- public PerformanceSettings(Configuration.MainSettings settings, IResource downloadResources, SystemParametersConstructor systemParams)
+ this.WhenActivated(async disposables =>
{
- var p = systemParams.Create();
+ var settings = (await _settingsManager.GetSettings()).Select((kv) =>
+ {
+ return new PerformanceSettingVM(manager)
+ {
+ HumanName = kv.Key,
+ MaxTasks = kv.Value.MaxTasks,
+ MaxThroughput = kv.Value.MaxThroughput
+ };
+ });
+
+ _settings.Edit(s =>
+ {
+ s.Clear();
+ s.AddRange(settings);
+ });
+
+ _settings.Connect()
+ .Bind(out Settings)
+ .Subscribe()
+ .DisposeWith(disposables);
- _settings = settings;
- }
+
+ });
}
+
+}
+public class GalleryFilterSettings
+{
+ public string GameType { get; set; }
+ public bool IncludeNSFW { get; set; }
+ public bool IncludeUnofficial { get; set; }
+ public bool OnlyInstalled { get; set; }
+ public string Search { get; set; }
}
diff --git a/Wabbajack.App.Wpf/StatusMessages/CriticalFailureIntervention.cs b/Wabbajack.App.Wpf/StatusMessages/CriticalFailureIntervention.cs
index 618776efa..97f4254f6 100644
--- a/Wabbajack.App.Wpf/StatusMessages/CriticalFailureIntervention.cs
+++ b/Wabbajack.App.Wpf/StatusMessages/CriticalFailureIntervention.cs
@@ -1,5 +1,4 @@
using System.Threading.Tasks;
-using Wabbajack.Common;
using Wabbajack.Interventions;
namespace Wabbajack
diff --git a/Wabbajack.App.Wpf/StatusMessages/YesNoIntervention.cs b/Wabbajack.App.Wpf/StatusMessages/YesNoIntervention.cs
index a8e59eb6b..ba523ff2d 100644
--- a/Wabbajack.App.Wpf/StatusMessages/YesNoIntervention.cs
+++ b/Wabbajack.App.Wpf/StatusMessages/YesNoIntervention.cs
@@ -1,15 +1,12 @@
-using Wabbajack.Common;
+namespace Wabbajack;
-namespace Wabbajack
+public class YesNoIntervention : ConfirmationIntervention
{
- public class YesNoIntervention : ConfirmationIntervention
+ public YesNoIntervention(string description, string title)
{
- public YesNoIntervention(string description, string title)
- {
- ExtendedDescription = description;
- ShortDescription = title;
- }
- public override string ShortDescription { get; }
- public override string ExtendedDescription { get; }
+ ExtendedDescription = description;
+ ShortDescription = title;
}
+ public override string ShortDescription { get; }
+ public override string ExtendedDescription { get; }
}
diff --git a/Wabbajack.App.Wpf/Themes/Styles.xaml b/Wabbajack.App.Wpf/Themes/Styles.xaml
index 88495b482..9f90935a3 100644
--- a/Wabbajack.App.Wpf/Themes/Styles.xaml
+++ b/Wabbajack.App.Wpf/Themes/Styles.xaml
@@ -8,8 +8,14 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
+ xmlns:wj="clr-namespace:Wabbajack"
+ xmlns:ic="clr-namespace:FluentIcons.Wpf;assembly=FluentIcons.Wpf"
+ xmlns:generic="http://schemas.sdl.com/xaml"
+ xmlns:math="http://hexinnovation.com/math" xmlns:controls="http://schemas.sdl.com/xaml"
mc:Ignorable="d">
+ pack://application:,,,/Resources/Fonts/#Gabarito
+
@@ -19,44 +25,63 @@
-
+
+
+
+
- #121212
- #222222
- #272727
- #424242
- #323232
+ #222531
+ #2A2B41
+ #3c3652
+ #4e4571
+ #4e4571
+ #222531
#424242
- #323232
- #666666
- #362675
+ #4e4571
+ #514c6b
- #EFEFEF
- #CCCCCC
+ #E5E5E8
+ #40E5E5E8
- #BDBDBD
+ #3b3c50
+
+ #D9BBF9
#525252
#ffc400
- #e83a40
- #52b545
+ #5e2c2b
+ #5fad56
#967400
- #BB86FC
- #00BB86FC
- #3700B3
+ #D8BAF8
+
+
+ #303141
+
+ #383750
+ #3f3c57
+ #46425F
+ #81739d
+ #2d2e45
+ #5f6071
+
+ #313146
+
+
+ #8866ad
+ #514c6b
#270080
#1b0059
- #03DAC6
- #0e8f83
+ #3C3652
+ #363952
#095952
#042421
#cef0ed
#8cede5
#00ffe7
- #C7FC86
- #8eb55e
- #4b6130
+ #4e4571
+ #3C3652
+ #2A2B41
#abf74d
#868CFC
#F686FC
@@ -64,15 +89,15 @@
#FCBB86
- #FF3700B3
+ #FF222531
- #CC868CFC
+ #CCD8BAF8
- #99868CFC
+ #99D8BAF8
- #66868CFC
+ #66D8BAF8
- #33868CFC
+ #33D8BAF8
+ Color="{StaticResource Primary}" />
+
+
+ 16
+ 12
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -137,6 +180,9 @@
+
+
+
@@ -146,42 +192,56 @@
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
+
+
-
+
@@ -191,13 +251,13 @@
-
-
-
-
+
+
+
+
-
-
+
+
@@ -209,16 +269,232 @@
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
M-0.7,5.2 L-2.2,6.7 3.6,12.6 9.5,6.7 8,5.2 3.6,9.6 z
M-2.2,10.9 L-0.7,12.4 3.7,8 8,12.4 9.5,10.9 3.7,5 z
M1.0E-41,4.2 L0,2.1 2.5,4.5 6.7,4.4E-47 6.7,2.3 2.5,6.7 z
@@ -231,24 +507,24 @@
M-0,6 L-0,8 8,8 8,-0 6,-0 6,6 z
M5,-0 L9,5 1,5 z
-
@@ -256,9 +532,20 @@
+
+
+
+ -->
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ x:Name="Border"
+ Background="{TemplateBinding Background}"
+ BorderBrush="{TemplateBinding BorderBrush}"
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="8">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -1299,7 +1712,7 @@
-
+
+
+
+
-
-
+
+
@@ -1333,33 +1755,113 @@
+
+
+
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -1889,14 +2400,14 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
- CornerRadius="6">
+ CornerRadius="4">
+ CornerRadius="4" />
+
+
+
-
-
+
-
-
@@ -3476,11 +4212,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs
index 9373e42a1..56613d7c5 100644
--- a/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs
@@ -1,17 +1,15 @@
+using System;
using System.Net.Http;
-using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.DTOs.Logins;
-using Wabbajack.Models;
-using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.UserIntervention;
-public class LoversLabLoginHandler : OAuth2LoginHandler
+public class LoversLabLoginHandler : OAuth2LoginHandler
{
- public LoversLabLoginHandler(ILogger logger, HttpClient httpClient, EncryptedJsonTokenProvider tokenProvider)
- : base(logger, httpClient, tokenProvider)
+ public LoversLabLoginHandler(ILogger logger, HttpClient httpClient, EncryptedJsonTokenProvider tokenProvider, IServiceProvider serviceProvider)
+ : base(logger, httpClient, tokenProvider, serviceProvider)
{
}
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/UserIntervention/ManualBlobDownloadHandler.cs b/Wabbajack.App.Wpf/UserIntervention/ManualBlobDownloadHandler.cs
index 2c99cc234..4f965b10a 100644
--- a/Wabbajack.App.Wpf/UserIntervention/ManualBlobDownloadHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/ManualBlobDownloadHandler.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.DTOs.DownloadStates;
@@ -9,6 +10,8 @@ public class ManualBlobDownloadHandler : BrowserWindowViewModel
{
public ManualBlobDownload Intervention { get; set; }
+ public ManualBlobDownloadHandler(IServiceProvider serviceProvider) : base(serviceProvider) { }
+
protected override async Task Run(CancellationToken token)
{
//await WaitForReady();
diff --git a/Wabbajack.App.Wpf/UserIntervention/ManualDownloadHandler.cs b/Wabbajack.App.Wpf/UserIntervention/ManualDownloadHandler.cs
index 2c21a3206..346d2251a 100644
--- a/Wabbajack.App.Wpf/UserIntervention/ManualDownloadHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/ManualDownloadHandler.cs
@@ -1,14 +1,17 @@
+using System;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.Interventions;
-namespace Wabbajack.UserIntervention;
+namespace Wabbajack;
public class ManualDownloadHandler : BrowserWindowViewModel
{
public ManualDownload Intervention { get; set; }
+ public ManualDownloadHandler(IServiceProvider serviceProvider) : base(serviceProvider) { }
+
protected override async Task Run(CancellationToken token)
{
//await WaitForReady();
diff --git a/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs
index 7bf069bf6..9e3644075 100644
--- a/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs
@@ -1,26 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Net;
using System.Net.Http;
-using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using System.Web;
-using Fizzler.Systems.HtmlAgilityPack;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Wabbajack.DTOs.Logins;
using Wabbajack.DTOs.OAuth;
-using Wabbajack.Messages;
-using Wabbajack.Models;
-using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Services.OSIntegrated;
-using Cookie = Wabbajack.DTOs.Logins.Cookie;
namespace Wabbajack.UserIntervention;
@@ -34,21 +26,13 @@ public class NexusLoginHandler : BrowserWindowViewModel
private readonly ILogger _logger;
private readonly HttpClient _client;
- public NexusLoginHandler(ILogger logger, HttpClient client, EncryptedJsonTokenProvider tokenProvider)
+ public NexusLoginHandler(ILogger logger, HttpClient client, EncryptedJsonTokenProvider tokenProvider, IServiceProvider serviceProvider) : base(serviceProvider)
{
_logger = logger;
_client = client;
HeaderText = "Nexus Login";
_tokenProvider = tokenProvider;
}
-
- private string Base64Id()
- {
- var bytes = new byte[32];
- using var rng = RandomNumberGenerator.Create();
- rng.GetBytes(bytes);
- return Convert.ToBase64String(bytes);
- }
protected override async Task Run(CancellationToken token)
{
@@ -69,7 +53,7 @@ protected override async Task Run(CancellationToken token)
await NavigateTo(new Uri("https://nexusmods.com"));
var codeCompletionSource = new TaskCompletionSource>();
- Browser!.Browser.CoreWebView2.NewWindowRequested += (sender, args) =>
+ Browser.CoreWebView2.NewWindowRequested += (sender, args) =>
{
var uri = new Uri(args.Uri);
_logger.LogInformation("New Window Requested {Uri}", args.Uri);
diff --git a/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs
index a54ac5449..d601c39b9 100644
--- a/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs
@@ -6,15 +6,9 @@
using System.Threading;
using System.Threading.Tasks;
using System.Web;
-using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
-using ReactiveUI;
using Wabbajack.Common;
-using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.Logins;
-using Wabbajack.Messages;
-using Wabbajack.Models;
-using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.UserIntervention;
@@ -27,7 +21,7 @@ public abstract class OAuth2LoginHandler : BrowserWindowViewModel
private readonly ILogger _logger;
public OAuth2LoginHandler(ILogger logger, HttpClient httpClient,
- EncryptedJsonTokenProvider tokenProvider)
+ EncryptedJsonTokenProvider tokenProvider, IServiceProvider serviceProvider) : base(serviceProvider)
{
var tlogin = new TLoginType();
HeaderText = $"{tlogin.SiteName} Login";
@@ -43,8 +37,8 @@ protected override async Task Run(CancellationToken token)
var tcs = new TaskCompletionSource();
await NavigateTo(tlogin.AuthorizationEndpoint);
- Browser!.Browser.CoreWebView2.Settings.UserAgent = "Wabbajack";
- Browser!.Browser.NavigationStarting += (sender, args) =>
+ Browser.CoreWebView2.Settings.UserAgent = "Wabbajack";
+ Browser.NavigationStarting += (sender, args) =>
{
var uri = new Uri(args.Uri);
if (uri.Scheme == "wabbajack")
diff --git a/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs
index b41e736cf..693fd4ffc 100644
--- a/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs
@@ -1,16 +1,15 @@
+using System;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Wabbajack.DTOs.Logins;
-using Wabbajack.Models;
-using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.UserIntervention;
-public class VectorPlexusLoginHandler : OAuth2LoginHandler
+public class VectorPlexusLoginHandler : OAuth2LoginHandler
{
- public VectorPlexusLoginHandler(ILogger logger, HttpClient httpClient, EncryptedJsonTokenProvider tokenProvider)
- : base(logger, httpClient, tokenProvider)
+ public VectorPlexusLoginHandler(ILogger logger, HttpClient httpClient, EncryptedJsonTokenProvider tokenProvider, IServiceProvider serviceProvider)
+ : base(logger, httpClient, tokenProvider, serviceProvider)
{
}
}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Util/AsyncLazy.cs b/Wabbajack.App.Wpf/Util/AsyncLazy.cs
index 69488c282..3a0a206a4 100644
--- a/Wabbajack.App.Wpf/Util/AsyncLazy.cs
+++ b/Wabbajack.App.Wpf/Util/AsyncLazy.cs
@@ -1,7 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using System.Threading.Tasks;
namespace Wabbajack
diff --git a/Wabbajack.App.Wpf/Util/DriveHelper.cs b/Wabbajack.App.Wpf/Util/DriveHelper.cs
new file mode 100644
index 000000000..53160ed0d
--- /dev/null
+++ b/Wabbajack.App.Wpf/Util/DriveHelper.cs
@@ -0,0 +1,413 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Management;
+
+namespace Wabbajack;
+public static class DriveHelper
+{
+ private static Dictionary _cachedDisks = new Dictionary();
+ private static Dictionary _cachedPartitions = new Dictionary();
+ private static DriveInfo[]? _cachedDrives = null;
+
+ ///
+ /// All the physical disks by disk number
+ ///
+ public static Dictionary PhysicalDisks
+ {
+ get
+ {
+ if (_cachedDisks.Count == 0)
+ _cachedDisks = GetPhysicalDisks();
+ return _cachedDisks;
+ }
+ }
+
+ ///
+ /// All the physical disks by partition (drive letter)
+ ///
+ public static Dictionary Partitions
+ {
+ get
+ {
+ if (_cachedPartitions.Count == 0)
+ _cachedPartitions = GetPartitions();
+ return _cachedPartitions;
+ }
+ }
+
+ public static DriveInfo[] Drives
+ {
+ get
+ {
+ if (_cachedDrives == null)
+ _cachedDrives = DriveInfo.GetDrives();
+ return _cachedDrives;
+ }
+ }
+
+ public static void ReloadPhysicalDisks()
+ {
+ if (_cachedDisks.Count > 0)
+ _cachedDisks.Clear();
+ _cachedDisks = GetPhysicalDisks();
+ }
+
+ public static MediaType GetMediaTypeForPath(string path)
+ {
+ var root = Path.GetPathRoot(path);
+ if (string.IsNullOrEmpty(root)) return MediaType.Unspecified;
+ return Partitions[root[0]].MediaType;
+ }
+
+ public static DriveInfo? GetPreferredInstallationDrive(long modlistSize)
+ {
+ return DriveInfo.GetDrives()
+ .Where(d => d.IsReady && d.DriveType == DriveType.Fixed)
+ .OrderByDescending(d => d.AvailableFreeSpace > modlistSize)
+ .ThenByDescending(d => Partitions[d.RootDirectory.Name[0]].MediaType == MediaType.SSD)
+ .ThenByDescending(d => d.AvailableFreeSpace)
+ .FirstOrDefault();
+ }
+
+ [DebuggerHidden]
+ private static Dictionary GetPhysicalDisks()
+ {
+ try
+ {
+ var disks = new Dictionary();
+ var scope = new ManagementScope(@"\\localhost\ROOT\Microsoft\Windows\Storage");
+ var query = new ObjectQuery("SELECT * FROM MSFT_PhysicalDisk");
+ using var searcher = new ManagementObjectSearcher(scope, query);
+ var dObj = searcher.Get();
+ foreach (ManagementObject diskobj in dObj)
+ {
+ var dis = new PhysicalDisk();
+ try
+ {
+ dis.SupportedUsages = (ushort[])diskobj["SupportedUsages"];
+ }
+ catch (Exception)
+ {
+ dis.SupportedUsages = null;
+ }
+ try
+ {
+ dis.CannotPoolReason = (ushort[])diskobj["CannotPoolReason"];
+ }
+ catch (Exception)
+ {
+ dis.CannotPoolReason = null;
+ }
+ try
+ {
+ dis.OperationalStatus = (ushort[])diskobj["OperationalStatus"];
+ }
+ catch (Exception)
+ {
+ dis.OperationalStatus = null;
+ }
+ try
+ {
+ dis.OperationalDetails = (string[])diskobj["OperationalDetails"];
+ }
+ catch (Exception)
+ {
+ dis.OperationalDetails = null;
+ }
+ try
+ {
+ dis.UniqueIdFormat = (ushort)diskobj["UniqueIdFormat"];
+ }
+ catch (Exception)
+ {
+ dis.UniqueIdFormat = 0;
+ }
+ try
+ {
+ dis.DeviceId = diskobj["DeviceId"].ToString();
+ }
+ catch (Exception)
+ {
+ dis.DeviceId = "NA";
+ }
+ try
+ {
+ dis.FriendlyName = (string)diskobj["FriendlyName"];
+ }
+ catch (Exception)
+ {
+ dis.FriendlyName = "?";
+ }
+ try
+ {
+ dis.HealthStatus = (ushort)diskobj["HealthStatus"];
+ }
+ catch (Exception)
+ {
+ dis.HealthStatus = 0;
+ }
+ try
+ {
+ dis.PhysicalLocation = (string)diskobj["PhysicalLocation"];
+ }
+ catch (Exception)
+ {
+ dis.PhysicalLocation = "?";
+ }
+ try
+ {
+ dis.VirtualDiskFootprint = (ushort)diskobj["VirtualDiskFootprint"];
+ }
+ catch (Exception)
+ {
+ dis.VirtualDiskFootprint = 0;
+ }
+ try
+ {
+ dis.Usage = (ushort)diskobj["Usage"];
+ }
+ catch (Exception)
+ {
+ dis.Usage = 0;
+ }
+ try
+ {
+ dis.Description = (string)diskobj["Description"];
+ }
+ catch (Exception)
+ {
+ dis.Description = "?";
+ }
+ try
+ {
+ dis.PartNumber = (string)diskobj["PartNumber"];
+ }
+ catch (Exception)
+ {
+ dis.PartNumber = "?";
+ }
+ try
+ {
+ dis.FirmwareVersion = (string)diskobj["FirmwareVersion"];
+ }
+ catch (Exception)
+ {
+ dis.FirmwareVersion = "?";
+ }
+ try
+ {
+ dis.SoftwareVersion = (string)diskobj["SoftwareVersion"];
+ }
+ catch (Exception)
+ {
+ dis.SoftwareVersion = "?";
+ }
+ try
+ {
+ dis.Size = (ulong)diskobj["SoftwareVersion"];
+ }
+ catch (Exception)
+ {
+ dis.Size = 0;
+ }
+ try
+ {
+ dis.AllocatedSize = (ulong)diskobj["AllocatedSize"];
+ }
+ catch (Exception)
+ {
+ dis.AllocatedSize = 0;
+ }
+ try
+ {
+ dis.BusType = (ushort)diskobj["BusType"];
+ }
+ catch (Exception)
+ {
+ dis.BusType = 0;
+ }
+ try
+ {
+ dis.IsWriteCacheEnabled = (bool)diskobj["IsWriteCacheEnabled"];
+ }
+ catch (Exception)
+ {
+ dis.IsWriteCacheEnabled = false;
+ }
+ try
+ {
+ dis.IsPowerProtected = (bool)diskobj["IsPowerProtected"];
+ }
+ catch (Exception)
+ {
+ dis.IsPowerProtected = false;
+ }
+ try
+ {
+ dis.PhysicalSectorSize = (ulong)diskobj["PhysicalSectorSize"];
+ }
+ catch (Exception)
+ {
+ dis.PhysicalSectorSize = 0;
+ }
+ try
+ {
+ dis.LogicalSectorSize = (ulong)diskobj["LogicalSectorSize"];
+ }
+ catch (Exception)
+ {
+ dis.LogicalSectorSize = 0;
+ }
+ try
+ {
+ dis.SpindleSpeed = (uint)diskobj["SpindleSpeed"];
+ }
+ catch (Exception)
+ {
+ dis.SpindleSpeed = 0;
+ }
+ try
+ {
+ dis.IsIndicationEnabled = (bool)diskobj["IsIndicationEnabled"];
+ }
+ catch (Exception)
+ {
+ dis.IsIndicationEnabled = false;
+ }
+ try
+ {
+ dis.EnclosureNumber = (ushort)diskobj["EnclosureNumber"];
+ }
+ catch (Exception)
+ {
+ dis.EnclosureNumber = 0;
+ }
+ try
+ {
+ dis.SlotNumber = (ushort)diskobj["SlotNumber"];
+ }
+ catch (Exception)
+ {
+ dis.SlotNumber = 0;
+ }
+ try
+ {
+ dis.CanPool = (bool)diskobj["CanPool"];
+ }
+ catch (Exception)
+ {
+ dis.CanPool = false;
+ }
+ try
+ {
+ dis.OtherCannotPoolReasonDescription = (string)diskobj["OtherCannotPoolReasonDescription"];
+ }
+ catch (Exception)
+ {
+ dis.OtherCannotPoolReasonDescription = "?";
+ }
+ try
+ {
+ dis.IsPartial = (bool)diskobj["IsPartial"];
+ }
+ catch (Exception)
+ {
+ dis.IsPartial = false;
+ }
+ try
+ {
+ dis.MediaType = (MediaType)diskobj["MediaType"];
+ }
+ catch (Exception)
+ {
+ dis.MediaType = 0;
+ }
+ disks.Add(dis.DeviceId, dis);
+ }
+ return disks;
+ }
+ catch(Exception ex)
+ {
+ return new Dictionary();
+ }
+ }
+
+ [DebuggerHidden]
+ private static Dictionary GetPartitions()
+ {
+ var partitions = new Dictionary();
+ try
+ {
+ var scope = new ManagementScope(@"\\.\root\Microsoft\Windows\Storage");
+ scope.Connect();
+
+ using var partitionSearcher = new ManagementObjectSearcher($"SELECT DiskNumber, DriveLetter FROM MSFT_Partition");
+ partitionSearcher.Scope = scope;
+
+ var queryResult = partitionSearcher.Get();
+ if (queryResult.Count <= 0) return new Dictionary();
+
+ foreach (var partition in queryResult)
+ {
+ var diskNumber = partition["DiskNumber"].ToString();
+ var driveLetter = partition["DriveLetter"].ToString()[0];
+
+ partitions[driveLetter] = PhysicalDisks[diskNumber];
+ }
+
+ return partitions;
+ }
+ catch(Exception)
+ {
+ return partitions;
+ }
+ }
+}
+
+///
+/// Documentation: https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-physicaldisk
+///
+public class PhysicalDisk
+{
+ public ulong AllocatedSize;
+ public ushort BusType;
+ public ushort[] CannotPoolReason;
+ public bool CanPool;
+ public string Description;
+ public string DeviceId;
+ public ushort EnclosureNumber;
+ public string FirmwareVersion;
+ public string FriendlyName;
+ public ushort HealthStatus;
+ public bool IsIndicationEnabled;
+ public bool IsPartial;
+ public bool IsPowerProtected;
+ public bool IsWriteCacheEnabled;
+ public ulong LogicalSectorSize;
+ public MediaType MediaType;
+ public string[] OperationalDetails;
+ public ushort[] OperationalStatus;
+ public string OtherCannotPoolReasonDescription;
+ public string PartNumber;
+ public string PhysicalLocation;
+ public ulong PhysicalSectorSize;
+ public ulong Size;
+ public ushort SlotNumber;
+ public string SoftwareVersion;
+ public uint SpindleSpeed;
+ public ushort[] SupportedUsages;
+ public ushort UniqueIdFormat;
+ public ushort Usage;
+ public ushort VirtualDiskFootprint;
+}
+
+public enum MediaType : ushort
+{
+ Unspecified = 0,
+ HDD = 3,
+ SSD = 4,
+ SCM = 5
+}
diff --git a/Wabbajack.App.Wpf/Util/FilePickerVM.cs b/Wabbajack.App.Wpf/Util/FilePickerVM.cs
index 6197e5eb2..7de530146 100644
--- a/Wabbajack.App.Wpf/Util/FilePickerVM.cs
+++ b/Wabbajack.App.Wpf/Util/FilePickerVM.cs
@@ -6,7 +6,6 @@
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
-using Wabbajack;
using Wabbajack.Extensions;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
@@ -30,6 +29,9 @@ public enum CheckOptions
On
}
+ public delegate AbsolutePath TransformPath(AbsolutePath targetPath);
+ public TransformPath PathTransformer { get; set; }
+
public object Parent { get; }
[Reactive]
@@ -271,7 +273,10 @@ public ICommand ConstructTypicalPickerCommand(IObservable canExecute = nul
dlg.Filters.Add(filter);
}
if (dlg.ShowDialog() != CommonFileDialogResult.Ok) return;
- TargetPath = (AbsolutePath)dlg.FileName;
+
+ var path = (AbsolutePath)dlg.FileName;
+ TargetPath = PathTransformer == null ? path : PathTransformer(path);
+
}, canExecute: canExecute);
}
}
diff --git a/Wabbajack.App.Wpf/Util/ImageCacheManager.cs b/Wabbajack.App.Wpf/Util/ImageCacheManager.cs
new file mode 100644
index 000000000..85a2af5d6
--- /dev/null
+++ b/Wabbajack.App.Wpf/Util/ImageCacheManager.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows.Media.Imaging;
+using DynamicData.Kernel;
+using Microsoft.Extensions.Logging;
+using ReactiveUI;
+using Wabbajack.Hashing.xxHash64;
+using Wabbajack.Paths;
+using Wabbajack.Paths.IO;
+using static System.Text.Encoding;
+using Convert = System.Convert;
+
+namespace Wabbajack;
+
+public class ImageCacheManager
+{
+ private readonly TimeSpan _pollInterval = TimeSpan.FromMinutes(1);
+ private readonly Services.OSIntegrated.Configuration _configuration;
+ private readonly ILogger _logger;
+
+ private AbsolutePath _imageCachePath;
+ private ConcurrentDictionary _cachedImages { get; } = new();
+
+ private async Task SaveImage(Hash hash, MemoryStream ms)
+ {
+ var path = _imageCachePath.Combine(hash.ToHex());
+ await using var fs = new FileStream(path.ToString(), FileMode.Create, FileAccess.Write);
+ ms.WriteTo(fs);
+ }
+ private async Task<(bool, MemoryStream)> LoadImage(Hash hash)
+ {
+ MemoryStream imageStream = null;
+ var path = _imageCachePath.Combine(hash.ToHex());
+ if (!path.FileExists())
+ {
+ return (false, imageStream);
+ }
+
+ imageStream = new MemoryStream();
+ await using var fs = new FileStream(path.ToString(), FileMode.Open, FileAccess.Read);
+ await fs.CopyToAsync(imageStream);
+ return (true, imageStream);
+ }
+
+ public ImageCacheManager(ILogger logger, Services.OSIntegrated.Configuration configuration)
+ {
+ _logger = logger;
+ _configuration = configuration;
+ _imageCachePath = _configuration.ImageCacheLocation;
+ _imageCachePath.CreateDirectory();
+
+ RxApp.TaskpoolScheduler.ScheduleRecurringAction(_pollInterval, () =>
+ {
+ foreach (var (hash, cachedImage) in _cachedImages)
+ {
+ if (!cachedImage.IsExpired()) continue;
+
+ try
+ {
+ _cachedImages.TryRemove(hash, out _);
+ File.Delete(_configuration.ImageCacheLocation.Combine(hash).ToString());
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to delete cached image {b64}", hash);
+ }
+ }
+ });
+
+ }
+
+ public async Task Add(string url, BitmapImage img)
+ {
+ var hash = await UTF8.GetBytes(url).Hash();
+ if (!_cachedImages.TryAdd(hash, new CachedImage(img))) return false;
+
+ await SaveImage(hash, (MemoryStream)img.StreamSource);
+ return true;
+
+ }
+
+ public async Task<(bool, BitmapImage)> Get(string url)
+ {
+ var hash = await UTF8.GetBytes(url).Hash();
+ // Try to load the image from memory
+ if (_cachedImages.TryGetValue(hash, out var cachedImage)) return (true, cachedImage.Image);
+
+ // Try to load the image from disk
+ var (success, imageStream) = await LoadImage(hash);
+ if (!success) return (false, null);
+
+ var img = UIUtils.BitmapImageFromStream(imageStream);
+ _cachedImages.TryAdd(hash, new CachedImage(img));
+ await imageStream.DisposeAsync();
+ return (true, img);
+
+ }
+}
+
+public class CachedImage(BitmapImage image)
+{
+ private readonly DateTime _cachedAt = DateTime.Now;
+ private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5);
+
+ public BitmapImage Image { get; } = image;
+
+ public bool IsExpired() => _cachedAt - DateTime.Now > _cacheDuration;
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Util/InstallResultHelper.cs b/Wabbajack.App.Wpf/Util/InstallResultHelper.cs
new file mode 100644
index 000000000..2b2fd9fc9
--- /dev/null
+++ b/Wabbajack.App.Wpf/Util/InstallResultHelper.cs
@@ -0,0 +1,35 @@
+using Wabbajack.Installer;
+
+namespace Wabbajack;
+
+public static class InstallResultHelper
+{
+ public static string GetTitle(this InstallResult result)
+ {
+ return result switch
+ {
+ InstallResult.Succeeded => "Modlist installed",
+ InstallResult.Cancelled => "Cancelled",
+ InstallResult.Errored => "An error occurred",
+ InstallResult.GameMissing => "Game not found",
+ InstallResult.GameInvalid => "Game installation invalid",
+ InstallResult.DownloadFailed => "Download failed",
+ InstallResult.NotEnoughSpace => "Not enough space",
+ _ => ""
+ };
+ }
+ public static string GetDescription(this InstallResult result)
+ {
+ return result switch
+ {
+ InstallResult.Succeeded => "The modlist installation completed successfully. Start up Mod Organizer in the installation directory, hit run on the top right and enjoy playing!",
+ InstallResult.Cancelled => "The modlist installation was cancelled.",
+ InstallResult.Errored => "The modlist installation has failed because of an unknown error. Check the log for more information.",
+ InstallResult.GameMissing => "The modlist installation has failed because the game could not be found. Please make sure a valid copy of the game is installed.",
+ InstallResult.GameInvalid => "The modlist installation has failed because not all required game files could be found. Verify all game files are present and retry installation.",
+ InstallResult.DownloadFailed => "The modlist installation has failed because one or more required files could not be downloaded. Try manually placing these files in the downloads directory.",
+ InstallResult.NotEnoughSpace => "The modlist installation has failed because not enough free space was available on the disk. Please free up enough space and retry the installation.",
+ _ => ""
+ };
+ }
+}
diff --git a/Wabbajack.App.Wpf/Util/SystemParametersConstructor.cs b/Wabbajack.App.Wpf/Util/SystemParametersConstructor.cs
index db5153b25..10d1fc33b 100644
--- a/Wabbajack.App.Wpf/Util/SystemParametersConstructor.cs
+++ b/Wabbajack.App.Wpf/Util/SystemParametersConstructor.cs
@@ -1,16 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.CompilerServices;
+using System.Management;
using System.Runtime.InteropServices;
-using System.Text;
using Microsoft.Extensions.Logging;
using PInvoke;
using Silk.NET.Core.Native;
using Silk.NET.DXGI;
-using Wabbajack.Common;
using Wabbajack.Installer;
-using Wabbajack;
using static PInvoke.User32;
using UnmanagedType = System.Runtime.InteropServices.UnmanagedType;
@@ -113,16 +110,42 @@ public SystemParameters Create()
}
var memory = GetMemoryStatus();
+ var gpuName = GetGPUName();
return new SystemParameters
{
ScreenWidth = width,
ScreenHeight = height,
VideoMemorySize = (long)dxgiMemory,
SystemMemorySize = (long)memory.ullTotalPhys,
- SystemPageSize = (long)memory.ullTotalPageFile - (long)memory.ullTotalPhys
+ SystemPageSize = (long)memory.ullTotalPageFile - (long)memory.ullTotalPhys,
+ GpuName = gpuName
};
}
-
+
+ private string GetGPUName()
+ {
+ string gpuName = "";
+ try
+ {
+ ManagementObjectSearcher videoControllers = new ManagementObjectSearcher("SELECT * FROM Win32_VideoController");
+
+ uint gpuRefreshRate = 0;
+
+ foreach (ManagementObject obj in videoControllers.Get())
+ {
+ var currentRefreshRate = (uint)obj["CurrentRefreshRate"];
+ if (currentRefreshRate > gpuRefreshRate)
+ gpuName = obj["Description"].ToString();
+ }
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError("Failed to get GPU information: {ex}", ex.ToString());
+ }
+
+ return gpuName;
+ }
+
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer);
diff --git a/Wabbajack.App.Wpf/Util/UIUtils.cs b/Wabbajack.App.Wpf/Util/UIUtils.cs
index b4fc10ac8..b4875e8ca 100644
--- a/Wabbajack.App.Wpf/Util/UIUtils.cs
+++ b/Wabbajack.App.Wpf/Util/UIUtils.cs
@@ -1,190 +1,186 @@
-using DynamicData;
-using DynamicData.Binding;
-using Microsoft.WindowsAPICodePack.Dialogs;
-using ReactiveUI;
+using ReactiveUI;
using System;
using System.Diagnostics;
+using System.Drawing.Imaging;
using System.IO;
using System.Net.Http;
using System.Reactive.Linq;
-using System.Reflection;
using System.Text;
-using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
-using Wabbajack.Common;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Extensions;
using Wabbajack.Models;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Png;
+using Wabbajack.DTOs;
+using Exception = System.Exception;
+using SharpImage = SixLabors.ImageSharp.Image;
-namespace Wabbajack
+namespace Wabbajack;
+
+public static class UIUtils
{
- public static class UIUtils
- {
- public static BitmapImage BitmapImageFromResource(string name) => BitmapImageFromStream(System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/Wabbajack;component/" + name)).Stream);
+ public static BitmapImage BitmapImageFromResource(string name) => BitmapImageFromStream(System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/Wabbajack;component/" + name)).Stream);
- public static BitmapImage BitmapImageFromStream(Stream stream)
- {
- var img = new BitmapImage();
- img.BeginInit();
- img.CacheOption = BitmapCacheOption.OnLoad;
- img.StreamSource = stream;
- img.EndInit();
- img.Freeze();
- return img;
- }
+ public static BitmapImage BitmapImageFromStream(Stream stream)
+ {
+ var img = new BitmapImage();
+ img.BeginInit();
+ img.CacheOption = BitmapCacheOption.OnLoad;
+ img.StreamSource = stream;
+ img.EndInit();
+ img.Freeze();
+ return img;
+ }
- public static bool TryGetBitmapImageFromFile(AbsolutePath path, out BitmapImage bitmapImage)
+ public static bool TryGetBitmapImageFromFile(AbsolutePath path, out BitmapImage bitmapImage)
+ {
+ try
{
- try
- {
- if (!path.FileExists())
- {
- bitmapImage = default;
- return false;
- }
- bitmapImage = new BitmapImage(new Uri(path.ToString(), UriKind.RelativeOrAbsolute));
- return true;
- }
- catch (Exception)
+ if (!path.FileExists())
{
bitmapImage = default;
return false;
}
+ bitmapImage = new BitmapImage(new Uri(path.ToString(), UriKind.RelativeOrAbsolute));
+ return true;
}
-
- public static void OpenWebsite(Uri url)
+ catch (Exception)
{
- Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}")
- {
- CreateNoWindow = true,
- });
+ bitmapImage = default;
+ return false;
}
+ }
+
- public static void OpenFolder(AbsolutePath path)
+ public static void OpenWebsite(Uri url)
+ {
+ Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}")
{
- string folderPath = path.ToString();
- if (!folderPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
- {
- folderPath += Path.DirectorySeparatorChar.ToString();
- }
+ CreateNoWindow = true,
+ });
+ }
- Process.Start(new ProcessStartInfo()
- {
- FileName = folderPath,
- UseShellExecute = true,
- Verb = "open"
- });
- }
+ public static void OpenWebsite(string url)
+ {
+ Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}")
+ {
+ CreateNoWindow = true,
+ });
+ }
- public static AbsolutePath OpenFileDialog(string filter, string initialDirectory = null)
+ public static void OpenFolder(AbsolutePath path)
+ {
+ string folderPath = path.ToString();
+ if (!folderPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
- OpenFileDialog ofd = new OpenFileDialog();
- ofd.Filter = filter;
- ofd.InitialDirectory = initialDirectory;
- if (ofd.ShowDialog() == DialogResult.OK)
- return (AbsolutePath)ofd.FileName;
- return default;
+ folderPath += Path.DirectorySeparatorChar.ToString();
}
- public static IObservable DownloadBitmapImage(this IObservable obs, Action exceptionHandler,
- LoadingLock loadingLock)
+ Process.Start(new ProcessStartInfo()
{
- return obs
- .ObserveOn(RxApp.TaskpoolScheduler)
- .SelectTask(async url =>
+ FileName = folderPath,
+ UseShellExecute = true,
+ Verb = "open"
+ });
+ }
+
+ public static void OpenFolderAndSelectFile(AbsolutePath pathToFile)
+ {
+ Process.Start(new ProcessStartInfo() { FileName = "explorer.exe ", Arguments = $"/select, \"{pathToFile}\"" });
+ }
+
+ public static AbsolutePath OpenFileDialog(string filter, string initialDirectory = null)
+ {
+ OpenFileDialog ofd = new OpenFileDialog();
+ ofd.Filter = filter;
+ ofd.InitialDirectory = initialDirectory;
+ if (ofd.ShowDialog() == DialogResult.OK)
+ return (AbsolutePath)ofd.FileName;
+ return default;
+ }
+
+ public static IObservable DownloadBitmapImage(this IObservable obs, Action exceptionHandler,
+ LoadingLock loadingLock, HttpClient client, ImageCacheManager icm)
+ {
+ return obs
+ .ObserveOn(RxApp.TaskpoolScheduler)
+ .SelectTask(async url =>
+ {
+ using var ll = loadingLock.WithLoading();
+ try
{
- var ll = loadingLock.WithLoading();
- try
- {
- var (found, mstream) = await FindCachedImage(url);
- if (found) return (ll, mstream);
-
- var ret = new MemoryStream();
- using (var client = new HttpClient())
- await using (var stream = await client.GetStreamAsync(url))
- {
- await stream.CopyToAsync(ret);
- }
-
- ret.Seek(0, SeekOrigin.Begin);
-
- await WriteCachedImage(url, ret.ToArray());
- return (ll, ret);
- }
- catch (Exception ex)
+ var (cached, cachedImg) = await icm.Get(url);
+ if (cached) return cachedImg;
+
+ await using var stream = await client.GetStreamAsync(url);
+
+ using var pngStream = new MemoryStream();
+ using (var sharpImg = await SharpImage.LoadAsync(stream))
{
- exceptionHandler(ex);
- return (ll, default);
+ await sharpImg.SaveAsPngAsync(pngStream);
}
- })
- .Select(x =>
+
+ var img = BitmapImageFromStream(pngStream);
+ await icm.Add(url, img);
+ return img;
+ }
+ catch (Exception ex)
{
- var (ll, memStream) = x;
- if (memStream == null) return default;
- try
- {
- return BitmapImageFromStream(memStream);
- }
- catch (Exception ex)
- {
- exceptionHandler(ex);
- return default;
- }
- finally
- {
- ll.Dispose();
- memStream.Dispose();
- }
- })
- .ObserveOnGuiThread();
- }
+ exceptionHandler(ex);
+ return default;
+ }
+ })
+ .ObserveOnGuiThread();
+ }
- private static async Task WriteCachedImage(string url, byte[] data)
+ ///
+ /// Format bytes to a greater unit
+ ///
+ /// number of bytes
+ ///
+ public static string FormatBytes(long bytes)
+ {
+ string[] Suffix = { "B", "KB", "MB", "GB", "TB" };
+ int i;
+ double dblSByte = bytes;
+ for (i = 0; i < Suffix.Length && bytes >= 1024; i++, bytes /= 1024)
{
- var folder = KnownFolders.WabbajackAppLocal.Combine("ModListImages");
- if (!folder.DirectoryExists()) folder.CreateDirectory();
-
- var path = folder.Combine((await Encoding.UTF8.GetBytes(url).Hash()).ToHex());
- await path.WriteAllBytesAsync(data);
+ dblSByte = bytes / 1024.0;
}
- private static async Task<(bool Found, MemoryStream data)> FindCachedImage(string uri)
- {
- var folder = KnownFolders.WabbajackAppLocal.Combine("ModListImages");
- if (!folder.DirectoryExists()) folder.CreateDirectory();
-
- var path = folder.Combine((await Encoding.UTF8.GetBytes(uri).Hash()).ToHex());
- return path.FileExists() ? (true, new MemoryStream(await path.ReadAllBytesAsync())) : (false, default);
- }
+ return String.Format("{0:0.##} {1}", dblSByte, Suffix[i]);
+ }
- ///
- /// Format bytes to a greater unit
- ///
- /// number of bytes
- ///
- public static string FormatBytes(long bytes)
+ public static void OpenFile(AbsolutePath file)
+ {
+ Process.Start(new ProcessStartInfo("cmd.exe", $"/c start \"\" \"{file}\"")
{
- string[] Suffix = { "B", "KB", "MB", "GB", "TB" };
- int i;
- double dblSByte = bytes;
- for (i = 0; i < Suffix.Length && bytes >= 1024; i++, bytes /= 1024)
- {
- dblSByte = bytes / 1024.0;
- }
+ CreateNoWindow = true,
+ });
+ }
- return String.Format("{0:0.##} {1}", dblSByte, Suffix[i]);
- }
+ public static string GetSmallImageUri(ModlistMetadata metadata)
+ {
+ var fileName = metadata.Links.MachineURL + "_small.webp";
+ return $"https://raw.githubusercontent.com/wabbajack-tools/mod-lists/refs/heads/master/reports/{metadata.RepositoryName}/{fileName}";
+ }
- public static void OpenFile(AbsolutePath file)
+ public static string GetHumanReadableReadmeLink(string uri)
+ {
+ if (uri.Contains("raw.githubusercontent.com") && uri.EndsWith(".md"))
{
- Process.Start(new ProcessStartInfo("cmd.exe", $"/c start \"\" \"{file}\"")
- {
- CreateNoWindow = true,
- });
+ var urlParts = uri.Split('/');
+ var user = urlParts[3];
+ var repository = urlParts[4];
+ var branch = urlParts[5];
+ var fileName = urlParts[6];
+ return $"https://github.com/{user}/{repository}/blob/{branch}/{fileName}#{repository}";
}
+ return uri;
}
-}
+}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/Verbs/NexusLogin.cs b/Wabbajack.App.Wpf/Verbs/NexusLogin.cs
index b91148fe3..ef79c8570 100644
--- a/Wabbajack.App.Wpf/Verbs/NexusLogin.cs
+++ b/Wabbajack.App.Wpf/Verbs/NexusLogin.cs
@@ -4,6 +4,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Wabbajack.CLI.Builder;
+using Wabbajack.Messages;
using Wabbajack.UserIntervention;
namespace Wabbajack.Verbs;
@@ -25,11 +26,9 @@ public NexusLogin(ILogger logger, IServiceProvider services)
public async Task Run(CancellationToken token)
{
var tcs = new TaskCompletionSource();
- var view = new BrowserWindow(_services);
- view.Closed += (sender, args) => { tcs.TrySetResult(0); };
- var provider = _services.GetRequiredService();
- view.DataContext = provider;
- view.Show();
+ var handler = _services.GetRequiredService();
+ handler.Closed += (sender, args) => { tcs.TrySetResult(0); };
+ ShowBrowserWindow.Send(handler);
return await tcs.Task;
}
diff --git a/Wabbajack.App.Wpf/View Models/BackNavigatingVM.cs b/Wabbajack.App.Wpf/View Models/BackNavigatingVM.cs
deleted file mode 100644
index f60641049..000000000
--- a/Wabbajack.App.Wpf/View Models/BackNavigatingVM.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Reactive;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using Microsoft.Extensions.Logging;
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Common;
-using Wabbajack;
-using Wabbajack.Messages;
-
-namespace Wabbajack
-{
- public interface IBackNavigatingVM : IReactiveObject
- {
- ViewModel NavigateBackTarget { get; set; }
- ReactiveCommand BackCommand { get; }
-
- Subject IsBackEnabledSubject { get; }
- IObservable IsBackEnabled { get; }
- }
-
- public class BackNavigatingVM : ViewModel, IBackNavigatingVM
- {
- [Reactive]
- public ViewModel NavigateBackTarget { get; set; }
- public ReactiveCommand BackCommand { get; protected set; }
-
- [Reactive]
- public bool IsActive { get; set; }
-
- public Subject IsBackEnabledSubject { get; } = new Subject();
- public IObservable IsBackEnabled { get; }
-
- public BackNavigatingVM(ILogger logger)
- {
- IsBackEnabled = IsBackEnabledSubject.StartWith(true);
- BackCommand = ReactiveCommand.Create(
- execute: () => logger.CatchAndLog(() =>
- {
- NavigateBack.Send();
- Unload();
- }),
- canExecute: this.ConstructCanNavigateBack()
- .ObserveOnGuiThread());
-
- this.WhenActivated(disposables =>
- {
- IsActive = true;
- Disposable.Create(() => IsActive = false).DisposeWith(disposables);
- });
- }
-
- public virtual void Unload()
- {
- }
- }
-
- public static class IBackNavigatingVMExt
- {
- public static IObservable ConstructCanNavigateBack(this IBackNavigatingVM vm)
- {
- return vm.WhenAny(x => x.NavigateBackTarget)
- .CombineLatest(vm.IsBackEnabled)
- .Select(x => x.First != null && x.Second);
- }
-
- public static IObservable ConstructIsActive(this IBackNavigatingVM vm, MainWindowVM mwvm)
- {
- return mwvm.WhenAny(x => x.ActivePane)
- .Select(x => object.ReferenceEquals(vm, x));
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/CPUDisplayVM.cs b/Wabbajack.App.Wpf/View Models/CPUDisplayVM.cs
deleted file mode 100644
index 87371bc53..000000000
--- a/Wabbajack.App.Wpf/View Models/CPUDisplayVM.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack;
-using Wabbajack.RateLimiter;
-
-namespace Wabbajack
-{
- public class CPUDisplayVM : ViewModel
- {
- [Reactive]
- public ulong ID { get; set; }
- [Reactive]
- public DateTime StartTime { get; set; }
- [Reactive]
- public bool IsWorking { get; set; }
- [Reactive]
- public string Msg { get; set; }
- [Reactive]
- public Percent ProgressPercent { get; set; }
-
- public CPUDisplayVM()
- {
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs b/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs
deleted file mode 100644
index f514aea9f..000000000
--- a/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs
+++ /dev/null
@@ -1,515 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.IO;
-using System.Linq;
-using System.Reactive;
-using Microsoft.Extensions.Logging;
-using Wabbajack.Messages;
-using ReactiveUI;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows.Media;
-using DynamicData;
-using Microsoft.WindowsAPICodePack.Dialogs;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Common;
-using Wabbajack.Compiler;
-using Wabbajack.Downloaders;
-using Wabbajack.DTOs;
-using Wabbajack.DTOs.DownloadStates;
-using Wabbajack.DTOs.JsonConverters;
-using Wabbajack.Extensions;
-using Wabbajack.Installer;
-using Wabbajack.LoginManagers;
-using Wabbajack.Models;
-using Wabbajack.Networking.WabbajackClientApi;
-using Wabbajack.Paths;
-using Wabbajack.Paths.IO;
-using Wabbajack.RateLimiter;
-using Wabbajack.Services.OSIntegrated;
-
-namespace Wabbajack
-{
- public enum CompilerState
- {
- Configuration,
- Compiling,
- Completed,
- Errored
- }
-
- public class CompilerVM : BackNavigatingVM, ICpuStatusVM
- {
- private const string LastSavedCompilerSettings = "last-saved-compiler-settings";
- private readonly DTOSerializer _dtos;
- private readonly SettingsManager _settingsManager;
- private readonly IServiceProvider _serviceProvider;
- private readonly ILogger _logger;
- private readonly ResourceMonitor _resourceMonitor;
- private readonly CompilerSettingsInferencer _inferencer;
- private readonly IEnumerable _logins;
- private readonly DownloadDispatcher _downloadDispatcher;
- private readonly Client _wjClient;
- private AsyncLock _waitForLoginLock = new ();
-
- [Reactive] public string StatusText { get; set; }
- [Reactive] public Percent StatusProgress { get; set; }
-
- [Reactive] public CompilerState State { get; set; }
-
- [Reactive] public MO2CompilerVM SubCompilerVM { get; set; }
-
- // Paths
- public FilePickerVM ModlistLocation { get; }
- public FilePickerVM DownloadLocation { get; }
- public FilePickerVM OutputLocation { get; }
-
- // Modlist Settings
-
- [Reactive] public string ModListName { get; set; }
- [Reactive] public string Version { get; set; }
- [Reactive] public string Author { get; set; }
- [Reactive] public string Description { get; set; }
- public FilePickerVM ModListImagePath { get; } = new();
- [Reactive] public ImageSource ModListImage { get; set; }
- [Reactive] public string Website { get; set; }
- [Reactive] public string Readme { get; set; }
- [Reactive] public bool IsNSFW { get; set; }
- [Reactive] public bool PublishUpdate { get; set; }
- [Reactive] public string MachineUrl { get; set; }
- [Reactive] public Game BaseGame { get; set; }
- [Reactive] public string SelectedProfile { get; set; }
- [Reactive] public AbsolutePath GamePath { get; set; }
- [Reactive] public bool IsMO2Compilation { get; set; }
-
- [Reactive] public RelativePath[] AlwaysEnabled { get; set; } = Array.Empty();
- [Reactive] public RelativePath[] NoMatchInclude { get; set; } = Array.Empty();
- [Reactive] public RelativePath[] Include { get; set; } = Array.Empty();
- [Reactive] public RelativePath[] Ignore { get; set; } = Array.Empty();
-
- [Reactive] public string[] OtherProfiles { get; set; } = Array.Empty();
-
- [Reactive] public AbsolutePath Source { get; set; }
-
- public AbsolutePath SettingsOutputLocation => Source.Combine(ModListName).WithExtension(Ext.CompilerSettings);
-
-
- public ReactiveCommand ExecuteCommand { get; }
- public ReactiveCommand ReInferSettingsCommand { get; set; }
-
- public LogStream LoggerProvider { get; }
- public ReadOnlyObservableCollection StatusList => _resourceMonitor.Tasks;
-
- [Reactive] public ErrorResponse ErrorState { get; private set; }
-
- public CompilerVM(ILogger logger, DTOSerializer dtos, SettingsManager settingsManager,
- IServiceProvider serviceProvider, LogStream loggerProvider, ResourceMonitor resourceMonitor,
- CompilerSettingsInferencer inferencer, Client wjClient, IEnumerable logins, DownloadDispatcher downloadDispatcher) : base(logger)
- {
- _logger = logger;
- _dtos = dtos;
- _settingsManager = settingsManager;
- _serviceProvider = serviceProvider;
- LoggerProvider = loggerProvider;
- _resourceMonitor = resourceMonitor;
- _inferencer = inferencer;
- _wjClient = wjClient;
- _logins = logins;
- _downloadDispatcher = downloadDispatcher;
-
- StatusText = "Compiler Settings";
- StatusProgress = Percent.Zero;
-
- BackCommand =
- ReactiveCommand.CreateFromTask(async () =>
- {
- await SaveSettingsFile();
- NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
- });
-
- SubCompilerVM = new MO2CompilerVM(this);
-
- ExecuteCommand = ReactiveCommand.CreateFromTask(async () => await StartCompilation());
- ReInferSettingsCommand = ReactiveCommand.CreateFromTask(async () => await ReInferSettings(),
- this.WhenAnyValue(vm => vm.Source)
- .ObserveOnGuiThread()
- .Select(v => v != default)
- .CombineLatest(this.WhenAnyValue(vm => vm.ModListName)
- .ObserveOnGuiThread()
- .Select(p => !string.IsNullOrWhiteSpace(p)))
- .Select(v => v.First && v.Second));
-
- ModlistLocation = new FilePickerVM
- {
- ExistCheckOption = FilePickerVM.CheckOptions.On,
- PathType = FilePickerVM.PathTypeOptions.File,
- PromptTitle = "Select a config file or a modlist.txt file"
- };
-
- DownloadLocation = new FilePickerVM
- {
- ExistCheckOption = FilePickerVM.CheckOptions.On,
- PathType = FilePickerVM.PathTypeOptions.Folder,
- PromptTitle = "Location where the downloads for this list are stored"
- };
-
- OutputLocation = new FilePickerVM
- {
- ExistCheckOption = FilePickerVM.CheckOptions.Off,
- PathType = FilePickerVM.PathTypeOptions.Folder,
- PromptTitle = "Location where the compiled modlist will be stored"
- };
-
- ModlistLocation.Filters.AddRange(new[]
- {
- new CommonFileDialogFilter("MO2 Modlist", "*" + Ext.Txt),
- new CommonFileDialogFilter("Compiler Settings File", "*" + Ext.CompilerSettings)
- });
-
-
- this.WhenActivated(disposables =>
- {
- State = CompilerState.Configuration;
- Disposable.Empty.DisposeWith(disposables);
-
- ModlistLocation.WhenAnyValue(vm => vm.TargetPath)
- .Subscribe(p => InferModListFromLocation(p).FireAndForget())
- .DisposeWith(disposables);
-
-
- this.WhenAnyValue(x => x.DownloadLocation.TargetPath)
- .CombineLatest(this.WhenAnyValue(x => x.ModlistLocation.TargetPath),
- this.WhenAnyValue(x => x.OutputLocation.TargetPath),
- this.WhenAnyValue(x => x.DownloadLocation.ErrorState),
- this.WhenAnyValue(x => x.ModlistLocation.ErrorState),
- this.WhenAnyValue(x => x.OutputLocation.ErrorState),
- this.WhenAnyValue(x => x.ModListName),
- this.WhenAnyValue(x => x.Version))
- .Select(_ => Validate())
- .BindToStrict(this, vm => vm.ErrorState)
- .DisposeWith(disposables);
-
- LoadLastSavedSettings().FireAndForget();
- });
- }
-
-
- private async Task ReInferSettings()
- {
- var newSettings = await _inferencer.InferModListFromLocation(
- Source.Combine("profiles", SelectedProfile, "modlist.txt"));
-
- if (newSettings == null)
- {
- _logger.LogError("Cannot infer settings");
- return;
- }
-
- Include = newSettings.Include;
- Ignore = newSettings.Ignore;
- AlwaysEnabled = newSettings.AlwaysEnabled;
- NoMatchInclude = newSettings.NoMatchInclude;
- OtherProfiles = newSettings.AdditionalProfiles;
- }
-
- private ErrorResponse Validate()
- {
- var errors = new List();
- errors.Add(DownloadLocation.ErrorState);
- errors.Add(ModlistLocation.ErrorState);
- errors.Add(OutputLocation.ErrorState);
- return ErrorResponse.Combine(errors);
- }
-
- private async Task InferModListFromLocation(AbsolutePath path)
- {
- using var _ = LoadingLock.WithLoading();
-
- CompilerSettings settings;
- if (path == default) return;
- if (path.FileName.Extension == Ext.CompilerSettings)
- {
- await using var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
- settings = (await _dtos.DeserializeAsync(fs))!;
- }
- else if (path.FileName == "modlist.txt".ToRelativePath())
- {
- settings = await _inferencer.InferModListFromLocation(path);
- if (settings == null) return;
- }
- else
- {
- return;
- }
-
- BaseGame = settings.Game;
- ModListName = settings.ModListName;
- Version = settings.Version?.ToString() ?? "";
- Author = settings.ModListAuthor;
- Description = settings.Description;
- ModListImagePath.TargetPath = settings.ModListImage;
- Website = settings.ModListWebsite?.ToString() ?? "";
- Readme = settings.ModListReadme?.ToString() ?? "";
- IsNSFW = settings.ModlistIsNSFW;
-
- Source = settings.Source;
- DownloadLocation.TargetPath = settings.Downloads;
- if (settings.OutputFile.Extension == Ext.Wabbajack)
- settings.OutputFile = settings.OutputFile.Parent;
- OutputLocation.TargetPath = settings.OutputFile;
- SelectedProfile = settings.Profile;
- PublishUpdate = settings.PublishUpdate;
- MachineUrl = settings.MachineUrl;
- OtherProfiles = settings.AdditionalProfiles;
- AlwaysEnabled = settings.AlwaysEnabled;
- NoMatchInclude = settings.NoMatchInclude;
- Include = settings.Include;
- Ignore = settings.Ignore;
- if (path.FileName == "modlist.txt".ToRelativePath())
- {
- await SaveSettingsFile();
- await LoadLastSavedSettings();
- }
- }
-
-
- private async Task StartCompilation()
- {
- var tsk = Task.Run(async () =>
- {
- try
- {
- await SaveSettingsFile();
- var token = CancellationToken.None;
- State = CompilerState.Compiling;
-
- foreach (var downloader in await _downloadDispatcher.AllDownloaders([new Nexus()]))
- {
- _logger.LogInformation("Preparing {Name}", downloader.GetType().Name);
- if (await downloader.Prepare())
- continue;
-
- var manager = _logins
- .FirstOrDefault(l => l.LoginFor() == downloader.GetType());
- if (manager == null)
- {
- _logger.LogError("Cannot install, could not prepare {Name} for downloading",
- downloader.GetType().Name);
- throw new Exception($"No way to prepare {downloader}");
- }
-
- RxApp.MainThreadScheduler.Schedule(manager, (_, _) =>
- {
- manager.TriggerLogin.Execute(null);
- return Disposable.Empty;
- });
-
- while (true)
- {
- if (await downloader.Prepare())
- break;
- await Task.Delay(1000);
- }
- }
-
- var mo2Settings = GetSettings();
- mo2Settings.UseGamePaths = true;
- if (mo2Settings.OutputFile.DirectoryExists())
- mo2Settings.OutputFile = mo2Settings.OutputFile.Combine(mo2Settings.ModListName.ToRelativePath()
- .WithExtension(Ext.Wabbajack));
-
- if (PublishUpdate && !await RunPreflightChecks(token))
- {
- State = CompilerState.Errored;
- return;
- }
-
- var compiler = MO2Compiler.Create(_serviceProvider, mo2Settings);
-
- var events = Observable.FromEventPattern(h => compiler.OnStatusUpdate += h,
- h => compiler.OnStatusUpdate -= h)
- .ObserveOnGuiThread()
- .Debounce(TimeSpan.FromSeconds(0.5))
- .Subscribe(update =>
- {
- var s = update.EventArgs;
- StatusText = $"[Step {s.CurrentStep}] {s.StatusText}";
- StatusProgress = s.StepProgress;
- });
-
-
- try
- {
- var result = await compiler.Begin(token);
- if (!result)
- throw new Exception("Compilation Failed");
- }
- finally
- {
- events.Dispose();
- }
-
- if (PublishUpdate)
- {
- _logger.LogInformation("Publishing List");
- var downloadMetadata = _dtos.Deserialize(
- await mo2Settings.OutputFile.WithExtension(Ext.Meta).WithExtension(Ext.Json)
- .ReadAllTextAsync())!;
- await _wjClient.PublishModlist(MachineUrl, System.Version.Parse(Version),
- mo2Settings.OutputFile, downloadMetadata);
- }
-
- _logger.LogInformation("Compiler Finished");
-
- RxApp.MainThreadScheduler.Schedule(_logger, (_, _) =>
- {
- StatusText = "Compilation Completed";
- StatusProgress = Percent.Zero;
- State = CompilerState.Completed;
- return Disposable.Empty;
- });
- }
- catch (Exception ex)
- {
- RxApp.MainThreadScheduler.Schedule(_logger, (_, _) =>
- {
- StatusText = "Compilation Failed";
- StatusProgress = Percent.Zero;
-
- State = CompilerState.Errored;
- _logger.LogInformation(ex, "Failed Compilation : {Message}", ex.Message);
- return Disposable.Empty;
- });
- }
- });
-
- await tsk;
- }
-
- private async Task RunPreflightChecks(CancellationToken token)
- {
- var lists = await _wjClient.GetMyModlists(token);
- if (!lists.Any(x => x.Equals(MachineUrl, StringComparison.InvariantCultureIgnoreCase)))
- {
- _logger.LogError("Preflight Check failed, list {MachineUrl} not found in any repository", MachineUrl);
- return false;
- }
-
- if (!System.Version.TryParse(Version, out var v))
- {
- _logger.LogError("Bad Version Number {Version}", Version);
- return false;
- }
-
- return true;
- }
-
- private async Task SaveSettingsFile()
- {
- if (Source == default) return;
- await using var st = SettingsOutputLocation.Open(FileMode.Create, FileAccess.Write, FileShare.None);
- await JsonSerializer.SerializeAsync(st, GetSettings(), _dtos.Options);
-
- await _settingsManager.Save(LastSavedCompilerSettings, SettingsOutputLocation);
- }
-
- private async Task LoadLastSavedSettings()
- {
- var lastPath = await _settingsManager.Load(LastSavedCompilerSettings);
- if (lastPath == default || !lastPath.FileExists() ||
- lastPath.FileName.Extension != Ext.CompilerSettings) return;
- ModlistLocation.TargetPath = lastPath;
- }
-
-
- private CompilerSettings GetSettings()
- {
- System.Version.TryParse(Version, out var pversion);
- Uri.TryCreate(Website, UriKind.Absolute, out var websiteUri);
-
- return new CompilerSettings
- {
- ModListName = ModListName,
- ModListAuthor = Author,
- Version = pversion ?? new Version(),
- Description = Description,
- ModListReadme = Readme,
- ModListImage = ModListImagePath.TargetPath,
- ModlistIsNSFW = IsNSFW,
- ModListWebsite = websiteUri ?? new Uri("http://www.wabbajack.org"),
- Downloads = DownloadLocation.TargetPath,
- Source = Source,
- Game = BaseGame,
- PublishUpdate = PublishUpdate,
- MachineUrl = MachineUrl,
- Profile = SelectedProfile,
- UseGamePaths = true,
- OutputFile = OutputLocation.TargetPath,
- AlwaysEnabled = AlwaysEnabled,
- AdditionalProfiles = OtherProfiles,
- NoMatchInclude = NoMatchInclude,
- Include = Include,
- Ignore = Ignore
- };
- }
-
- #region ListOps
-
- public void AddOtherProfile(string profile)
- {
- OtherProfiles = (OtherProfiles ?? Array.Empty()).Append(profile).Distinct().ToArray();
- }
-
- public void RemoveProfile(string profile)
- {
- OtherProfiles = OtherProfiles.Where(p => p != profile).ToArray();
- }
-
- public void AddAlwaysEnabled(RelativePath path)
- {
- AlwaysEnabled = (AlwaysEnabled ?? Array.Empty()).Append(path).Distinct().ToArray();
- }
-
- public void RemoveAlwaysEnabled(RelativePath path)
- {
- AlwaysEnabled = AlwaysEnabled.Where(p => p != path).ToArray();
- }
-
- public void AddNoMatchInclude(RelativePath path)
- {
- NoMatchInclude = (NoMatchInclude ?? Array.Empty()).Append(path).Distinct().ToArray();
- }
-
- public void RemoveNoMatchInclude(RelativePath path)
- {
- NoMatchInclude = NoMatchInclude.Where(p => p != path).ToArray();
- }
-
- public void AddInclude(RelativePath path)
- {
- Include = (Include ?? Array.Empty()).Append(path).Distinct().ToArray();
- }
-
- public void RemoveInclude(RelativePath path)
- {
- Include = Include.Where(p => p != path).ToArray();
- }
-
-
- public void AddIgnore(RelativePath path)
- {
- Ignore = (Ignore ?? Array.Empty()).Append(path).Distinct().ToArray();
- }
-
- public void RemoveIgnore(RelativePath path)
- {
- Ignore = Ignore.Where(p => p != path).ToArray();
- }
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/View Models/Compilers/MO2CompilerVM.cs b/Wabbajack.App.Wpf/View Models/Compilers/MO2CompilerVM.cs
deleted file mode 100644
index b9f708ae0..000000000
--- a/Wabbajack.App.Wpf/View Models/Compilers/MO2CompilerVM.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using Microsoft.WindowsAPICodePack.Dialogs;
-using ReactiveUI.Fody.Helpers;
-using System;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Threading.Tasks;
-using DynamicData;
-using Wabbajack.Common;
-using Wabbajack.Compiler;
-using Wabbajack.DTOs;
-using Wabbajack.DTOs.GitHub;
-using Wabbajack;
-using Wabbajack.Extensions;
-using Wabbajack.Paths.IO;
-using Consts = Wabbajack.Consts;
-
-namespace Wabbajack
-{
- public class MO2CompilerVM : ViewModel
- {
- public CompilerVM Parent { get; }
-
- public FilePickerVM DownloadLocation { get; }
-
- public FilePickerVM ModListLocation { get; }
-
- [Reactive]
- public ACompiler ActiveCompilation { get; private set; }
-
- [Reactive]
- public object StatusTracker { get; private set; }
-
- public void Unload()
- {
- throw new NotImplementedException();
- }
-
- public IObservable CanCompile { get; }
- public Task> Compile()
- {
- throw new NotImplementedException();
- }
-
- public MO2CompilerVM(CompilerVM parent)
- {
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/Gallery/ModListGalleryVM.cs b/Wabbajack.App.Wpf/View Models/Gallery/ModListGalleryVM.cs
deleted file mode 100644
index 48045dcf9..000000000
--- a/Wabbajack.App.Wpf/View Models/Gallery/ModListGalleryVM.cs
+++ /dev/null
@@ -1,261 +0,0 @@
-
-
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows.Input;
-using DynamicData;
-using Microsoft.Extensions.Logging;
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Common;
-using Wabbajack.Downloaders.GameFile;
-using Wabbajack.DTOs;
-using Wabbajack.Messages;
-using Wabbajack.Networking.WabbajackClientApi;
-using Wabbajack.Services.OSIntegrated;
-using Wabbajack.Services.OSIntegrated.Services;
-
-namespace Wabbajack
-{
- public class ModListGalleryVM : BackNavigatingVM
- {
- public MainWindowVM MWVM { get; }
-
- private readonly SourceCache _modLists = new(x => x.Metadata.NamespacedName);
- public ReadOnlyObservableCollection _filteredModLists;
-
- public ReadOnlyObservableCollection ModLists => _filteredModLists;
-
- private const string ALL_GAME_TYPE = "All";
-
- [Reactive] public IErrorResponse Error { get; set; }
-
- [Reactive] public string Search { get; set; }
-
- [Reactive] public bool OnlyInstalled { get; set; }
-
- [Reactive] public bool ShowNSFW { get; set; }
-
- [Reactive] public bool ShowUnofficialLists { get; set; }
-
- [Reactive] public string GameType { get; set; }
-
- public class GameTypeEntry
- {
- public GameTypeEntry(string humanFriendlyName, int amount)
- {
- HumanFriendlyName = humanFriendlyName;
- Amount = amount;
- FormattedName = $"{HumanFriendlyName} ({Amount})";
- }
- public string HumanFriendlyName { get; set; }
- public int Amount { get; set; }
- public string FormattedName { get; set; }
- }
-
- [Reactive] public List GameTypeEntries { get; set; }
- private bool _filteringOnGame;
- private GameTypeEntry _selectedGameTypeEntry = null;
-
- public GameTypeEntry SelectedGameTypeEntry
- {
- get => _selectedGameTypeEntry;
- set
- {
- RaiseAndSetIfChanged(ref _selectedGameTypeEntry, value == null ? GameTypeEntries?.FirstOrDefault(gte => gte.HumanFriendlyName == ALL_GAME_TYPE) : value);
- GameType = _selectedGameTypeEntry?.HumanFriendlyName;
- }
- }
-
- private readonly Client _wjClient;
- private readonly ILogger _logger;
- private readonly GameLocator _locator;
- private readonly ModListDownloadMaintainer _maintainer;
- private readonly SettingsManager _settingsManager;
- private readonly CancellationToken _cancellationToken;
-
- public ICommand ClearFiltersCommand { get; set; }
-
- public ModListGalleryVM(ILogger logger, Client wjClient, GameLocator locator,
- SettingsManager settingsManager, ModListDownloadMaintainer maintainer, CancellationToken cancellationToken)
- : base(logger)
- {
- _wjClient = wjClient;
- _logger = logger;
- _locator = locator;
- _maintainer = maintainer;
- _settingsManager = settingsManager;
- _cancellationToken = cancellationToken;
-
- ClearFiltersCommand = ReactiveCommand.Create(
- () =>
- {
- OnlyInstalled = false;
- ShowNSFW = false;
- ShowUnofficialLists = false;
- Search = string.Empty;
- SelectedGameTypeEntry = GameTypeEntries.FirstOrDefault();
- });
-
- BackCommand = ReactiveCommand.Create(
- () =>
- {
- NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
- });
-
-
- this.WhenActivated(disposables =>
- {
- LoadModLists().FireAndForget();
- LoadSettings().FireAndForget();
-
- Disposable.Create(() => SaveSettings().FireAndForget())
- .DisposeWith(disposables);
-
- var searchTextPredicates = this.ObservableForProperty(vm => vm.Search)
- .Select(change => change.Value)
- .StartWith(Search)
- .Select>(txt =>
- {
- if (string.IsNullOrWhiteSpace(txt)) return _ => true;
- return item => item.Metadata.Title.ContainsCaseInsensitive(txt) ||
- item.Metadata.Description.ContainsCaseInsensitive(txt);
- });
-
- var onlyInstalledGamesFilter = this.ObservableForProperty(vm => vm.OnlyInstalled)
- .Select(v => v.Value)
- .Select>(onlyInstalled =>
- {
- if (onlyInstalled == false) return _ => true;
- return item => _locator.IsInstalled(item.Metadata.Game);
- })
- .StartWith(_ => true);
-
- var showUnofficial = this.ObservableForProperty(vm => vm.ShowUnofficialLists)
- .Select(v => v.Value)
- .StartWith(false)
- .Select>(unoffical =>
- {
- if (unoffical) return x => true;
- return x => x.Metadata.Official;
- });
-
- var showNSFWFilter = this.ObservableForProperty(vm => vm.ShowNSFW)
- .Select(v => v.Value)
- .Select>(showNsfw => { return item => item.Metadata.NSFW == showNsfw; })
- .StartWith(item => item.Metadata.NSFW == false);
-
- var gameFilter = this.ObservableForProperty(vm => vm.GameType)
- .Select(v => v.Value)
- .Select>(selected =>
- {
- _filteringOnGame = true;
- if (selected is null or ALL_GAME_TYPE) return _ => true;
- return item => item.Metadata.Game.MetaData().HumanFriendlyGameName == selected;
- })
- .StartWith(_ => true);
-
- _modLists.Connect()
- .ObserveOn(RxApp.MainThreadScheduler)
- .Filter(searchTextPredicates)
- .Filter(onlyInstalledGamesFilter)
- .Filter(showUnofficial)
- .Filter(showNSFWFilter)
- .Filter(gameFilter)
- .Bind(out _filteredModLists)
- .Subscribe((_) =>
- {
- if (!_filteringOnGame)
- {
- var previousGameType = GameType;
- SelectedGameTypeEntry = null;
- GameTypeEntries = new(GetGameTypeEntries());
- var nextEntry = GameTypeEntries.FirstOrDefault(gte => previousGameType == gte.HumanFriendlyName);
- SelectedGameTypeEntry = nextEntry != default ? nextEntry : GameTypeEntries.FirstOrDefault(gte => GameType == ALL_GAME_TYPE);
- }
- _filteringOnGame = false;
- })
- .DisposeWith(disposables);
- });
- }
-
- private class FilterSettings
- {
- public string GameType { get; set; }
- public bool ShowNSFW { get; set; }
- public bool ShowUnofficialLists { get; set; }
- public bool OnlyInstalled { get; set; }
- public string Search { get; set; }
- }
-
- public override void Unload()
- {
- Error = null;
- }
-
- private async Task SaveSettings()
- {
- await _settingsManager.Save("modlist_gallery", new FilterSettings
- {
- GameType = GameType,
- ShowNSFW = ShowNSFW,
- ShowUnofficialLists = ShowUnofficialLists,
- Search = Search,
- OnlyInstalled = OnlyInstalled,
- });
- }
-
- private async Task LoadSettings()
- {
- using var ll = LoadingLock.WithLoading();
- RxApp.MainThreadScheduler.Schedule(await _settingsManager.Load("modlist_gallery"),
- (_, s) =>
- {
- SelectedGameTypeEntry = GameTypeEntries?.FirstOrDefault(gte => gte.HumanFriendlyName.Equals(s.GameType));
- ShowNSFW = s.ShowNSFW;
- ShowUnofficialLists = s.ShowUnofficialLists;
- Search = s.Search;
- OnlyInstalled = s.OnlyInstalled;
- return Disposable.Empty;
- });
- }
-
- private async Task LoadModLists()
- {
- using var ll = LoadingLock.WithLoading();
- try
- {
- var modLists = await _wjClient.LoadLists();
- var modlistSummaries = await _wjClient.GetListStatuses();
- _modLists.Edit(e =>
- {
- e.Clear();
- e.AddOrUpdate(modLists.Select(m =>
- new ModListMetadataVM(_logger, this, m, _maintainer, modlistSummaries, _wjClient, _cancellationToken)));
- });
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "While loading lists");
- ll.Fail();
- }
- ll.Succeed();
- }
-
- private List GetGameTypeEntries()
- {
- return ModLists.Select(fm => fm.Metadata)
- .GroupBy(m => m.Game)
- .Select(g => new GameTypeEntry(g.Key.MetaData().HumanFriendlyGameName, g.Count()))
- .OrderBy(gte => gte.HumanFriendlyName)
- .Prepend(new GameTypeEntry(ALL_GAME_TYPE, ModLists.Count))
- .ToList();
- }
- }
-}
\ No newline at end of file
diff --git a/Wabbajack.App.Wpf/View Models/Gallery/ModListMetadataVM.cs b/Wabbajack.App.Wpf/View Models/Gallery/ModListMetadataVM.cs
deleted file mode 100644
index d9336e48a..000000000
--- a/Wabbajack.App.Wpf/View Models/Gallery/ModListMetadataVM.cs
+++ /dev/null
@@ -1,243 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows.Input;
-using System.Windows.Media.Imaging;
-using DynamicData;
-using Microsoft.Extensions.Logging;
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Common;
-using Wabbajack.DTOs;
-using Wabbajack.DTOs.ServerResponses;
-using Wabbajack;
-using Wabbajack.Extensions;
-using Wabbajack.Messages;
-using Wabbajack.Models;
-using Wabbajack.Networking.WabbajackClientApi;
-using Wabbajack.Paths;
-using Wabbajack.Paths.IO;
-using Wabbajack.RateLimiter;
-using Wabbajack.Services.OSIntegrated.Services;
-
-namespace Wabbajack
-{
-
- public struct ModListTag
- {
- public ModListTag(string name)
- {
- Name = name;
- }
-
- public string Name { get; }
- }
-
- public class ModListMetadataVM : ViewModel
- {
- public ModlistMetadata Metadata { get; }
- private ModListGalleryVM _parent;
-
- public ICommand OpenWebsiteCommand { get; }
- public ICommand ExecuteCommand { get; }
-
- public ICommand ModListContentsCommend { get; }
-
- private readonly ObservableAsPropertyHelper _Exists;
- public bool Exists => _Exists.Value;
-
- public AbsolutePath Location { get; }
-
- public LoadingLock LoadingImageLock { get; } = new();
-
- [Reactive]
- public List ModListTagList { get; private set; }
-
- [Reactive]
- public Percent ProgressPercent { get; private set; }
-
- [Reactive]
- public bool IsBroken { get; private set; }
-
- [Reactive]
- public ModListStatus Status { get; set; }
-
- [Reactive]
- public bool IsDownloading { get; private set; }
-
- [Reactive]
- public string DownloadSizeText { get; private set; }
-
- [Reactive]
- public string InstallSizeText { get; private set; }
-
- [Reactive]
- public string TotalSizeRequirementText { get; private set; }
-
- [Reactive]
- public string VersionText { get; private set; }
-
- [Reactive]
- public bool ImageContainsTitle { get; private set; }
-
- [Reactive]
-
- public bool DisplayVersionOnlyInInstallerView { get; private set; }
-
- [Reactive]
- public IErrorResponse Error { get; private set; }
-
- private readonly ObservableAsPropertyHelper _Image;
- public BitmapImage Image => _Image.Value;
-
- private readonly ObservableAsPropertyHelper _LoadingImage;
- public bool LoadingImage => _LoadingImage.Value;
-
- private Subject IsLoadingIdle;
- private readonly ILogger _logger;
- private readonly ModListDownloadMaintainer _maintainer;
- private readonly Client _wjClient;
- private readonly CancellationToken _cancellationToken;
-
- public ModListMetadataVM(ILogger logger, ModListGalleryVM parent, ModlistMetadata metadata,
- ModListDownloadMaintainer maintainer, ModListSummary[] modlistSummaries, Client wjClient, CancellationToken cancellationToken)
- {
- _logger = logger;
- _parent = parent;
- _maintainer = maintainer;
- Metadata = metadata;
- _wjClient = wjClient;
- _cancellationToken = cancellationToken;
- Location = LauncherUpdater.CommonFolder.Value.Combine("downloaded_mod_lists", Metadata.NamespacedName).WithExtension(Ext.Wabbajack);
- ModListTagList = new List();
-
- UpdateStatus().FireAndForget();
-
- Metadata.Tags.ForEach(tag =>
- {
- ModListTagList.Add(new ModListTag(tag));
- });
- ModListTagList.Add(new ModListTag(metadata.Game.MetaData().HumanFriendlyGameName));
-
- DownloadSizeText = "Download size : " + UIUtils.FormatBytes(Metadata.DownloadMetadata.SizeOfArchives);
- InstallSizeText = "Installation size : " + UIUtils.FormatBytes(Metadata.DownloadMetadata.SizeOfInstalledFiles);
- TotalSizeRequirementText = "Total size requirement: " + UIUtils.FormatBytes(
- Metadata.DownloadMetadata.SizeOfArchives + Metadata.DownloadMetadata.SizeOfInstalledFiles
- );
- VersionText = "Modlist version : " + Metadata.Version;
- ImageContainsTitle = Metadata.ImageContainsTitle;
- DisplayVersionOnlyInInstallerView = Metadata.DisplayVersionOnlyInInstallerView;
- var modListSummary = GetModListSummaryForModlist(modlistSummaries, metadata.NamespacedName);
- IsBroken = modListSummary.HasFailures || metadata.ForceDown;
- // https://www.wabbajack.org/modlist/wj-featured/aldrnari
- OpenWebsiteCommand = ReactiveCommand.Create(() => UIUtils.OpenWebsite(new Uri($"https://www.wabbajack.org/modlist/{Metadata.NamespacedName}")));
-
- IsLoadingIdle = new Subject();
-
- ModListContentsCommend = ReactiveCommand.Create(async () =>
- {
- UIUtils.OpenWebsite(new Uri($"https://www.wabbajack.org/search/{Metadata.NamespacedName}"));
- }, IsLoadingIdle.StartWith(true));
-
- ExecuteCommand = ReactiveCommand.CreateFromTask(async () =>
- {
- if (await _maintainer.HaveModList(Metadata))
- {
- LoadModlistForInstalling.Send(_maintainer.ModListPath(Metadata), Metadata);
- NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Installer);
- }
- else
- {
- await Download();
- }
- }, LoadingLock.WhenAnyValue(ll => ll.IsLoading)
- .CombineLatest(this.WhenAnyValue(vm => vm.IsBroken))
- .Select(v => !v.First && !v.Second));
-
- _Exists = Observable.Interval(TimeSpan.FromSeconds(0.5))
- .Unit()
- .StartWith(Unit.Default)
- .FlowSwitch(_parent.WhenAny(x => x.IsActive))
- .SelectAsync(async _ =>
- {
- try
- {
- return !IsDownloading && await maintainer.HaveModList(metadata);
- }
- catch (Exception)
- {
- return true;
- }
- })
- .ToGuiProperty(this, nameof(Exists));
-
- var imageObs = Observable.Return(Metadata.Links.ImageUri)
- .DownloadBitmapImage((ex) => _logger.LogError("Error downloading modlist image {Title}", Metadata.Title), LoadingImageLock);
-
- _Image = imageObs
- .ToGuiProperty(this, nameof(Image));
-
- _LoadingImage = imageObs
- .Select(x => false)
- .StartWith(true)
- .ToGuiProperty(this, nameof(LoadingImage));
- }
-
-
-
- private async Task Download()
- {
- try
- {
- Status = ModListStatus.Downloading;
-
- using var ll = LoadingLock.WithLoading();
- var (progress, task) = _maintainer.DownloadModlist(Metadata, _cancellationToken);
- var dispose = progress
- .BindToStrict(this, vm => vm.ProgressPercent);
- try
- {
- await _wjClient.SendMetric("downloading", Metadata.Title);
- await task;
- await UpdateStatus();
- }
- finally
- {
- dispose.Dispose();
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "While downloading {Modlist}", Metadata.RepositoryName);
- await UpdateStatus();
- }
- }
-
- private async Task UpdateStatus()
- {
- if (await _maintainer.HaveModList(Metadata))
- Status = ModListStatus.Downloaded;
- else if (LoadingLock.IsLoading)
- Status = ModListStatus.Downloading;
- else
- Status = ModListStatus.NotDownloaded;
- }
-
- public enum ModListStatus
- {
- NotDownloaded,
- Downloading,
- Downloaded
- }
-
- private static ModListSummary GetModListSummaryForModlist(ModListSummary[] modListSummaries, string machineUrl)
- {
- return modListSummaries.FirstOrDefault(x => x.MachineURL == machineUrl);
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/GameVM.cs b/Wabbajack.App.Wpf/View Models/GameVM.cs
deleted file mode 100644
index 602b0c4d3..000000000
--- a/Wabbajack.App.Wpf/View Models/GameVM.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Wabbajack.DTOs;
-
-namespace Wabbajack
-{
- public class GameVM
- {
- public Game Game { get; }
- public string DisplayName { get; }
-
- public GameVM(Game game)
- {
- Game = game;
- DisplayName = game.MetaData().HumanFriendlyGameName;
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs
deleted file mode 100644
index 8849400a4..000000000
--- a/Wabbajack.App.Wpf/View Models/Installers/ISubInstallerVM.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Threading.Tasks;
-using Wabbajack.Installer;
-using Wabbajack.DTOs.Interventions;
-
-namespace Wabbajack
-{
- public interface ISubInstallerVM
- {
- InstallerVM Parent { get; }
- IInstaller ActiveInstallation { get; }
- void Unload();
- bool SupportsAfterInstallNavigation { get; }
- void AfterInstallNavigation();
- int ConfigVisualVerticalOffset { get; }
- ErrorResponse CanInstall { get; }
- Task Install();
- IUserIntervention InterventionConverter(IUserIntervention intervention);
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs
deleted file mode 100644
index 99918e1bc..000000000
--- a/Wabbajack.App.Wpf/View Models/Installers/InstallerVM.cs
+++ /dev/null
@@ -1,643 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
-using ReactiveUI;
-using System.Reactive.Disposables;
-using System.Windows.Media.Imaging;
-using ReactiveUI.Fody.Helpers;
-using DynamicData;
-using System.Reactive;
-using System.Reactive.Linq;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows.Shell;
-using System.Windows.Threading;
-using Microsoft.Extensions.Logging;
-using Microsoft.WindowsAPICodePack.Dialogs;
-using Wabbajack.Common;
-using Wabbajack.Downloaders;
-using Wabbajack.Downloaders.GameFile;
-using Wabbajack.DTOs;
-using Wabbajack.DTOs.DownloadStates;
-using Wabbajack.DTOs.JsonConverters;
-using Wabbajack.Hashing.xxHash64;
-using Wabbajack.Installer;
-using Wabbajack.LoginManagers;
-using Wabbajack.Messages;
-using Wabbajack.Models;
-using Wabbajack.Paths;
-using Wabbajack.RateLimiter;
-using Wabbajack.Paths.IO;
-using Wabbajack.Services.OSIntegrated;
-using Wabbajack.Util;
-using System.Windows.Forms;
-using Microsoft.Extensions.DependencyInjection;
-using Wabbajack.CLI.Verbs;
-using Wabbajack.VFS;
-
-namespace Wabbajack;
-
-public enum ModManager
-{
- Standard
-}
-
-public enum InstallState
-{
- Configuration,
- Installing,
- Success,
- Failure
-}
-
-public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
-{
- private const string LastLoadedModlist = "last-loaded-modlist";
- private const string InstallSettingsPrefix = "install-settings-";
- private Random _random = new();
-
-
- [Reactive]
- public Percent StatusProgress { get; set; }
-
- [Reactive]
- public string StatusText { get; set; }
-
- [Reactive]
- public ModList ModList { get; set; }
-
- [Reactive]
- public ModlistMetadata ModlistMetadata { get; set; }
-
- [Reactive]
- public ErrorResponse? Completed { get; set; }
-
- [Reactive]
- public FilePickerVM ModListLocation { get; set; }
-
- [Reactive]
- public MO2InstallerVM Installer { get; set; }
-
- [Reactive]
- public BitmapFrame ModListImage { get; set; }
-
- [Reactive]
-
- public BitmapFrame SlideShowImage { get; set; }
-
-
- [Reactive]
- public InstallState InstallState { get; set; }
-
- [Reactive]
- protected ErrorResponse[] Errors { get; private set; }
-
- [Reactive]
- public ErrorResponse Error { get; private set; }
-
- ///
- /// Slideshow Data
- ///
- [Reactive]
- public string SlideShowTitle { get; set; }
-
- [Reactive]
- public string SlideShowAuthor { get; set; }
-
- [Reactive]
- public string SlideShowDescription { get; set; }
-
-
- private readonly DTOSerializer _dtos;
- private readonly ILogger _logger;
- private readonly SettingsManager _settingsManager;
- private readonly IServiceProvider _serviceProvider;
- private readonly SystemParametersConstructor _parametersConstructor;
- private readonly IGameLocator _gameLocator;
- private readonly ResourceMonitor _resourceMonitor;
- private readonly Services.OSIntegrated.Configuration _configuration;
- private readonly HttpClient _client;
- private readonly DownloadDispatcher _downloadDispatcher;
- private readonly IEnumerable _logins;
- private readonly CancellationToken _cancellationToken;
- public ReadOnlyObservableCollection StatusList => _resourceMonitor.Tasks;
-
- [Reactive]
- public bool Installing { get; set; }
-
- [Reactive]
- public ErrorResponse ErrorState { get; set; }
-
- [Reactive]
- public bool ShowNSFWSlides { get; set; }
-
- public LogStream LoggerProvider { get; }
-
- private AbsolutePath LastInstallPath { get; set; }
-
- [Reactive] public bool OverwriteFiles { get; set; }
-
-
- // Command properties
- public ReactiveCommand ShowManifestCommand { get; }
- public ReactiveCommand OpenReadmeCommand { get; }
- public ReactiveCommand OpenWikiCommand { get; }
- public ReactiveCommand OpenDiscordButton { get; }
- public ReactiveCommand VisitModListWebsiteCommand { get; }
-
- public ReactiveCommand CloseWhenCompleteCommand { get; }
- public ReactiveCommand OpenLogsCommand { get; }
- public ReactiveCommand GoToInstallCommand { get; }
- public ReactiveCommand BeginCommand { get; }
-
- public ReactiveCommand VerifyCommand { get; }
-
- public InstallerVM(ILogger logger, DTOSerializer dtos, SettingsManager settingsManager, IServiceProvider serviceProvider,
- SystemParametersConstructor parametersConstructor, IGameLocator gameLocator, LogStream loggerProvider, ResourceMonitor resourceMonitor,
- Wabbajack.Services.OSIntegrated.Configuration configuration, HttpClient client, DownloadDispatcher dispatcher, IEnumerable logins,
- CancellationToken cancellationToken) : base(logger)
- {
- _logger = logger;
- _configuration = configuration;
- LoggerProvider = loggerProvider;
- _settingsManager = settingsManager;
- _dtos = dtos;
- _serviceProvider = serviceProvider;
- _parametersConstructor = parametersConstructor;
- _gameLocator = gameLocator;
- _resourceMonitor = resourceMonitor;
- _client = client;
- _downloadDispatcher = dispatcher;
- _logins = logins;
- _cancellationToken = cancellationToken;
-
- Installer = new MO2InstallerVM(this);
-
- BackCommand = ReactiveCommand.Create(() => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView));
-
- BeginCommand = ReactiveCommand.Create(() => BeginInstall().FireAndForget());
-
- VerifyCommand = ReactiveCommand.Create(() => Verify().FireAndForget());
-
- OpenReadmeCommand = ReactiveCommand.Create(() =>
- {
- UIUtils.OpenWebsite(new Uri(ModList!.Readme));
- }, this.WhenAnyValue(vm => vm.LoadingLock.IsNotLoading, vm => vm.ModList.Readme, (isNotLoading, readme) => isNotLoading && !string.IsNullOrWhiteSpace(readme)));
-
- OpenWikiCommand = ReactiveCommand.Create(() =>
- {
- UIUtils.OpenWebsite(new Uri("https://wiki.wabbajack.org/index.html"));
- }, this.WhenAnyValue(vm => vm.LoadingLock.IsNotLoading));
-
- VisitModListWebsiteCommand = ReactiveCommand.Create(() =>
- {
- UIUtils.OpenWebsite(ModList!.Website);
- }, LoadingLock.IsNotLoadingObservable);
-
- ModListLocation = new FilePickerVM
- {
- ExistCheckOption = FilePickerVM.CheckOptions.On,
- PathType = FilePickerVM.PathTypeOptions.File,
- PromptTitle = "Select a ModList to install"
- };
- ModListLocation.Filters.Add(new CommonFileDialogFilter("Wabbajack Modlist", "*.wabbajack"));
-
- OpenLogsCommand = ReactiveCommand.Create(() =>
- {
- UIUtils.OpenFolder(_configuration.LogLocation);
- });
-
- OpenDiscordButton = ReactiveCommand.Create(() =>
- {
- UIUtils.OpenWebsite(new Uri(ModlistMetadata.Links.DiscordURL));
- }, this.WhenAnyValue(x => x.ModlistMetadata)
- .WhereNotNull()
- .Select(md => !string.IsNullOrWhiteSpace(md.Links.DiscordURL)));
-
- ShowManifestCommand = ReactiveCommand.Create(() =>
- {
- UIUtils.OpenWebsite(new Uri("https://www.wabbajack.org/search/" + ModlistMetadata.NamespacedName));
- }, this.WhenAnyValue(x => x.ModlistMetadata)
- .WhereNotNull()
- .Select(md => !string.IsNullOrWhiteSpace(md.Links.MachineURL)));
-
- CloseWhenCompleteCommand = ReactiveCommand.Create(() =>
- {
- Environment.Exit(0);
- });
-
- GoToInstallCommand = ReactiveCommand.Create(() =>
- {
- UIUtils.OpenFolder(Installer.Location.TargetPath);
- });
-
- this.WhenAnyValue(x => x.OverwriteFiles)
- .Subscribe(x => ConfirmOverwrite());
-
- MessageBus.Current.Listen()
- .Subscribe(msg => LoadModlistFromGallery(msg.Path, msg.Metadata).FireAndForget())
- .DisposeWith(CompositeDisposable);
-
- MessageBus.Current.Listen()
- .Subscribe(msg =>
- {
- LoadLastModlist().FireAndForget();
- });
-
- this.WhenActivated(disposables =>
- {
- ModListLocation.WhenAnyValue(l => l.TargetPath)
- .Subscribe(p => LoadModlist(p, null).FireAndForget())
- .DisposeWith(disposables);
-
- var token = new CancellationTokenSource();
- BeginSlideShow(token.Token).FireAndForget();
- Disposable.Create(() => token.Cancel())
- .DisposeWith(disposables);
-
- this.WhenAny(vm => vm.ModListLocation.ErrorState)
- .CombineLatest(this.WhenAny(vm => vm.Installer.DownloadLocation.ErrorState),
- this.WhenAny(vm => vm.Installer.Location.ErrorState),
- this.WhenAny(vm => vm.ModListLocation.TargetPath),
- this.WhenAny(vm => vm.Installer.Location.TargetPath),
- this.WhenAny(vm => vm.Installer.DownloadLocation.TargetPath))
- .Select(t =>
- {
- var errors = new[] {t.First, t.Second, t.Third}
- .Where(t => t.Failed)
- .Concat(Validate())
- .ToArray();
- if (!errors.Any()) return ErrorResponse.Success;
- return ErrorResponse.Fail(string.Join("\n", errors.Select(e => e.Reason)));
- })
- .BindTo(this, vm => vm.ErrorState)
- .DisposeWith(disposables);
- });
-
- }
-
- private IEnumerable Validate()
- {
- if (!ModListLocation.TargetPath.FileExists())
- yield return ErrorResponse.Fail("Mod list source does not exist");
-
- var downloadPath = Installer.DownloadLocation.TargetPath;
- if (downloadPath.Depth <= 1)
- yield return ErrorResponse.Fail("Download path isn't set to a folder");
-
- var installPath = Installer.Location.TargetPath;
- if (installPath.Depth <= 1)
- yield return ErrorResponse.Fail("Install path isn't set to a folder");
- if (installPath.InFolder(KnownFolders.Windows))
- yield return ErrorResponse.Fail("Don't install modlists into your Windows folder");
- if( installPath.ToString().Length > 0 && downloadPath.ToString().Length > 0 && installPath == downloadPath)
- {
- yield return ErrorResponse.Fail("Can't have identical install and download folders");
- }
- if (installPath.ToString().Length > 0 && downloadPath.ToString().Length > 0 && KnownFolders.IsSubDirectoryOf(installPath.ToString(), downloadPath.ToString()))
- {
- yield return ErrorResponse.Fail("Can't put the install folder inside the download folder");
- }
- foreach (var game in GameRegistry.Games)
- {
- if (!_gameLocator.TryFindLocation(game.Key, out var location))
- continue;
-
- if (installPath.InFolder(location))
- yield return ErrorResponse.Fail("Can't install a modlist into a game folder");
-
- if (location.ThisAndAllParents().Any(path => installPath == path))
- {
- yield return ErrorResponse.Fail(
- "Can't install in this path, installed files may overwrite important game files");
- }
- }
-
- if (installPath.InFolder(KnownFolders.EntryPoint))
- yield return ErrorResponse.Fail("Can't install a modlist into the Wabbajack.exe path");
- if (downloadPath.InFolder(KnownFolders.EntryPoint))
- yield return ErrorResponse.Fail("Can't download a modlist into the Wabbajack.exe path");
- if (KnownFolders.EntryPoint.ThisAndAllParents().Any(path => installPath == path))
- {
- yield return ErrorResponse.Fail("Installing in this folder may overwrite Wabbajack");
- }
-
- if (installPath.ToString().Length != 0 && installPath != LastInstallPath && !OverwriteFiles &&
- Directory.EnumerateFileSystemEntries(installPath.ToString()).Any())
- {
- yield return ErrorResponse.Fail("There are files in the install folder, please tick 'Overwrite Installation' to confirm you want to install to this folder " + Environment.NewLine +
- "if you are updating an existing modlist, then this is expected and can be overwritten.");
- }
-
- if (KnownFolders.IsInSpecialFolder(installPath) || KnownFolders.IsInSpecialFolder(downloadPath))
- {
- yield return ErrorResponse.Fail("Can't install into Windows locations such as Documents etc, please make a new folder for the modlist - C:\\ModList\\ for example.");
- }
- // Disabled Because it was causing issues for people trying to update lists.
- //if (installPath.ToString().Length > 0 && downloadPath.ToString().Length > 0 && !HasEnoughSpace(installPath, downloadPath)){
- // yield return ErrorResponse.Fail("Can't install modlist due to lack of free hard drive space, please read the modlist Readme to learn more.");
- //}
- }
-
- /*
- private bool HasEnoughSpace(AbsolutePath inpath, AbsolutePath downpath)
- {
- string driveLetterInPath = inpath.ToString().Substring(0,1);
- string driveLetterDownPath = inpath.ToString().Substring(0,1);
- DriveInfo driveUsedInPath = new DriveInfo(driveLetterInPath);
- DriveInfo driveUsedDownPath = new DriveInfo(driveLetterDownPath);
- long spaceRequiredforInstall = ModlistMetadata.DownloadMetadata.SizeOfInstalledFiles;
- long spaceRequiredforDownload = ModlistMetadata.DownloadMetadata.SizeOfArchives;
- long spaceInstRemaining = driveUsedInPath.AvailableFreeSpace;
- long spaceDownRemaining = driveUsedDownPath.AvailableFreeSpace;
- if ( driveLetterInPath == driveLetterDownPath)
- {
- long totalSpaceRequired = spaceRequiredforInstall + spaceRequiredforDownload;
- if (spaceInstRemaining < totalSpaceRequired)
- {
- return false;
- }
-
- } else
- {
- if( spaceInstRemaining < spaceRequiredforInstall || spaceDownRemaining < spaceRequiredforDownload)
- {
- return false;
- }
- }
- return true;
-
- }*/
-
- private async Task BeginSlideShow(CancellationToken token)
- {
- while (!token.IsCancellationRequested)
- {
- await Task.Delay(5000, token);
- if (InstallState == InstallState.Installing)
- {
- await PopulateNextModSlide(ModList);
- }
- }
- }
-
- private async Task LoadLastModlist()
- {
- var lst = await _settingsManager.Load(LastLoadedModlist);
- if (lst.FileExists())
- {
- ModListLocation.TargetPath = lst;
- }
- }
-
- private async Task LoadModlistFromGallery(AbsolutePath path, ModlistMetadata metadata)
- {
- ModListLocation.TargetPath = path;
- ModlistMetadata = metadata;
- }
-
- private async Task LoadModlist(AbsolutePath path, ModlistMetadata? metadata)
- {
- using var ll = LoadingLock.WithLoading();
- InstallState = InstallState.Configuration;
- ModListLocation.TargetPath = path;
- try
- {
- ModList = await StandardInstaller.LoadFromFile(_dtos, path);
- ModListImage = BitmapFrame.Create(await StandardInstaller.ModListImageStream(path));
-
- if (!string.IsNullOrWhiteSpace(ModList.Readme))
- UIUtils.OpenWebsite(new Uri(ModList.Readme));
-
-
- StatusText = $"Install configuration for {ModList.Name}";
- TaskBarUpdate.Send($"Loaded {ModList.Name}", TaskbarItemProgressState.Normal);
-
- var hex = (await ModListLocation.TargetPath.ToString().Hash()).ToHex();
- var prevSettings = await _settingsManager.Load(InstallSettingsPrefix + hex);
-
- if (path.WithExtension(Ext.MetaData).FileExists())
- {
- try
- {
- metadata = JsonSerializer.Deserialize(await path.WithExtension(Ext.MetaData)
- .ReadAllTextAsync());
- ModlistMetadata = metadata;
- }
- catch (Exception ex)
- {
- _logger.LogInformation(ex, "Can't load metadata cached next to file");
- }
- }
-
- if (prevSettings.ModListLocation == path)
- {
- ModListLocation.TargetPath = prevSettings.ModListLocation;
- LastInstallPath = prevSettings.InstallLocation;
- Installer.Location.TargetPath = prevSettings.InstallLocation;
- Installer.DownloadLocation.TargetPath = prevSettings.DownloadLoadction;
- ModlistMetadata = metadata ?? prevSettings.Metadata;
- }
-
- PopulateSlideShow(ModList);
-
- ll.Succeed();
- await _settingsManager.Save(LastLoadedModlist, path);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "While loading modlist");
- ll.Fail();
- }
- }
-
- private void ConfirmOverwrite()
- {
- AbsolutePath prev = Installer.Location.TargetPath;
- Installer.Location.TargetPath = "".ToAbsolutePath();
- Installer.Location.TargetPath = prev;
- }
-
- private async Task Verify()
- {
- await Task.Run(async () =>
- {
- InstallState = InstallState.Installing;
-
- StatusText = $"Verifying {ModList.Name}";
-
-
- var cmd = new VerifyModlistInstall(_serviceProvider.GetRequiredService>(), _dtos,
- _serviceProvider.GetRequiredService>(),
- _serviceProvider.GetRequiredService());
-
- var result = await cmd.Run(ModListLocation.TargetPath, Installer.Location.TargetPath, _cancellationToken);
-
- if (result != 0)
- {
- TaskBarUpdate.Send($"Error during verification of {ModList.Name}", TaskbarItemProgressState.Error);
- InstallState = InstallState.Failure;
- StatusText = $"Error during install of {ModList.Name}";
- StatusProgress = Percent.Zero;
- }
- else
- {
- TaskBarUpdate.Send($"Finished verification of {ModList.Name}", TaskbarItemProgressState.Normal);
- InstallState = InstallState.Success;
- }
-
- });
- }
-
- private async Task BeginInstall()
- {
- await Task.Run(async () =>
- {
- InstallState = InstallState.Installing;
-
- foreach (var downloader in await _downloadDispatcher.AllDownloaders(ModList.Archives.Select(a => a.State)))
- {
- _logger.LogInformation("Preparing {Name}", downloader.GetType().Name);
- if (await downloader.Prepare())
- continue;
-
- var manager = _logins
- .FirstOrDefault(l => l.LoginFor() == downloader.GetType());
- if (manager == null)
- {
- _logger.LogError("Cannot install, could not prepare {Name} for downloading",
- downloader.GetType().Name);
- throw new Exception($"No way to prepare {downloader}");
- }
-
- RxApp.MainThreadScheduler.Schedule(manager, (_, _) =>
- {
- manager.TriggerLogin.Execute(null);
- return Disposable.Empty;
- });
-
- while (true)
- {
- if (await downloader.Prepare())
- break;
- await Task.Delay(1000);
- }
- }
-
-
- var postfix = (await ModListLocation.TargetPath.ToString().Hash()).ToHex();
- await _settingsManager.Save(InstallSettingsPrefix + postfix, new SavedInstallSettings
- {
- ModListLocation = ModListLocation.TargetPath,
- InstallLocation = Installer.Location.TargetPath,
- DownloadLoadction = Installer.DownloadLocation.TargetPath,
- Metadata = ModlistMetadata
- });
- await _settingsManager.Save(LastLoadedModlist, ModListLocation.TargetPath);
-
- try
- {
- var installer = StandardInstaller.Create(_serviceProvider, new InstallerConfiguration
- {
- Game = ModList.GameType,
- Downloads = Installer.DownloadLocation.TargetPath,
- Install = Installer.Location.TargetPath,
- ModList = ModList,
- ModlistArchive = ModListLocation.TargetPath,
- SystemParameters = _parametersConstructor.Create(),
- GameFolder = _gameLocator.GameLocation(ModList.GameType)
- });
-
-
- installer.OnStatusUpdate = update =>
- {
- StatusText = update.StatusText;
- StatusProgress = update.StepsProgress;
-
- TaskBarUpdate.Send(update.StatusText, TaskbarItemProgressState.Indeterminate,
- update.StepsProgress.Value);
- };
-
- if (!await installer.Begin(_cancellationToken))
- {
- TaskBarUpdate.Send($"Error during install of {ModList.Name}", TaskbarItemProgressState.Error);
- InstallState = InstallState.Failure;
- StatusText = $"Error during install of {ModList.Name}";
- StatusProgress = Percent.Zero;
- }
- else
- {
- TaskBarUpdate.Send($"Finished install of {ModList.Name}", TaskbarItemProgressState.Normal);
- InstallState = InstallState.Success;
-
- if (!string.IsNullOrWhiteSpace(ModList.Readme))
- UIUtils.OpenWebsite(new Uri(ModList.Readme));
-
- }
- }
- catch (Exception ex)
- {
- TaskBarUpdate.Send($"Error during install of {ModList.Name}", TaskbarItemProgressState.Error);
- _logger.LogError(ex, ex.Message);
- InstallState = InstallState.Failure;
- StatusText = $"Error during install of {ModList.Name}";
- StatusProgress = Percent.Zero;
- }
- });
-
- }
-
-
- class SavedInstallSettings
- {
- public AbsolutePath ModListLocation { get; set; }
- public AbsolutePath InstallLocation { get; set; }
- public AbsolutePath DownloadLoadction { get; set; }
-
- public ModlistMetadata Metadata { get; set; }
- }
-
- private void PopulateSlideShow(ModList modList)
- {
- if (ModlistMetadata.ImageContainsTitle && ModlistMetadata.DisplayVersionOnlyInInstallerView)
- {
- SlideShowTitle = "v" + ModlistMetadata.Version.ToString();
- }
- else
- {
- SlideShowTitle = modList.Name;
- }
- SlideShowAuthor = modList.Author;
- SlideShowDescription = modList.Description;
- SlideShowImage = ModListImage;
- }
-
-
- private async Task PopulateNextModSlide(ModList modList)
- {
- try
- {
- var mods = modList.Archives.Select(a => a.State)
- .OfType()
- .Where(t => ShowNSFWSlides || !t.IsNSFW)
- .Where(t => t.ImageURL != null)
- .ToArray();
- var thisMod = mods[_random.Next(0, mods.Length)];
- var data = await _client.GetByteArrayAsync(thisMod.ImageURL!);
- var image = BitmapFrame.Create(new MemoryStream(data));
- SlideShowTitle = thisMod.Name;
- SlideShowAuthor = thisMod.Author;
- SlideShowDescription = thisMod.Description;
- SlideShowImage = image;
- }
- catch (Exception ex)
- {
- _logger.LogTrace(ex, "While loading slide");
- }
- }
-
-}
diff --git a/Wabbajack.App.Wpf/View Models/Installers/MO2InstallerVM.cs b/Wabbajack.App.Wpf/View Models/Installers/MO2InstallerVM.cs
deleted file mode 100644
index 623381e0e..000000000
--- a/Wabbajack.App.Wpf/View Models/Installers/MO2InstallerVM.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Reactive.Disposables;
-using System.Threading.Tasks;
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Installer;
-using Wabbajack.DTOs.Interventions;
-using Wabbajack.Paths;
-
-namespace Wabbajack
-{
- public class MO2InstallerVM : ViewModel, ISubInstallerVM
- {
- public InstallerVM Parent { get; }
-
- [Reactive]
- public ErrorResponse CanInstall { get; set; }
-
- [Reactive]
- public IInstaller ActiveInstallation { get; private set; }
-
- [Reactive]
- public Mo2ModlistInstallationSettings CurrentSettings { get; set; }
-
- public FilePickerVM Location { get; }
-
- public FilePickerVM DownloadLocation { get; }
-
- public bool SupportsAfterInstallNavigation => true;
-
- [Reactive]
- public bool AutomaticallyOverwrite { get; set; }
-
- public int ConfigVisualVerticalOffset => 25;
-
- public MO2InstallerVM(InstallerVM installerVM)
- {
- Parent = installerVM;
-
- Location = new FilePickerVM()
- {
- ExistCheckOption = FilePickerVM.CheckOptions.Off,
- PathType = FilePickerVM.PathTypeOptions.Folder,
- PromptTitle = "Select Installation Directory",
- };
- Location.WhenAnyValue(t => t.TargetPath)
- .Subscribe(newPath =>
- {
- if (newPath != default && DownloadLocation!.TargetPath == AbsolutePath.Empty)
- {
- DownloadLocation.TargetPath = newPath.Combine("downloads");
- }
- }).DisposeWith(CompositeDisposable);
-
- DownloadLocation = new FilePickerVM()
- {
- ExistCheckOption = FilePickerVM.CheckOptions.Off,
- PathType = FilePickerVM.PathTypeOptions.Folder,
- PromptTitle = "Select a location for MO2 downloads",
- };
- }
-
- public void Unload()
- {
- SaveSettings(this.CurrentSettings);
- }
-
- private void SaveSettings(Mo2ModlistInstallationSettings settings)
- {
- //Parent.MWVM.Settings.Installer.LastInstalledListLocation = Parent.ModListLocation.TargetPath;
- if (settings == null) return;
- settings.InstallationLocation = Location.TargetPath;
- settings.DownloadLocation = DownloadLocation.TargetPath;
- settings.AutomaticallyOverrideExistingInstall = AutomaticallyOverwrite;
- }
-
- public void AfterInstallNavigation()
- {
- UIUtils.OpenFolder(Location.TargetPath);
- }
-
- public async Task Install()
- {
- /*
- using (var installer = new MO2Installer(
- archive: Parent.ModListLocation.TargetPath,
- modList: Parent.ModList.SourceModList,
- outputFolder: Location.TargetPath,
- downloadFolder: DownloadLocation.TargetPath,
- parameters: SystemParametersConstructor.Create()))
- {
- installer.Metadata = Parent.ModList.SourceModListMetadata;
- installer.UseCompression = Parent.MWVM.Settings.Filters.UseCompression;
- Parent.MWVM.Settings.Performance.SetProcessorSettings(installer);
-
- return await Task.Run(async () =>
- {
- try
- {
- var workTask = installer.Begin();
- ActiveInstallation = installer;
- return await workTask;
- }
- finally
- {
- ActiveInstallation = null;
- }
- });
- }
- */
- return true;
- }
-
- public IUserIntervention InterventionConverter(IUserIntervention intervention)
- {
- switch (intervention)
- {
- case ConfirmUpdateOfExistingInstall confirm:
- return new ConfirmUpdateOfExistingInstallVM(this, confirm);
- default:
- return intervention;
- }
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/Interfaces/ICpuStatusVM.cs b/Wabbajack.App.Wpf/View Models/Interfaces/ICpuStatusVM.cs
deleted file mode 100644
index 3a149bae8..000000000
--- a/Wabbajack.App.Wpf/View Models/Interfaces/ICpuStatusVM.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using DynamicData.Binding;
-using ReactiveUI;
-
-namespace Wabbajack
-{
- public interface ICpuStatusVM : IReactiveObject
- {
- ReadOnlyObservableCollection