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..3775699ee 100644
--- a/Wabbajack.App.Wpf/App.xaml.cs
+++ b/Wabbajack.App.Wpf/App.xaml.cs
@@ -26,176 +26,180 @@
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 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;
+
+ try
+ {
+ var identity = WindowsIdentity.GetCurrent();
+ var owner = identity.Owner;
+ if (owner is not null) return owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
- var principle = new WindowsPrincipal(identity);
- return principle.IsInRole(WindowsBuiltInRole.Administrator);
+ var principle = new WindowsPrincipal(identity);
+ return principle.IsInRole(WindowsBuiltInRole.Administrator);
- }
- catch (Exception)
- {
- return false;
- }
}
-
- 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.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());
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddTransient();
+ 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();
+ 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..c1f2643f4 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;
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..cffe1050b 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;
@@ -15,7 +10,6 @@
using Wabbajack.Common;
using Wabbajack.Downloaders.IPS4OAuth2Downloader;
using Wabbajack.DTOs.Logins;
-using Wabbajack.Messages;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.UserIntervention;
diff --git a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
index 27ff83543..ca7437c5c 100644
--- a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
+++ b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs
@@ -39,7 +39,7 @@ public NexusLoginManager(ILogger logger, ITokenProvider await RefreshTokenState());
+ Task.Run(RefreshTokenState);
ClearLogin = ReactiveCommand.CreateFromTask(async () =>
{
diff --git a/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs
index 62a13e260..ab2260342 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;
@@ -14,7 +10,6 @@
using Wabbajack.Common;
using Wabbajack.Downloaders.IPS4OAuth2Downloader;
using Wabbajack.DTOs.Logins;
-using Wabbajack.Messages;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.UserIntervention;
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/ShowFloatingWindow.cs b/Wabbajack.App.Wpf/Messages/ShowFloatingWindow.cs
new file mode 100644
index 000000000..a49212321
--- /dev/null
+++ b/Wabbajack.App.Wpf/Messages/ShowFloatingWindow.cs
@@ -0,0 +1,26 @@
+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/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..5296661b9 100644
--- a/Wabbajack.App.Wpf/Models/ResourceMonitor.cs
+++ b/Wabbajack.App.Wpf/Models/ResourceMonitor.cs
@@ -14,7 +14,7 @@ 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;
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 4ad1517c3..1094667e1 100644
--- a/Wabbajack.App.Wpf/Settings.cs
+++ b/Wabbajack.App.Wpf/Settings.cs
@@ -4,51 +4,58 @@
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;
- private readonly int _defaultMaximumMemoryPerDownloadThreadMb;
+public class PerformanceSettings : ViewModel
+{
+ private readonly Configuration.MainSettings _settings;
+ private readonly int _defaultMaximumMemoryPerDownloadThreadMb;
- 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;
- // Split half of available memory among download threads
- _defaultMaximumMemoryPerDownloadThreadMb = (int)(p.SystemMemorySize / downloadResources.MaxTasks / 1024 / 1024) / 2;
- _maximumMemoryPerDownloadThreadMb = settings.PerformanceSettings.MaximumMemoryPerDownloadThreadMb;
+ _settings = settings;
+ // Split half of available memory among download threads
+ _defaultMaximumMemoryPerDownloadThreadMb = (int)(p.SystemMemorySize / downloadResources.MaxTasks / 1024 / 1024) / 2;
+ _maximumMemoryPerDownloadThreadMb = settings.PerformanceSettings.MaximumMemoryPerDownloadThreadMb;
- if (MaximumMemoryPerDownloadThreadMb < 0)
- {
- ResetMaximumMemoryPerDownloadThreadMb();
- }
+ if (MaximumMemoryPerDownloadThreadMb < 0)
+ {
+ ResetMaximumMemoryPerDownloadThreadMb();
}
+ }
- private int _maximumMemoryPerDownloadThreadMb;
+ private int _maximumMemoryPerDownloadThreadMb;
- public int MaximumMemoryPerDownloadThreadMb
+ public int MaximumMemoryPerDownloadThreadMb
+ {
+ get => _maximumMemoryPerDownloadThreadMb;
+ set
{
- get => _maximumMemoryPerDownloadThreadMb;
- set
- {
- RaiseAndSetIfChanged(ref _maximumMemoryPerDownloadThreadMb, value);
- _settings.PerformanceSettings.MaximumMemoryPerDownloadThreadMb = value;
- }
+ RaiseAndSetIfChanged(ref _maximumMemoryPerDownloadThreadMb, value);
+ _settings.PerformanceSettings.MaximumMemoryPerDownloadThreadMb = value;
}
+ }
- public void ResetMaximumMemoryPerDownloadThreadMb()
- {
- MaximumMemoryPerDownloadThreadMb = _defaultMaximumMemoryPerDownloadThreadMb;
- }
+ public void ResetMaximumMemoryPerDownloadThreadMb()
+ {
+ MaximumMemoryPerDownloadThreadMb = _defaultMaximumMemoryPerDownloadThreadMb;
}
}
+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..fbec718fd 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
+ #40FFFFFF
- #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
-
-
+
@@ -114,10 +145,19 @@
+
+
+
+
+
+
+
+
+
@@ -137,6 +177,9 @@
+
+
+
@@ -146,42 +189,56 @@
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
+
+
-
+
@@ -191,13 +248,13 @@
-
-
+
+
-
+
-
-
+
+
@@ -209,16 +266,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 +504,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 +531,7 @@
+
+ -->
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ x:Name="Border"
+ Background="{TemplateBinding Background}"
+ BorderBrush="{TemplateBinding BorderBrush}"
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="8">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
@@ -1299,7 +1695,7 @@
-
+
+
+
+
-
-
+
+
@@ -1333,33 +1738,82 @@
+
+
+
+
-
-
+
+
-
+
+
+
+
+
+
+
-
-
@@ -1889,14 +2346,14 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
- CornerRadius="6">
+ CornerRadius="4">
+ CornerRadius="4" />
+
+
+
-
+
-
-
@@ -3476,11 +4158,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs
index 9373e42a1..f02d1dcdc 100644
--- a/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs
@@ -1,9 +1,6 @@
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;
diff --git a/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs
index 7bf069bf6..95af22e63 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;
diff --git a/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs
index a54ac5449..cf04482d2 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;
diff --git a/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs
index b41e736cf..fa42e877b 100644
--- a/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs
+++ b/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs
@@ -1,8 +1,6 @@
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;
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..aff2add7e
--- /dev/null
+++ b/Wabbajack.App.Wpf/Util/DriveHelper.cs
@@ -0,0 +1,402 @@
+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 _diskCache = new Dictionary();
+ private static Dictionary _partCache = new Dictionary();
+
+ ///
+ /// All the physical disks by disk number
+ ///
+ public static Dictionary PhysicalDisks
+ {
+ get
+ {
+ if (_diskCache.Count == 0)
+ _diskCache = GetPhysicalDisks();
+ return _diskCache;
+ }
+ }
+
+ ///
+ /// All the physical disks by partition (drive letter)
+ ///
+ public static Dictionary Partitions
+ {
+ get
+ {
+ if (_partCache.Count == 0)
+ _partCache = GetPartitions();
+ return _partCache;
+ }
+ }
+
+ public static void ReloadPhysicalDisks()
+ {
+ if (_diskCache.Count > 0)
+ _diskCache.Clear();
+ _diskCache = 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..43c0c059a 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;
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/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..ee44970cc 100644
--- a/Wabbajack.App.Wpf/Util/UIUtils.cs
+++ b/Wabbajack.App.Wpf/Util/UIUtils.cs
@@ -1,60 +1,61 @@
-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;
}
+ catch (Exception)
+ {
+ bitmapImage = default;
+ return false;
+ }
+ }
+
public static void OpenWebsite(Uri url)
{
@@ -63,7 +64,7 @@ public static void OpenWebsite(Uri url)
CreateNoWindow = true,
});
}
-
+
public static void OpenFolder(AbsolutePath path)
{
string folderPath = path.ToString();
@@ -80,111 +81,80 @@ public static void OpenFolder(AbsolutePath path)
});
}
- 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)
- {
- return obs
- .ObserveOn(RxApp.TaskpoolScheduler)
- .SelectTask(async url =>
+ 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);
- }
+ var (cached, cachedImg) = await icm.Get(url);
+ if (cached) return cachedImg;
- ret.Seek(0, SeekOrigin.Begin);
+ await using var stream = await client.GetStreamAsync(url);
- await WriteCachedImage(url, ret.ToArray());
- return (ll, ret);
- }
- catch (Exception ex)
+ 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;
- }
-
- return String.Format("{0:0.##} {1}", dblSByte, Suffix[i]);
- }
+ CreateNoWindow = true,
+ });
+ }
- public static void OpenFile(AbsolutePath file)
- {
- Process.Start(new ProcessStartInfo("cmd.exe", $"/c start \"\" \"{file}\"")
- {
- CreateNoWindow = true,
- });
- }
+ 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}";
}
-}
+}
\ No newline at end of file
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/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()
- .ObserveOnGuiThread()
- .Subscribe(HandleSpawnBrowserWindow)
- .DisposeWith(CompositeDisposable);
-
- _resourceMonitor.Updates
- .Select(r => string.Join(", ", r.Where(r => r.Throughput > 0)
- .Select(s => $"{s.Name} - {s.Throughput.ToFileSizeString()}/sec")))
- .BindToStrict(this, view => view.ResourceStatus);
-
-
- if (IsStartingFromModlist(out var path))
- {
- LoadModlistForInstalling.Send(path, null);
- NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Installer);
- }
- else
- {
- // Start on mode selection
- NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
- }
-
- try
- {
- var assembly = Assembly.GetExecutingAssembly();
- var assemblyLocation = assembly.Location;
- var processLocation = Process.GetCurrentProcess().MainModule?.FileName ?? throw new Exception("Process location is unavailable!");
-
- _logger.LogInformation("Assembly Location: {AssemblyLocation}", assemblyLocation);
- _logger.LogInformation("Process Location: {ProcessLocation}", processLocation);
-
- var fvi = FileVersionInfo.GetVersionInfo(string.IsNullOrWhiteSpace(assemblyLocation) ? processLocation : assemblyLocation);
- Consts.CurrentMinimumWabbajackVersion = Version.Parse(fvi.FileVersion);
- VersionDisplay = $"v{fvi.FileVersion}";
- AppName = "WABBAJACK " + VersionDisplay;
- _logger.LogInformation("Wabbajack Version: {FileVersion}", fvi.FileVersion);
-
- Task.Run(() => _wjClient.SendMetric("started_wabbajack", fvi.FileVersion)).FireAndForget();
- Task.Run(() => _wjClient.SendMetric("started_sha", ThisAssembly.Git.Sha));
-
- // setup file association
- try
- {
- var applicationRegistrationService = _serviceProvider.GetRequiredService();
-
- var applicationInfo = new ApplicationInfo("Wabbajack", "Wabbajack", "Wabbajack", processLocation);
- applicationInfo.SupportedExtensions.Add("wabbajack");
- applicationRegistrationService.RegisterApplication(applicationInfo);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "While setting up file associations");
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "During App configuration");
- VersionDisplay = "ERROR";
- }
- CopyVersionCommand = ReactiveCommand.Create(() =>
- {
- Clipboard.SetText($"Wabbajack {VersionDisplay}\n{ThisAssembly.Git.Sha}");
- });
- OpenSettingsCommand = ReactiveCommand.Create(
- canExecute: this.WhenAny(x => x.ActivePane)
- .Select(active => !object.ReferenceEquals(active, SettingsPane)),
- execute: () => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Settings));
- }
-
- private void HandleNavigateTo(ViewModel objViewModel)
- {
-
- ActivePane = objViewModel;
- }
-
- private void HandleNavigateBack(NavigateBack navigateBack)
- {
- ActivePane = PreviousPanes.Last();
- PreviousPanes.RemoveAt(PreviousPanes.Count - 1);
- }
-
- private void HandleManualDownload(ManualDownload manualDownload)
- {
- var handler = _serviceProvider.GetRequiredService();
- handler.Intervention = manualDownload;
- //MessageBus.Current.SendMessage(new OpenBrowserTab(handler));
- }
-
- private void HandleManualBlobDownload(ManualBlobDownload manualDownload)
- {
- var handler = _serviceProvider.GetRequiredService();
- handler.Intervention = manualDownload;
- //MessageBus.Current.SendMessage(new OpenBrowserTab(handler));
- }
-
- private void HandleSpawnBrowserWindow(SpawnBrowserWindow msg)
- {
- var window = _serviceProvider.GetRequiredService();
- window.DataContext = msg.Vm;
- window.Show();
- }
-
- private void HandleNavigateTo(NavigateToGlobal.ScreenType s)
- {
- if (s is NavigateToGlobal.ScreenType.Settings)
- PreviousPanes.Add(ActivePane);
-
- ActivePane = s switch
- {
- NavigateToGlobal.ScreenType.ModeSelectionView => ModeSelectionVM,
- NavigateToGlobal.ScreenType.ModListGallery => Gallery,
- NavigateToGlobal.ScreenType.Installer => Installer,
- NavigateToGlobal.ScreenType.Compiler => Compiler,
- NavigateToGlobal.ScreenType.Settings => SettingsPane,
- _ => ActivePane
- };
- }
-
-
- private static bool IsStartingFromModlist(out AbsolutePath modlistPath)
- {
- var args = Environment.GetCommandLineArgs();
- if (args.Length == 2)
- {
- var arg = args[1].ToAbsolutePath();
- if (arg.FileExists() && arg.Extension == Ext.Wabbajack)
- {
- modlistPath = arg;
- return true;
- }
- }
-
- modlistPath = default;
- return false;
- }
-
- public void CancelRunningTasks(TimeSpan timeout)
- {
- var endTime = DateTime.Now.Add(timeout);
- var cancellationTokenSource = _serviceProvider.GetRequiredService();
- cancellationTokenSource.Cancel();
-
- bool IsInstalling() => Installer.InstallState is InstallState.Installing;
-
- while (DateTime.Now < endTime && IsInstalling())
- {
- Thread.Sleep(TimeSpan.FromSeconds(1));
- }
- }
-
- /*
- public void NavigateTo(ViewModel vm)
- {
- ActivePane = vm;
- }*/
-
- /*
- public void NavigateTo(T vm)
- where T : ViewModel, IBackNavigatingVM
- {
- vm.NavigateBackTarget = ActivePane;
- ActivePane = vm;
- }*/
-
- public async Task ShutdownApplication()
- {
- /*
- Dispose();
- Settings.PosX = MainWindow.Left;
- Settings.PosY = MainWindow.Top;
- Settings.Width = MainWindow.Width;
- Settings.Height = MainWindow.Height;
- await MainSettings.SaveSettings(Settings);
- Application.Current.Shutdown();
- */
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/ModListContentsVM.cs b/Wabbajack.App.Wpf/View Models/ModListContentsVM.cs
deleted file mode 100644
index 558f67772..000000000
--- a/Wabbajack.App.Wpf/View Models/ModListContentsVM.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System;
-using System.Collections.ObjectModel;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Text.RegularExpressions;
-using DynamicData;
-using DynamicData.Binding;
-using Microsoft.Extensions.Logging;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Common;
-using Wabbajack.DTOs;
-using Wabbajack.DTOs.ServerResponses;
-
-namespace Wabbajack.View_Models
-{
- public class ModListContentsVM : BackNavigatingVM
- {
- private MainWindowVM _mwvm;
- [Reactive]
- public string Name { get; set; }
-
- [Reactive]
- public ObservableCollection Status { get; set; }
-
- [Reactive]
- public string SearchString { get; set; }
-
- private readonly ReadOnlyObservableCollection _archives;
- public ReadOnlyObservableCollection Archives => _archives;
-
- private static readonly Regex NameMatcher = new(@"(?<=\.)[^\.]+(?=\+State)", RegexOptions.Compiled);
- private readonly ILogger _logger;
-
- public ModListContentsVM(ILogger logger, MainWindowVM mwvm) : base(logger)
- {
- _logger = logger;
- _mwvm = mwvm;
- Status = new ObservableCollectionExtended();
-
- string TransformClassName(Archive a)
- {
- var cname = a.State.GetType().FullName;
- if (cname == null) return null;
-
- var match = NameMatcher.Match(cname);
- return match.Success ? match.ToString() : null;
- }
-
- this.Status
- .ToObservableChangeSet()
- .Transform(a => new ModListArchive
- {
- Name = a.Name,
- Size = a.Archive?.Size ?? 0,
- Downloader = TransformClassName(a.Archive) ?? "Unknown",
- Hash = a.Archive!.Hash.ToBase64()
- })
- .Filter(this.WhenAny(x => x.SearchString)
- .StartWith("")
- .Throttle(TimeSpan.FromMilliseconds(250))
- .Select>(s => (ModListArchive ar) =>
- string.IsNullOrEmpty(s) ||
- ar.Name.ContainsCaseInsensitive(s) ||
- ar.Downloader.ContainsCaseInsensitive(s) ||
- ar.Hash.ContainsCaseInsensitive(s) ||
- ar.Size.ToString() == s ||
- ar.Url.ContainsCaseInsensitive(s)))
- .ObserveOnGuiThread()
- .Bind(out _archives)
- .Subscribe()
- .DisposeWith(CompositeDisposable);
- }
- }
-
- public class ModListArchive
- {
- public string Name { get; set; }
- public long Size { get; set; }
- public string Url { get; set; }
- public string Downloader { get; set; }
- public string Hash { get; set; }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/ModListVM.cs b/Wabbajack.App.Wpf/View Models/ModListVM.cs
deleted file mode 100644
index 1056f97e5..000000000
--- a/Wabbajack.App.Wpf/View Models/ModListVM.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using ReactiveUI;
-using System;
-using System.IO;
-using System.IO.Compression;
-using System.Reactive;
-using System.Reactive.Linq;
-using System.Threading.Tasks;
-using System.Windows.Media.Imaging;
-using Microsoft.Extensions.Logging;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Common;
-using Wabbajack.DTOs;
-using Wabbajack.DTOs.JsonConverters;
-using Wabbajack.Installer;
-using Wabbajack;
-using Wabbajack.Paths;
-using Wabbajack.Paths.IO;
-using Consts = Wabbajack.Consts;
-
-namespace Wabbajack
-{
- public class ModListVM : ViewModel
- {
- private readonly DTOSerializer _dtos;
- private readonly ILogger _logger;
- public ModList SourceModList { get; private set; }
- public ModlistMetadata SourceModListMetadata { get; private set; }
-
- [Reactive]
- public Exception Error { get; set; }
- public AbsolutePath ModListPath { get; }
- public string Name => SourceModList?.Name;
- public string Readme => SourceModList?.Readme;
- public string Author => SourceModList?.Author;
- public string Description => SourceModList?.Description;
- public Uri Website => SourceModList?.Website;
- public Version Version => SourceModList?.Version;
- public Version WabbajackVersion => SourceModList?.WabbajackVersion;
- public bool IsNSFW => SourceModList?.IsNSFW ?? false;
-
- // Image isn't exposed as a direct property, but as an observable.
- // This acts as a caching mechanism, as interested parties will trigger it to be created,
- // and the cached image will automatically be released when the last interested party is gone.
- public IObservable ImageObservable { get; }
-
- public ModListVM(ILogger logger, AbsolutePath modListPath, DTOSerializer dtos)
- {
- _dtos = dtos;
- _logger = logger;
-
- ModListPath = modListPath;
-
- Task.Run(async () =>
- {
- try
- {
- SourceModList = await StandardInstaller.LoadFromFile(_dtos, modListPath);
- var metadataPath = modListPath.WithExtension(Ext.ModlistMetadataExtension);
- if (metadataPath.FileExists())
- {
- try
- {
- SourceModListMetadata = await metadataPath.FromJson();
- }
- catch (Exception)
- {
- SourceModListMetadata = null;
- }
- }
- }
- catch (Exception ex)
- {
- Error = ex;
- _logger.LogError(ex, "Exception while loading the modlist!");
- }
- });
-
- ImageObservable = Observable.Return(Unit.Default)
- // Download and retrieve bytes on background thread
- .ObserveOn(RxApp.TaskpoolScheduler)
- .SelectAsync(async filePath =>
- {
- try
- {
- await using var fs = ModListPath.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
- using var ar = new ZipArchive(fs, ZipArchiveMode.Read);
- var ms = new MemoryStream();
- var entry = ar.GetEntry("modlist-image.png");
- if (entry == null) return default(MemoryStream);
- await using var e = entry.Open();
- e.CopyTo(ms);
- return ms;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Exception while caching Mod List image {Name}", Name);
- return default(MemoryStream);
- }
- })
- // Create Bitmap image on GUI thread
- .ObserveOnGuiThread()
- .Select(memStream =>
- {
- if (memStream == null) return default(BitmapImage);
- try
- {
- return UIUtils.BitmapImageFromStream(memStream);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Exception while caching Mod List image {Name}", Name);
- return default(BitmapImage);
- }
- })
- // If ever would return null, show WJ logo instead
- .Select(x => x ?? ResourceLinks.WabbajackLogoNoText.Value)
- .Replay(1)
- .RefCount();
- }
-
- public void OpenReadme()
- {
- if (string.IsNullOrEmpty(Readme)) return;
- UIUtils.OpenWebsite(new Uri(Readme));
- }
-
- public override void Dispose()
- {
- base.Dispose();
- // Just drop reference explicitly, as it's large, so it can be GCed
- // Even if someone is holding a stale reference to the VM
- SourceModList = null;
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/ModVM.cs b/Wabbajack.App.Wpf/View Models/ModVM.cs
deleted file mode 100644
index 14b0d80a9..000000000
--- a/Wabbajack.App.Wpf/View Models/ModVM.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using ReactiveUI;
-using System;
-using System.Reactive.Linq;
-using System.Windows.Media.Imaging;
-using Microsoft.Extensions.Logging;
-using Wabbajack.DTOs.DownloadStates;
-using Wabbajack;
-
-namespace Wabbajack
-{
- public class ModVM : ViewModel
- {
- private readonly ILogger _logger;
- public IMetaState State { get; }
-
- // Image isn't exposed as a direct property, but as an observable.
- // This acts as a caching mechanism, as interested parties will trigger it to be created,
- // and the cached image will automatically be released when the last interested party is gone.
- public IObservable ImageObservable { get; }
-
- public ModVM(ILogger logger, IMetaState state)
- {
- _logger = logger;
- State = state;
-
- ImageObservable = Observable.Return(State.ImageURL?.ToString())
- .ObserveOn(RxApp.TaskpoolScheduler)
- .DownloadBitmapImage(ex => _logger.LogError(ex, "Skipping slide for mod {Name}", State.Name), LoadingLock)
- .Replay(1)
- .RefCount(TimeSpan.FromMilliseconds(5000));
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/ModeSelectionVM.cs b/Wabbajack.App.Wpf/View Models/ModeSelectionVM.cs
deleted file mode 100644
index 77ca9085f..000000000
--- a/Wabbajack.App.Wpf/View Models/ModeSelectionVM.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using System;
-using System.IO;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Linq;
-using System.Windows.Input;
-using Wabbajack.Common;
-using Wabbajack;
-using Wabbajack.Messages;
-using Wabbajack.Paths.IO;
-
-namespace Wabbajack
-{
- public class ModeSelectionVM : ViewModel
- {
- public ICommand BrowseCommand { get; }
- public ICommand InstallCommand { get; }
- public ICommand CompileCommand { get; }
-
- public ReactiveCommand UpdateCommand { get; }
-
- public ModeSelectionVM()
- {
- InstallCommand = ReactiveCommand.Create(() =>
- {
- LoadLastLoadedModlist.Send();
- NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Installer);
- });
- CompileCommand = ReactiveCommand.Create(() => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Compiler));
- BrowseCommand = ReactiveCommand.Create(() => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModListGallery));
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/Settings/AuthorFilesVM.cs b/Wabbajack.App.Wpf/View Models/Settings/AuthorFilesVM.cs
deleted file mode 100644
index 4b909dce8..000000000
--- a/Wabbajack.App.Wpf/View Models/Settings/AuthorFilesVM.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Input;
-using Microsoft.Extensions.Logging;
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack;
-using Wabbajack.Networking.WabbajackClientApi;
-using Wabbajack.Services.OSIntegrated.TokenProviders;
-
-namespace Wabbajack.View_Models.Settings
-{
- public class AuthorFilesVM : BackNavigatingVM
- {
- [Reactive]
- public Visibility IsVisible { get; set; }
-
- public ICommand SelectFile { get; }
- public ICommand HyperlinkCommand { get; }
- public IReactiveCommand Upload { get; }
- public IReactiveCommand ManageFiles { get; }
-
- [Reactive] public double UploadProgress { get; set; }
- [Reactive] public string FinalUrl { get; set; }
- public FilePickerVM Picker { get;}
-
- private Subject _isUploading = new();
- private readonly WabbajackApiTokenProvider _token;
- private readonly Client _wjClient;
- private IObservable IsUploading { get; }
-
- public AuthorFilesVM(ILogger logger, WabbajackApiTokenProvider token, Client wjClient, SettingsVM vm) : base(logger)
- {
- _token = token;
- _wjClient = wjClient;
- IsUploading = _isUploading;
- Picker = new FilePickerVM(this);
-
-
- IsVisible = Visibility.Hidden;
-
- Task.Run(async () =>
- {
- var isAuthor = !string.IsNullOrWhiteSpace((await _token.Get())?.AuthorKey);
- IsVisible = isAuthor ? Visibility.Visible : Visibility.Collapsed;
- });
-
- SelectFile = Picker.ConstructTypicalPickerCommand(IsUploading.StartWith(false).Select(u => !u));
-
- HyperlinkCommand = ReactiveCommand.Create(() => Clipboard.SetText(FinalUrl));
-
- ManageFiles = ReactiveCommand.Create(async () =>
- {
- var authorApiKey = (await token.Get())!.AuthorKey;
- UIUtils.OpenWebsite(new Uri($"{Consts.WabbajackBuildServerUri}author_controls/login/{authorApiKey}"));
- });
-
- Upload = ReactiveCommand.Create(async () =>
- {
- _isUploading.OnNext(true);
- try
- {
- var (progress, task) = await _wjClient.UploadAuthorFile(Picker.TargetPath);
-
- var disposable = progress.Subscribe(m =>
- {
- FinalUrl = m.Message;
- UploadProgress = (double)m.PercentDone;
- });
-
- var final = await task;
- disposable.Dispose();
- FinalUrl = final.ToString();
- }
- catch (Exception ex)
- {
- FinalUrl = ex.ToString();
- }
- finally
- {
- FinalUrl = FinalUrl.Replace(" ", "%20");
- _isUploading.OnNext(false);
- }
- }, IsUploading.StartWith(false).Select(u => !u)
- .CombineLatest(Picker.WhenAnyValue(t => t.TargetPath).Select(f => f != default),
- (a, b) => a && b));
- }
-
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/Settings/LoginManagerVM.cs b/Wabbajack.App.Wpf/View Models/Settings/LoginManagerVM.cs
deleted file mode 100644
index f2021215d..000000000
--- a/Wabbajack.App.Wpf/View Models/Settings/LoginManagerVM.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Extensions.Logging;
-using Wabbajack.LoginManagers;
-
-namespace Wabbajack
-{
-
- public class LoginManagerVM : BackNavigatingVM
- {
- public LoginTargetVM[] Logins { get; }
-
- public LoginManagerVM(ILogger logger, SettingsVM settingsVM, IEnumerable logins)
- : base(logger)
- {
- Logins = logins.Select(l => new LoginTargetVM(l)).ToArray();
- }
-
- }
-
- public class LoginTargetVM : ViewModel
- {
- public INeedsLogin Login { get; }
- public LoginTargetVM(INeedsLogin login)
- {
- Login = login;
- }
- }
-
-}
diff --git a/Wabbajack.App.Wpf/View Models/Settings/SettingsVM.cs b/Wabbajack.App.Wpf/View Models/Settings/SettingsVM.cs
deleted file mode 100644
index a32855cec..000000000
--- a/Wabbajack.App.Wpf/View Models/Settings/SettingsVM.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Reflection;
-using System.Threading.Tasks;
-using System.Windows.Input;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using ReactiveUI;
-using Wabbajack.Common;
-using Wabbajack.Downloaders;
-using Wabbajack.LoginManagers;
-using Wabbajack.Messages;
-using Wabbajack.Networking.WabbajackClientApi;
-using Wabbajack.RateLimiter;
-using Wabbajack.Services.OSIntegrated;
-using Wabbajack.Services.OSIntegrated.TokenProviders;
-using Wabbajack.Util;
-using Wabbajack.View_Models.Settings;
-
-namespace Wabbajack
-{
- public class SettingsVM : BackNavigatingVM
- {
- private readonly Configuration.MainSettings _settings;
- private readonly SettingsManager _settingsManager;
-
- public LoginManagerVM Login { get; }
- public PerformanceSettings Performance { get; }
- public AuthorFilesVM AuthorFile { get; }
-
- public ICommand OpenTerminalCommand { get; }
-
- public SettingsVM(ILogger logger, IServiceProvider provider)
- : base(logger)
- {
- _settings = provider.GetRequiredService();
- _settingsManager = provider.GetRequiredService();
-
- Login = new LoginManagerVM(provider.GetRequiredService>(), this,
- provider.GetRequiredService>());
- AuthorFile = new AuthorFilesVM(provider.GetRequiredService>()!,
- provider.GetRequiredService()!, provider.GetRequiredService()!, this);
- OpenTerminalCommand = ReactiveCommand.CreateFromTask(OpenTerminal);
- Performance = new PerformanceSettings(
- _settings,
- provider.GetRequiredService>(),
- provider.GetRequiredService());
- BackCommand = ReactiveCommand.Create(() =>
- {
- NavigateBack.Send();
- Unload();
- });
- }
-
- public override void Unload()
- {
- _settingsManager.Save(Configuration.MainSettings.SettingsFileName, _settings).FireAndForget();
-
- base.Unload();
- }
-
- private async Task OpenTerminal()
- {
- var process = new ProcessStartInfo
- {
- FileName = "cmd.exe",
- WorkingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!
- };
- Process.Start(process);
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/UserIntervention/ConfirmUpdateOfExistingInstallVM.cs b/Wabbajack.App.Wpf/View Models/UserIntervention/ConfirmUpdateOfExistingInstallVM.cs
deleted file mode 100644
index ece18fe01..000000000
--- a/Wabbajack.App.Wpf/View Models/UserIntervention/ConfirmUpdateOfExistingInstallVM.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Wabbajack.Common;
-using Wabbajack;
-using Wabbajack.DTOs.Interventions;
-using Wabbajack.Interventions;
-
-namespace Wabbajack
-{
- public class ConfirmUpdateOfExistingInstallVM : ViewModel, IUserIntervention
- {
- public ConfirmUpdateOfExistingInstall Source { get; }
-
- public MO2InstallerVM Installer { get; }
-
- public bool Handled => ((IUserIntervention)Source).Handled;
- public CancellationToken Token { get; }
- public void SetException(Exception exception)
- {
- throw new NotImplementedException();
- }
-
- public int CpuID => 0;
-
- public DateTime Timestamp => DateTime.Now;
-
- public string ShortDescription => "Short Desc";
-
- public string ExtendedDescription => "Extended Desc";
-
- public ConfirmUpdateOfExistingInstallVM(MO2InstallerVM installer, ConfirmUpdateOfExistingInstall confirm)
- {
- Source = confirm;
- Installer = installer;
- }
-
- public void Cancel()
- {
- ((IUserIntervention)Source).Cancel();
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs b/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs
deleted file mode 100644
index 19a49a1a5..000000000
--- a/Wabbajack.App.Wpf/View Models/UserInterventionHandlers.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using Microsoft.Extensions.Logging;
-using ReactiveUI;
-using Wabbajack.Common;
-using Wabbajack;
-using Wabbajack.DTOs.Interventions;
-using Wabbajack.Interventions;
-using Wabbajack.Messages;
-
-namespace Wabbajack
-{
- public class UserInterventionHandlers
- {
- public MainWindowVM MainWindow { get; }
- private AsyncLock _browserLock = new();
- private readonly ILogger _logger;
-
- public UserInterventionHandlers(ILogger logger, MainWindowVM mvm)
- {
- _logger = logger;
- MainWindow = mvm;
- }
-
- private async Task WrapBrowserJob(IUserIntervention intervention, WebBrowserVM vm, Func toDo)
- {
- var wait = await _browserLock.WaitAsync();
- var cancel = new CancellationTokenSource();
- var oldPane = MainWindow.ActivePane;
-
- // TODO: FIX using var vm = await WebBrowserVM.GetNew(_logger);
- NavigateTo.Send(vm);
- vm.BackCommand = ReactiveCommand.Create(() =>
- {
- cancel.Cancel();
- NavigateTo.Send(oldPane);
- intervention.Cancel();
- });
-
- try
- {
- await toDo(vm, cancel);
- }
- catch (TaskCanceledException)
- {
- intervention.Cancel();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "During Web browser job");
- intervention.Cancel();
- }
- finally
- {
- wait.Dispose();
- }
-
- NavigateTo.Send(oldPane);
- }
-
- public async Task Handle(IStatusMessage msg)
- {
- switch (msg)
- {
- /*
- case RequestNexusAuthorization c:
- await WrapBrowserJob(c, async (vm, cancel) =>
- {
- await vm.Driver.WaitForInitialized();
- var key = await NexusApiClient.SetupNexusLogin(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token);
- c.Resume(key);
- });
- break;
- case ManuallyDownloadNexusFile c:
- await WrapBrowserJob(c, (vm, cancel) => HandleManualNexusDownload(vm, cancel, c));
- break;
- case ManuallyDownloadFile c:
- await WrapBrowserJob(c, (vm, cancel) => HandleManualDownload(vm, cancel, c));
- break;
- case AbstractNeedsLoginDownloader.RequestSiteLogin c:
- await WrapBrowserJob(c, async (vm, cancel) =>
- {
- await vm.Driver.WaitForInitialized();
- var data = await c.Downloader.GetAndCacheCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token);
- c.Resume(data);
- });
- break;
- case RequestOAuthLogin oa:
- await WrapBrowserJob(oa, async (vm, cancel) =>
- {
- await OAuthLogin(oa, vm, cancel);
- });
-
-
- break;
- */
- case CriticalFailureIntervention c:
- MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK,
- MessageBoxImage.Error);
- c.Cancel();
- if (c.ExitApplication) await MainWindow.ShutdownApplication();
- break;
- case ConfirmationIntervention c:
- break;
- default:
- throw new NotImplementedException($"No handler for {msg}");
- }
- }
-
- }
-}
diff --git a/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs b/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs
deleted file mode 100644
index 3be45cfb0..000000000
--- a/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using System.Reactive;
-using System.Reactive.Subjects;
-using Microsoft.Extensions.Logging;
-using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
-using Wabbajack.Messages;
-using Wabbajack.Models;
-
-namespace Wabbajack
-{
- public class WebBrowserVM : ViewModel, IBackNavigatingVM, IDisposable
- {
- private readonly ILogger _logger;
- private readonly CefService _cefService;
-
- [Reactive]
- public string Instructions { get; set; }
-
- public dynamic Browser { get; }
- public dynamic Driver { get; set; }
-
- [Reactive]
- public ViewModel NavigateBackTarget { get; set; }
-
- [Reactive]
- public ReactiveCommand BackCommand { get; set; }
-
- public Subject IsBackEnabledSubject { get; } = new Subject();
- public IObservable IsBackEnabled { get; }
-
- public WebBrowserVM(ILogger logger, CefService cefService)
- {
- // CefService is required so that Cef is initalized
- _logger = logger;
- _cefService = cefService;
- Instructions = "Wabbajack Web Browser";
-
- BackCommand = ReactiveCommand.Create(NavigateBack.Send);
- //Browser = cefService.CreateBrowser();
- //Driver = new CefSharpWrapper(_logger, Browser, cefService);
-
- }
-
- public override void Dispose()
- {
- Browser.Dispose();
- base.Dispose();
- }
- }
-}
diff --git a/Wabbajack.App.Wpf/ViewModel.cs b/Wabbajack.App.Wpf/ViewModel.cs
deleted file mode 100644
index 8eb82a25c..000000000
--- a/Wabbajack.App.Wpf/ViewModel.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Newtonsoft.Json;
-using ReactiveUI;
-using System;
-using System.Collections.Generic;
-using System.Reactive.Disposables;
-using System.Runtime.CompilerServices;
-using Wabbajack.Models;
-
-namespace Wabbajack
-{
- public class ViewModel : ReactiveObject, IDisposable, IActivatableViewModel
- {
- private readonly Lazy _compositeDisposable = new();
- [JsonIgnore]
- public CompositeDisposable CompositeDisposable => _compositeDisposable.Value;
-
- [JsonIgnore] public LoadingLock LoadingLock { get; } = new();
-
- public virtual void Dispose()
- {
- if (_compositeDisposable.IsValueCreated)
- {
- _compositeDisposable.Value.Dispose();
- }
- }
-
- protected void RaiseAndSetIfChanged(
- ref T item,
- T newItem,
- [CallerMemberName] string? propertyName = null)
- {
- if (EqualityComparer.Default.Equals(item, newItem)) return;
- item = newItem;
- this.RaisePropertyChanged(propertyName);
- }
-
- public ViewModelActivator Activator { get; } = new();
- }
-}
diff --git a/Wabbajack.App.Wpf/ViewModels/BackNavigatingVM.cs b/Wabbajack.App.Wpf/ViewModels/BackNavigatingVM.cs
new file mode 100644
index 000000000..aa7791153
--- /dev/null
+++ b/Wabbajack.App.Wpf/ViewModels/BackNavigatingVM.cs
@@ -0,0 +1,73 @@
+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.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/BrowserWindowViewModel.cs b/Wabbajack.App.Wpf/ViewModels/BrowserWindowViewModel.cs
similarity index 97%
rename from Wabbajack.App.Wpf/View Models/BrowserWindowViewModel.cs
rename to Wabbajack.App.Wpf/ViewModels/BrowserWindowViewModel.cs
index 5daee154b..247d4a584 100644
--- a/Wabbajack.App.Wpf/View Models/BrowserWindowViewModel.cs
+++ b/Wabbajack.App.Wpf/ViewModels/BrowserWindowViewModel.cs
@@ -1,20 +1,15 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using HtmlAgilityPack;
using Microsoft.Web.WebView2.Core;
-using Microsoft.Web.WebView2.Wpf;
-using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.Logins;
using Wabbajack.Hashing.xxHash64;
-using Wabbajack.Messages;
using Wabbajack.Paths;
-using Wabbajack.Views;
namespace Wabbajack;
diff --git a/Wabbajack.App.Wpf/ViewModels/CPUDisplayVM.cs b/Wabbajack.App.Wpf/ViewModels/CPUDisplayVM.cs
new file mode 100644
index 000000000..6124d8271
--- /dev/null
+++ b/Wabbajack.App.Wpf/ViewModels/CPUDisplayVM.cs
@@ -0,0 +1,23 @@
+using System;
+using ReactiveUI.Fody.Helpers;
+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/ViewModels/Compiler/BaseCompilerVM.cs b/Wabbajack.App.Wpf/ViewModels/Compiler/BaseCompilerVM.cs
new file mode 100644
index 000000000..04dd4ea13
--- /dev/null
+++ b/Wabbajack.App.Wpf/ViewModels/Compiler/BaseCompilerVM.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reactive.Disposables;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using ReactiveUI;
+using ReactiveUI.Fody.Helpers;
+using Wabbajack.DTOs.JsonConverters;
+using Wabbajack.Paths;
+using Wabbajack.Services.OSIntegrated;
+using Wabbajack.Paths.IO;
+using Wabbajack.Networking.WabbajackClientApi;
+using Wabbajack.Messages;
+
+namespace Wabbajack;
+
+public abstract class BaseCompilerVM : ProgressViewModel
+{
+ protected readonly DTOSerializer _dtos;
+ protected readonly SettingsManager _settingsManager;
+ protected readonly ILogger _logger;
+ protected readonly Client _wjClient;
+
+ [Reactive] public CompilerSettingsVM Settings { get; set; } = new();
+
+ public BaseCompilerVM(DTOSerializer dtos, SettingsManager settingsManager, ILogger logger, Client wjClient)
+ {
+ _dtos = dtos;
+ _settingsManager = settingsManager;
+ _logger = logger;
+ _wjClient = wjClient;
+
+ MessageBus.Current.Listen()
+ .Subscribe(msg => {
+ var csVm = new CompilerSettingsVM(msg.CompilerSettings);
+ Settings = csVm;
+ })
+ .DisposeWith(CompositeDisposable);
+ }
+
+ protected async Task SaveSettings()
+ {
+ if (Settings.Source == default || Settings.CompilerSettingsPath == default) return;
+
+ try
+ {
+ await using var st = Settings.CompilerSettingsPath.Open(FileMode.Create, FileAccess.Write, FileShare.None);
+ await JsonSerializer.SerializeAsync(st, Settings.ToCompilerSettings(), new JsonSerializerOptions(_dtos.Options) { WriteIndented = true });
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError("Failed to save compiler settings to {0}! {1}", Settings.CompilerSettingsPath, ex.ToString());
+ }
+
+ var allSavedCompilerSettings = await _settingsManager.Load>(Consts.AllSavedCompilerSettingsPaths);
+
+ // Don't simply remove Settings.CompilerSettingsPath here, because WJ sometimes likes to make default compiler settings files
+ allSavedCompilerSettings.RemoveAll(path => path.Parent == Settings.Source);
+ allSavedCompilerSettings.Insert(0, Settings.CompilerSettingsPath);
+
+ try
+ {
+ await _settingsManager.Save(Consts.AllSavedCompilerSettingsPaths, allSavedCompilerSettings);
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError("Failed to save all saved compiler settings! {0}", ex.ToString());
+ }
+ }
+}
diff --git a/Wabbajack.App.Wpf/ViewModels/Compiler/CompiledModListTileVM.cs b/Wabbajack.App.Wpf/ViewModels/Compiler/CompiledModListTileVM.cs
new file mode 100644
index 000000000..5161460d7
--- /dev/null
+++ b/Wabbajack.App.Wpf/ViewModels/Compiler/CompiledModListTileVM.cs
@@ -0,0 +1,32 @@
+using System.Windows.Input;
+using Microsoft.Extensions.Logging;
+using ReactiveUI;
+using ReactiveUI.Fody.Helpers;
+using Wabbajack.Compiler;
+using Wabbajack.Messages;
+using Wabbajack.Models;
+
+namespace Wabbajack;
+
+public class CompiledModListTileVM
+{
+ private ILogger _logger;
+ public LoadingLock LoadingImageLock { get; } = new();
+ public ICommand CompileModListCommand { get; set; }
+ [Reactive]
+ public CompilerSettings CompilerSettings { get; set; }
+
+ public CompiledModListTileVM(ILogger logger, CompilerSettings compilerSettings)
+ {
+ _logger = logger;
+ CompilerSettings = compilerSettings;
+ CompileModListCommand = ReactiveCommand.Create(CompileModList);
+ }
+
+ private void CompileModList()
+ {
+ _logger.LogInformation($"Selected modlist {CompilerSettings.ModListName} for compilation, located in '{CompilerSettings.Source}'");
+ NavigateToGlobal.Send(ScreenType.CompilerMain);
+ LoadCompilerSettings.Send(CompilerSettings);
+ }
+}
diff --git a/Wabbajack.App.Wpf/ViewModels/Compiler/CompilerDetailsVM.cs b/Wabbajack.App.Wpf/ViewModels/Compiler/CompilerDetailsVM.cs
new file mode 100644
index 000000000..3c2c1b7a9
--- /dev/null
+++ b/Wabbajack.App.Wpf/ViewModels/Compiler/CompilerDetailsVM.cs
@@ -0,0 +1,279 @@
+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.Threading;
+using System.Threading.Tasks;
+using DynamicData;
+using Microsoft.WindowsAPICodePack.Dialogs;
+using ReactiveUI.Fody.Helpers;
+using Wabbajack.Common;
+using Wabbajack.Compiler;
+using Wabbajack.DTOs;
+using Wabbajack.DTOs.JsonConverters;
+using Wabbajack.Extensions;
+using Wabbajack.Installer;
+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 CompilerDetailsVM : BaseCompilerVM, ICpuStatusVM
+{
+ private readonly ResourceMonitor _resourceMonitor;
+ private readonly CompilerSettingsInferencer _inferencer;
+
+ public CompilerFileManagerVM CompilerFileManagerVM { get; private set; }
+ [Reactive] public List AvailableProfiles { get; set; }
+
+ [Reactive]
+ public CompilerState State { get; set; }
+
+ [Reactive]
+ public MO2CompilerVM SubCompilerVM { get; set; }
+
+ // Paths
+ public FilePickerVM ModlistLocation { get; private set; }
+ public FilePickerVM DownloadLocation { get; private set; }
+ public FilePickerVM OutputLocation { get; private set; }
+
+ public FilePickerVM ModListImageLocation { get; private set; } = new();
+
+ /* public ReactiveCommand ExecuteCommand { get; } */
+ public ReactiveCommand ReInferSettingsCommand { get; set; }
+ public ReactiveCommand StartCommand { get; }
+
+ public LogStream LoggerProvider { get; }
+ public ReadOnlyObservableCollection StatusList => _resourceMonitor.Tasks;
+
+ [Reactive]
+ public ErrorResponse ErrorState { get; private set; }
+
+ public CompilerDetailsVM(ILogger logger, DTOSerializer dtos, SettingsManager settingsManager,
+ IServiceProvider serviceProvider, LogStream loggerProvider, ResourceMonitor resourceMonitor,
+ CompilerSettingsInferencer inferencer, Client wjClient, CompilerFileManagerVM compilerFileManagerVM) : base(dtos, settingsManager, logger, wjClient)
+ {
+ LoggerProvider = loggerProvider;
+ _resourceMonitor = resourceMonitor;
+ _inferencer = inferencer;
+ CompilerFileManagerVM = compilerFileManagerVM;
+
+ SubCompilerVM = new MO2CompilerVM(this);
+
+ StartCommand = ReactiveCommand.CreateFromTask(StartCompilation);
+
+
+ this.WhenActivated(disposables =>
+ {
+ State = CompilerState.Configuration;
+
+ ModlistLocation = new FilePickerVM
+ {
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
+ PathType = FilePickerVM.PathTypeOptions.File,
+ PromptTitle = "Select a config file or a modlist.txt file",
+ TargetPath = Settings.ProfilePath
+ };
+
+ ModlistLocation.Filters.AddRange(new[]
+ {
+ new CommonFileDialogFilter("MO2 Modlist", "*" + Ext.Txt),
+ new CommonFileDialogFilter("Compiler Settings File", "*" + Ext.CompilerSettings)
+ });
+
+ 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"
+ };
+
+ ModListImageLocation = new FilePickerVM
+ {
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
+ PathType = FilePickerVM.PathTypeOptions.File,
+ PromptTitle = "Thumbnail image file to use for the modlist"
+ };
+ ModListImageLocation.Filters.AddRange(new[]
+ {
+ new CommonFileDialogFilter("WebP Image (preferred)", "*" + Ext.Webp),
+ new CommonFileDialogFilter("PNG Image", "*" + Ext.Png),
+ new CommonFileDialogFilter("JPG Image", "*" + Ext.Jpg),
+ });
+
+
+ ModlistLocation.WhenAnyValue(vm => vm.TargetPath)
+ .Subscribe(async p => {
+ if (p == default) return;
+ if (Settings.CompilerSettingsPath != default) return;
+ else if(p.FileName == "modlist.txt".ToRelativePath()) await ReInferSettings(p);
+ })
+ .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))
+ .Select(_ => Validate())
+ .BindToStrict(this, vm => vm.ErrorState)
+ .DisposeWith(disposables);
+ this.WhenAnyValue(x => x.Settings.Source)
+ .Subscribe(source =>
+ {
+ AvailableProfiles = source.Combine("profiles").EnumerateDirectories().Select(dir => dir.FileName.ToString()).ToList();
+ })
+ .DisposeWith(disposables);
+
+ });
+ }
+
+ private async Task ReInferSettings(AbsolutePath filePath)
+ {
+ var newSettings = await _inferencer.InferModListFromLocation(filePath);
+
+ if (newSettings == null)
+ {
+ _logger.LogError("Cannot infer settings from {0}", filePath);
+ return;
+ }
+
+ Settings.Source = newSettings.Source;
+ Settings.Downloads = newSettings.Downloads;
+
+ if (string.IsNullOrEmpty(Settings.ModListName))
+ Settings.OutputFile = newSettings.OutputFile.Combine(newSettings.Profile).WithExtension(Ext.Wabbajack);
+ else
+ Settings.OutputFile = newSettings.OutputFile.Combine(newSettings.ModListName).WithExtension(Ext.Wabbajack);
+
+ Settings.Game = newSettings.Game;
+ Settings.Include = newSettings.Include.ToHashSet();
+ Settings.Ignore = newSettings.Ignore.ToHashSet();
+ Settings.AlwaysEnabled = newSettings.AlwaysEnabled.ToHashSet();
+ Settings.NoMatchInclude = newSettings.NoMatchInclude.ToHashSet();
+ Settings.AdditionalProfiles = newSettings.AdditionalProfiles;
+ }
+
+ private ErrorResponse Validate()
+ {
+ var errors = new List
+ {
+ DownloadLocation.ErrorState,
+ ModlistLocation.ErrorState,
+ OutputLocation.ErrorState
+ };
+ return ErrorResponse.Combine(errors);
+ }
+
+ private async Task InferModListFromLocation(AbsolutePath path)
+ {
+ using var _ = LoadingLock.WithLoading();
+
+ CompilerSettings settings;
+ if (path == default) return new();
+ 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 new();
+ }
+ else
+ {
+ return new();
+ }
+
+ return settings;
+ }
+
+ private async Task StartCompilation()
+ {
+ await SaveSettings();
+ NavigateToGlobal.Send(ScreenType.CompilerMain);
+ LoadCompilerSettings.Send(Settings.ToCompilerSettings());
+ }
+
+ #region ListOps
+
+ public void AddOtherProfile(string profile)
+ {
+ Settings.AdditionalProfiles = (Settings.AdditionalProfiles ?? Array.Empty()).Append(profile).Distinct().ToArray();
+ }
+
+ public void RemoveProfile(string profile)
+ {
+ Settings.AdditionalProfiles = Settings.AdditionalProfiles.Where(p => p != profile).ToArray();
+ }
+
+ public void AddAlwaysEnabled(RelativePath path)
+ {
+ Settings.AlwaysEnabled = (Settings.AlwaysEnabled ?? new()).Append(path).Distinct().ToHashSet();
+ }
+
+ public void RemoveAlwaysEnabled(RelativePath path)
+ {
+ Settings.AlwaysEnabled = Settings.AlwaysEnabled.Where(p => p != path).ToHashSet();
+ }
+
+ public void AddNoMatchInclude(RelativePath path)
+ {
+ Settings.NoMatchInclude = (Settings.NoMatchInclude ?? new()).Append(path).Distinct().ToHashSet();
+ }
+
+ public void RemoveNoMatchInclude(RelativePath path)
+ {
+ Settings.NoMatchInclude = Settings.NoMatchInclude.Where(p => p != path).ToHashSet();
+ }
+
+ public void AddInclude(RelativePath path)
+ {
+ Settings.Include = (Settings.Include ?? new()).Append(path).Distinct().ToHashSet();
+ }
+
+ public void RemoveInclude(RelativePath path)
+ {
+ Settings.Include = Settings.Include.Where(p => p != path).ToHashSet();
+ }
+
+
+ public void AddIgnore(RelativePath path)
+ {
+ Settings.Ignore = (Settings.Ignore ?? new()).Append(path).Distinct().ToHashSet();
+ }
+
+ public void RemoveIgnore(RelativePath path)
+ {
+ Settings.Ignore = Settings.Ignore.Where(p => p != path).ToHashSet();
+ }
+
+ #endregion
+}
diff --git a/Wabbajack.App.Wpf/ViewModels/Compiler/CompilerFileManagerVM.cs b/Wabbajack.App.Wpf/ViewModels/Compiler/CompilerFileManagerVM.cs
new file mode 100644
index 000000000..50b28896b
--- /dev/null
+++ b/Wabbajack.App.Wpf/ViewModels/Compiler/CompilerFileManagerVM.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using Wabbajack.Messages;
+using ReactiveUI;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Wabbajack.Common;
+using Wabbajack.Compiler;
+using Wabbajack.DTOs.JsonConverters;
+using Wabbajack.Models;
+using Wabbajack.Networking.WabbajackClientApi;
+using Wabbajack.Paths;
+using Wabbajack.Services.OSIntegrated;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.ComponentModel;
+
+namespace Wabbajack;
+
+public class CompilerFileManagerVM : BaseCompilerVM
+{
+ private readonly IServiceProvider _serviceProvider;
+ private readonly ResourceMonitor _resourceMonitor;
+ private readonly CompilerSettingsInferencer _inferencer;
+
+ public ObservableCollection Files { get; set; }
+
+ public CompilerFileManagerVM(ILogger logger, DTOSerializer dtos, SettingsManager settingsManager,
+ IServiceProvider serviceProvider, ResourceMonitor resourceMonitor,
+ CompilerSettingsInferencer inferencer, Client wjClient) : base(dtos, settingsManager, logger, wjClient)
+ {
+ _serviceProvider = serviceProvider;
+ _resourceMonitor = resourceMonitor;
+ _inferencer = inferencer;
+ this.WhenActivated(disposables =>
+ {
+ if (Settings.Source != default)
+ {
+ var fileTree = GetDirectoryContents(new DirectoryInfo(Settings.Source.ToString()));
+ Files = LoadSource(new DirectoryInfo(Settings.Source.ToString()));
+ }
+
+ Disposable.Create(() => { }).DisposeWith(disposables);
+ });
+ }
+
+ private ObservableCollection LoadSource(DirectoryInfo parent)
+ {
+ var parentTreeItem = new FileTreeViewItem(parent)
+ {
+ IsExpanded = true,
+ ItemsSource = LoadDirectoryContents(parent),
+ };
+ return [parentTreeItem];
+
+ }
+
+ private IEnumerable