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..974c2b48e 100644
--- a/Wabbajack.App.Wpf/App.xaml.cs
+++ b/Wabbajack.App.Wpf/App.xaml.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
@@ -26,176 +27,195 @@
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);
+ private static bool IsAdmin()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return false;
- var principle = new WindowsPrincipal(identity);
- return principle.IsInRole(WindowsBuiltInRole.Administrator);
+ try
+ {
+ var identity = WindowsIdentity.GetCurrent();
+ var owner = identity.Owner;
+ if (owner is not null) return owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
- }
- catch (Exception)
- {
- return false;
- }
- }
+ 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");
+
+ var uiTarget = new LogStream
+ {
+ Name = "ui",
+ Layout = "${message:withexception=false}",
+ };
- loggingBuilder.Services.AddSingleton(uiTarget);
+ loggingBuilder.Services.AddSingleton(uiTarget);
- config.AddRuleForAllLevels(fileTarget);
- config.AddRuleForAllLevels(consoleTarget);
- config.AddRuleForAllLevels(uiTarget);
+ config.AddRuleForAllLevels(fileTarget);
+ config.AddRuleForAllLevels(consoleTarget);
+ config.AddRuleForAllLevels(uiTarget);
- loggingBuilder.ClearProviders();
- loggingBuilder.SetMinimumLevel(LogLevel.Information);
- loggingBuilder.AddNLog(config);
- }
+ 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());
-
- 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;
- }
+ 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();
+
+ // 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..e8b55d4c0 100644
--- a/Wabbajack.App.Wpf/Consts.cs
+++ b/Wabbajack.App.Wpf/Consts.cs
@@ -9,6 +9,11 @@ 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 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 +23,14 @@ public static class Consts
public static byte SettingsVersion = 0;
public static RelativePath NativeSettingsJson = "native_settings.json".ToRelativePath();
+ public const string AllSavedCompilerSettingsPaths = "compiler_settings_paths";
+
+ // Info - TODO, make rich document?
+ public const string FileManagerInfo = @"
+Your modlist will contain lots of files and Wabbajack needs to know where all those files came from to compile a modlist installer. Most of these should be mods that are sourced from the downloads folder. But you might have folders you do **not** want to ship with the modlist, or folders or config files that are generated and can be inlined into the .wabbajack installer. Here is where these files or folders are managed.
+
+Find more information on the Wabbajack wiki!
+
+https://wiki.wabbajack.org/modlist_author_documentation/Compilation.html
+";
}
\ 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..9e6cea24a 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;
diff --git a/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs
index 8e982af75..6923e25e5 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;
@@ -66,11 +61,9 @@ public LoversLabLoginManager(ILogger logger, ITokenProvid
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()
diff --git a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
index 27ff83543..67f370b29 100644
--- a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
+++ b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
@@ -10,6 +10,7 @@
using ReactiveUI.Fody.Helpers;
using Wabbajack.Downloaders;
using Wabbajack.DTOs.Logins;
+using Wabbajack.Messages;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.UserIntervention;
@@ -39,7 +40,7 @@ public NexusLoginManager(ILogger logger, ITokenProvider await RefreshTokenState());
+ Task.Run(RefreshTokenState);
ClearLogin = ReactiveCommand.CreateFromTask(async () =>
{
@@ -66,11 +67,9 @@ 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 (sender, args) => { await RefreshTokenState(); };
+ ShowBrowserWindow.Send(handler);
}
private async Task RefreshTokenState()
diff --git a/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs
index 62a13e260..e0c469bab 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;
@@ -66,11 +62,9 @@ public VectorPlexusLoginManager(ILogger logger, IToken
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());
}
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..fcefd66f8
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/ShowFloatingWindow.cs
@@ -0,0 +1,25 @@
+using ReactiveUI;
+
+namespace Wabbajack.Messages;
+
+public enum FloatingScreenType
+{
+ None,
+ ModListDetails
+}
+
+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..521878f48 100644
--- a/Wabbajack.App.Wpf/Settings.cs
+++ b/Wabbajack.App.Wpf/Settings.cs
@@ -4,25 +4,33 @@
using Wabbajack.RateLimiter;
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 PerformanceSettings : ViewModel
- {
- private readonly Configuration.MainSettings _settings;
+public class PerformanceSettings : ViewModel
+{
+ private readonly Configuration.MainSettings _settings;
- public PerformanceSettings(Configuration.MainSettings settings, IResource downloadResources, SystemParametersConstructor systemParams)
- {
- var p = systemParams.Create();
+ public PerformanceSettings(Configuration.MainSettings settings, IResource downloadResources, SystemParametersConstructor systemParams)
+ {
+ var p = systemParams.Create();
- _settings = settings;
- }
+ _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..b1c9fa1a7 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
-
@@ -258,7 +534,7 @@
+
+ -->
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ x:Name="Border"
+ Background="{TemplateBinding Background}"
+ BorderBrush="{TemplateBinding BorderBrush}"
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="8">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
@@ -1299,7 +1701,7 @@
-
+
+
+
+
-
-
+
+
@@ -1333,33 +1744,113 @@
+
+
+
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -1889,14 +2389,14 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
- CornerRadius="6">
+ CornerRadius="4">
+ CornerRadius="4" />
+
+
+
-
-
+
-
-
@@ -3476,11 +4201,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..07baa0485 100644
--- a/Wabbajack.App.Wpf/Util/SystemParametersConstructor.cs
+++ b/Wabbajack.App.Wpf/Util/SystemParametersConstructor.cs
@@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.CompilerServices;
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;
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 StatusList { get; }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/MainWindowVM.cs b/Wabbajack.App.Wpf/View Models/MainWindowVM.cs
deleted file mode 100644
index cd1430ed3..000000000
--- a/Wabbajack.App.Wpf/View Models/MainWindowVM.cs
+++ /dev/null
@@ -1,281 +0,0 @@
-using DynamicData.Binding;
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Input;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Orc.FileAssociation;
-using Wabbajack.Common;
-using Wabbajack.DTOs.Interventions;
-using Wabbajack.Interventions;
-using Wabbajack.Messages;
-using Wabbajack.Models;
-using Wabbajack.Networking.WabbajackClientApi;
-using Wabbajack.Paths;
-using Wabbajack.Paths.IO;
-using Wabbajack.UserIntervention;
-using Wabbajack.View_Models;
-
-namespace Wabbajack
-{
- ///
- /// Main View Model for the application.
- /// Keeps track of which sub view is being shown in the window, and has some singleton wiring like WorkQueue and Logging.
- ///
- public class MainWindowVM : ViewModel
- {
- public MainWindow MainWindow { get; }
-
- [Reactive]
- public ViewModel ActivePane { get; private set; }
-
- public ObservableCollectionExtended Log { get; } = new ObservableCollectionExtended();
-
- public readonly CompilerVM Compiler;
- public readonly InstallerVM Installer;
- public readonly SettingsVM SettingsPane;
- public readonly ModListGalleryVM Gallery;
- public readonly ModeSelectionVM ModeSelectionVM;
- public readonly WebBrowserVM WebBrowserVM;
- public readonly Lazy ModListContentsVM;
- public readonly UserInterventionHandlers UserInterventionHandlers;
- private readonly Client _wjClient;
- private readonly ILogger _logger;
- private readonly ResourceMonitor _resourceMonitor;
-
- private List PreviousPanes = new();
- private readonly IServiceProvider _serviceProvider;
-
- public ICommand CopyVersionCommand { get; }
- public ICommand ShowLoginManagerVM { get; }
- public ICommand OpenSettingsCommand { get; }
-
- public string VersionDisplay { get; }
-
- [Reactive]
- public string ResourceStatus { get; set; }
-
- [Reactive]
- public string AppName { get; set; }
-
- [Reactive]
- public bool UpdateAvailable { get; private set; }
-
- public MainWindowVM(ILogger logger, Client wjClient,
- IServiceProvider serviceProvider, ModeSelectionVM modeSelectionVM, ModListGalleryVM modListGalleryVM, ResourceMonitor resourceMonitor,
- InstallerVM installer, CompilerVM compilerVM, SettingsVM settingsVM, WebBrowserVM webBrowserVM)
- {
- _logger = logger;
- _wjClient = wjClient;
- _resourceMonitor = resourceMonitor;
- _serviceProvider = serviceProvider;
- ConverterRegistration.Register();
- Installer = installer;
- Compiler = compilerVM;
- SettingsPane = settingsVM;
- Gallery = modListGalleryVM;
- ModeSelectionVM = modeSelectionVM;
- WebBrowserVM = webBrowserVM;
- ModListContentsVM = new Lazy(() => new ModListContentsVM(serviceProvider.GetRequiredService>(), this));
- UserInterventionHandlers = new UserInterventionHandlers(serviceProvider.GetRequiredService>(), this);
-
- MessageBus.Current.Listen()
- .Subscribe(m => HandleNavigateTo(m.Screen))
- .DisposeWith(CompositeDisposable);
-
- MessageBus.Current.Listen()
- .Subscribe(m => HandleNavigateTo(m.ViewModel))
- .DisposeWith(CompositeDisposable);
-
- MessageBus.Current.Listen()
- .Subscribe(HandleNavigateBack)
- .DisposeWith(CompositeDisposable);
-
- MessageBus.Current.Listen