diff --git a/Wabbajack.Common/StatusFeed/IUserIntervention.cs b/Wabbajack.Common/StatusFeed/IUserIntervention.cs
deleted file mode 100644
index 6285fc70..00000000
--- a/Wabbajack.Common/StatusFeed/IUserIntervention.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Wabbajack.Common.StatusFeed
-{
- ///
- /// Defines a message that requires user interaction. The user must perform some action
- /// or make a choice.
- ///
- public interface IUserIntervention : IStatusMessage
- {
- ///
- /// The user didn't make a choice, so this action should be aborted
- ///
- void Cancel();
- }
-}
diff --git a/Wabbajack.Common/StatusFeed/Interventions/AUserIntervention.cs b/Wabbajack.Common/StatusFeed/Interventions/AUserIntervention.cs
new file mode 100644
index 00000000..2c33a010
--- /dev/null
+++ b/Wabbajack.Common/StatusFeed/Interventions/AUserIntervention.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using ReactiveUI;
+
+namespace Wabbajack.Common
+{
+ public abstract class AUserIntervention : ReactiveObject, IUserIntervention
+ {
+ 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 int CpuID { get; } = WorkQueue.CpuId;
+
+ public abstract void Cancel();
+ public ICommand CancelCommand { get; }
+
+ public AUserIntervention()
+ {
+ CancelCommand = ReactiveCommand.Create(() => Cancel());
+ }
+ }
+}
diff --git a/Wabbajack.Common/StatusFeed/Interventions/ConfirmationIntervention.cs b/Wabbajack.Common/StatusFeed/Interventions/ConfirmationIntervention.cs
new file mode 100644
index 00000000..de097c6e
--- /dev/null
+++ b/Wabbajack.Common/StatusFeed/Interventions/ConfirmationIntervention.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using ReactiveUI;
+
+namespace Wabbajack.Common
+{
+ public abstract class ConfirmationIntervention : AUserIntervention
+ {
+ public enum Choice
+ {
+ Continue,
+ Abort
+ }
+
+ private TaskCompletionSource _source = new TaskCompletionSource();
+ public Task Task => _source.Task;
+
+ public ICommand ConfirmCommand { get; }
+
+ public ConfirmationIntervention()
+ {
+ ConfirmCommand = ReactiveCommand.Create(() => Confirm());
+ }
+
+ public override void Cancel()
+ {
+ Handled = true;
+ _source.SetResult(Choice.Abort);
+ }
+
+ public void Confirm()
+ {
+ Handled = true;
+ _source.SetResult(Choice.Continue);
+ }
+ }
+}
diff --git a/Wabbajack.Common/StatusFeed/Interventions/IUserIntervention.cs b/Wabbajack.Common/StatusFeed/Interventions/IUserIntervention.cs
new file mode 100644
index 00000000..1f2ce864
--- /dev/null
+++ b/Wabbajack.Common/StatusFeed/Interventions/IUserIntervention.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ReactiveUI;
+using Wabbajack.Common.StatusFeed;
+
+namespace Wabbajack.Common
+{
+ ///
+ /// Defines a message that requires user interaction. The user must perform some action
+ /// or make a choice.
+ ///
+ public interface IUserIntervention : IStatusMessage, IReactiveObject
+ {
+ ///
+ /// The user didn't make a choice, so this action should be aborted
+ ///
+ void Cancel();
+
+ ///
+ /// Whether the interaction has been handled and no longer needs attention
+ /// Note: This needs to be Reactive so that users can monitor its status
+ ///
+ bool Handled { get; }
+
+ ///
+ /// WorkQueue job ID that is blocking on this intervention
+ ///
+ int CpuID { get; }
+ }
+}
diff --git a/Wabbajack.Common/Wabbajack.Common.csproj b/Wabbajack.Common/Wabbajack.Common.csproj
index db46d3b1..1eb44526 100644
--- a/Wabbajack.Common/Wabbajack.Common.csproj
+++ b/Wabbajack.Common/Wabbajack.Common.csproj
@@ -114,6 +114,7 @@
+
@@ -122,8 +123,9 @@
+
-
+
diff --git a/Wabbajack.Common/WorkQueue.cs b/Wabbajack.Common/WorkQueue.cs
index 97a13a14..15400642 100644
--- a/Wabbajack.Common/WorkQueue.cs
+++ b/Wabbajack.Common/WorkQueue.cs
@@ -14,7 +14,10 @@ namespace Wabbajack.Common
internal BlockingCollection
Queue = new BlockingCollection(new ConcurrentStack());
- [ThreadStatic] private static int CpuId;
+ public const int UnassignedCpuId = -1;
+
+ [ThreadStatic] private static int _cpuId = UnassignedCpuId;
+ public static int CpuId => _cpuId;
internal static bool WorkerThread => CurrentQueue != null;
[ThreadStatic] internal static WorkQueue CurrentQueue;
@@ -24,6 +27,11 @@ namespace Wabbajack.Common
public static List Threads { get; private set; }
+ // This is currently a lie, as it wires to the Utils singleton stream This is still good to have,
+ // so that logic related to a single WorkQueue can subscribe to this dummy member so that If/when we
+ // implement log messages in a non-singleton fashion, they will already be wired up properly.
+ public IObservable LogMessages => Utils.LogMessages;
+
public WorkQueue(int threadCount = 0)
{
StartThreads(threadCount == 0 ? Environment.ProcessorCount : threadCount);
@@ -48,7 +56,7 @@ namespace Wabbajack.Common
private void ThreadBody(int idx)
{
- CpuId = idx;
+ _cpuId = idx;
CurrentQueue = this;
while (true)
@@ -67,7 +75,7 @@ namespace Wabbajack.Common
Progress = progress,
ProgressPercent = progress / 100f,
Msg = msg,
- ID = CpuId,
+ ID = _cpuId,
IsWorking = isWorking
});
}
diff --git a/Wabbajack.Lib/ABatchProcessor.cs b/Wabbajack.Lib/ABatchProcessor.cs
index c05b9bde..38998a87 100644
--- a/Wabbajack.Lib/ABatchProcessor.cs
+++ b/Wabbajack.Lib/ABatchProcessor.cs
@@ -1,9 +1,11 @@
using System;
using System.IO;
+using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.Common;
+using Wabbajack.Common.StatusFeed;
using Wabbajack.VirtualFileSystem;
namespace Wabbajack.Lib
@@ -15,6 +17,7 @@ namespace Wabbajack.Lib
public void Dispose()
{
Queue?.Shutdown();
+ _subs.Dispose();
}
public Context VFS { get; private set; }
@@ -38,6 +41,9 @@ namespace Wabbajack.Lib
private Subject _queueStatus { get; } = new Subject();
public IObservable QueueStatus => _queueStatus;
+ private Subject _logMessages { get; } = new Subject();
+ public IObservable LogMessages => _logMessages;
+
private Subject _isRunning { get; } = new Subject();
public IObservable IsRunning => _isRunning;
@@ -46,6 +52,8 @@ namespace Wabbajack.Lib
private int _configured;
private int _started;
+ private readonly CompositeDisposable _subs = new CompositeDisposable();
+
protected void ConfigureProcessor(int steps, int threads = 0)
{
if (1 == Interlocked.CompareExchange(ref _configured, 1, 1))
@@ -54,7 +62,10 @@ namespace Wabbajack.Lib
}
Queue = new WorkQueue(threads);
UpdateTracker = new StatusUpdateTracker(steps);
- Queue.Status.Subscribe(_queueStatus);
+ Queue.Status.Subscribe(_queueStatus)
+ .DisposeWith(_subs);
+ Queue.LogMessages.Subscribe(_logMessages)
+ .DisposeWith(_subs);
UpdateTracker.Progress.Subscribe(_percentCompleted);
UpdateTracker.StepName.Subscribe(_textStatus);
VFS = new Context(Queue) { UpdateTracker = UpdateTracker };
diff --git a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs
index eb93bc46..3fcb5cda 100644
--- a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs
+++ b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs
@@ -9,11 +9,8 @@ using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Wabbajack.Common;
-using Wabbajack.Common.StatusFeed;
-using Wabbajack.Common.StatusFeed.Errors;
using Wabbajack.Lib.LibCefHelpers;
using Wabbajack.Lib.Validation;
-using Wabbajack.Lib.WebAutomation;
using Xilium.CefGlue.Common;
using File = Alphaleonis.Win32.Filesystem.File;
@@ -183,7 +180,7 @@ namespace Wabbajack.Lib.Downloaders
}
}
- public class RequestLoversLabLogin : AStatusMessage, IUserIntervention
+ public class RequestLoversLabLogin : AUserIntervention
{
public override string ShortDescription => "Getting LoversLab information";
public override string ExtendedDescription { get; }
@@ -193,10 +190,13 @@ namespace Wabbajack.Lib.Downloaders
public void Resume(Helpers.Cookie[] cookies)
{
+ Handled = true;
_source.SetResult(cookies);
}
- public void Cancel()
+
+ public override void Cancel()
{
+ Handled = true;
_source.SetCanceled();
}
}
diff --git a/Wabbajack.Lib/MO2Installer.cs b/Wabbajack.Lib/MO2Installer.cs
index f55e4d84..0002e2f9 100644
--- a/Wabbajack.Lib/MO2Installer.cs
+++ b/Wabbajack.Lib/MO2Installer.cs
@@ -10,7 +10,6 @@ using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps.CompilationErrors;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
-using Wabbajack.Lib.StatusMessages;
using Wabbajack.Lib.Validation;
using Directory = Alphaleonis.Win32.Filesystem.Directory;
using File = Alphaleonis.Win32.Filesystem.File;
diff --git a/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs b/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs
index 473c8e7c..fb9cffae 100644
--- a/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs
+++ b/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs
@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using Wabbajack.Common.StatusFeed;
+using Wabbajack.Common;
namespace Wabbajack.Lib.NexusApi
{
- public class RequestNexusAuthorization : AStatusMessage, IUserIntervention
+ public class RequestNexusAuthorization : AUserIntervention
{
public override string ShortDescription => "Getting User's Nexus API Key";
public override string ExtendedDescription { get; }
@@ -17,10 +17,13 @@ namespace Wabbajack.Lib.NexusApi
public void Resume(string apikey)
{
+ Handled = true;
_source.SetResult(apikey);
}
- public void Cancel()
+
+ public override void Cancel()
{
+ Handled = true;
_source.SetCanceled();
}
}
diff --git a/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs b/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs
index 97b51326..c7250bec 100644
--- a/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs
+++ b/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs
@@ -3,24 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using Wabbajack.Common.StatusFeed;
+using Wabbajack.Common;
-namespace Wabbajack.Lib.StatusMessages
+namespace Wabbajack.Lib
{
- public class ConfirmUpdateOfExistingInstall : AStatusMessage, IUserIntervention
+ public class ConfirmUpdateOfExistingInstall : ConfirmationIntervention
{
- public enum Choice
- {
- Continue,
- Abort
- }
-
public string OutputFolder { get; set; }
public string ModListName { get; set; }
- public override string ShortDescription { get; } = "Do you want to overwrite existing files?";
- private TaskCompletionSource _source = new TaskCompletionSource();
- public Task Task => _source.Task;
+ public override string ShortDescription { get; } = "Do you want to overwrite existing files?";
public override string ExtendedDescription
{
@@ -29,15 +21,5 @@ namespace Wabbajack.Lib.StatusMessages
Any files that exist in {OutputFolder} will be changed to match the files found in the {ModListName} modlist. This means that save games will be removed, custom settings
will be reverted. Are you sure you wish to continue?";
}
-
- public void Cancel()
- {
- _source.SetResult(Choice.Abort);
- }
-
- public void Confirm()
- {
- _source.SetResult(Choice.Continue);
- }
}
}
diff --git a/Wabbajack/Themes/Styles.xaml b/Wabbajack/Themes/Styles.xaml
index 5eb76514..a7a388b7 100644
--- a/Wabbajack/Themes/Styles.xaml
+++ b/Wabbajack/Themes/Styles.xaml
@@ -43,6 +43,7 @@
#03DAC6
#0e8f83
#095952
+ #042421
#cef0ed
#8cede5
#00ffe7
@@ -115,6 +116,7 @@
+
diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs
index 42f771c3..3794d854 100644
--- a/Wabbajack/View Models/Installers/InstallerVM.cs
+++ b/Wabbajack/View Models/Installers/InstallerVM.cs
@@ -19,7 +19,6 @@ using DynamicData;
using DynamicData.Binding;
using Wabbajack.Common.StatusFeed;
using System.Reactive;
-using Wabbajack.Common.StatusFeed;
namespace Wabbajack
{
@@ -79,6 +78,9 @@ namespace Wabbajack
private readonly ObservableAsPropertyHelper _TargetManager;
public ModManager? TargetManager => _TargetManager.Value;
+ private readonly ObservableAsPropertyHelper _ActiveGlobalUserIntervention;
+ public IUserIntervention ActiveGlobalUserIntervention => _ActiveGlobalUserIntervention.Value;
+
// Command properties
public IReactiveCommand ShowReportCommand { get; }
public IReactiveCommand OpenReadmeCommand { get; }
@@ -293,6 +295,22 @@ namespace Wabbajack
InstallingMode = true;
})
.DisposeWith(CompositeDisposable);
+
+ // Listen for user interventions, and compile a dynamic list of all unhandled ones
+ var activeInterventions = this.WhenAny(x => x.Installer.ActiveInstallation)
+ .SelectMany(c => c?.LogMessages ?? Observable.Empty())
+ .WhereCastable()
+ .ToObservableChangeSet()
+ .AutoRefresh(i => i.Handled)
+ .Filter(i => !i.Handled)
+ .AsObservableList();
+
+ // Find the top intervention /w no CPU ID to be marked as "global"
+ _ActiveGlobalUserIntervention = activeInterventions.Connect()
+ .Filter(x => x.CpuID == WorkQueue.UnassignedCpuId)
+ .QueryWhenChanged(query => query.FirstOrDefault())
+ .ObserveOnGuiThread()
+ .ToProperty(this, nameof(ActiveGlobalUserIntervention));
}
private void ShowReport()
diff --git a/Wabbajack/View Models/MainWindowVM.cs b/Wabbajack/View Models/MainWindowVM.cs
index 3301181b..3746b3db 100644
--- a/Wabbajack/View Models/MainWindowVM.cs
+++ b/Wabbajack/View Models/MainWindowVM.cs
@@ -12,9 +12,6 @@ using System.Windows.Threading;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Lib;
-using Wabbajack.Lib.Downloaders;
-using Wabbajack.Lib.NexusApi;
-using Wabbajack.Lib.StatusMessages;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/UserInterventionHandlers.cs b/Wabbajack/View Models/UserInterventionHandlers.cs
index 83f0a2a1..bcc5d941 100644
--- a/Wabbajack/View Models/UserInterventionHandlers.cs
+++ b/Wabbajack/View Models/UserInterventionHandlers.cs
@@ -8,10 +8,8 @@ using System.Windows;
using System.Windows.Threading;
using ReactiveUI;
using Wabbajack.Common;
-using Wabbajack.Common.StatusFeed;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
-using Wabbajack.Lib.StatusMessages;
namespace Wabbajack
{
@@ -54,22 +52,10 @@ namespace Wabbajack
MainWindow.ActivePane = oldPane;
}
- public void Handle(ConfirmUpdateOfExistingInstall msg)
- {
- var result = MessageBox.Show(msg.ExtendedDescription, msg.ShortDescription, MessageBoxButton.OKCancel);
- if (result == MessageBoxResult.OK)
- msg.Confirm();
- else
- msg.Cancel();
- }
-
public async Task Handle(IUserIntervention msg)
{
switch (msg)
{
- case ConfirmUpdateOfExistingInstall c:
- Handle(c);
- break;
case RequestNexusAuthorization c:
await WrapBrowserJob(msg, async (vm, cancel) =>
{
@@ -84,6 +70,8 @@ namespace Wabbajack
c.Resume(data);
});
break;
+ case ConfirmationIntervention c:
+ break;
default:
throw new NotImplementedException($"No handler for {msg}");
}
diff --git a/Wabbajack/Views/Compilers/CompilerView.xaml b/Wabbajack/Views/Compilers/CompilerView.xaml
index 51dbf30d..5bff7e3a 100644
--- a/Wabbajack/Views/Compilers/CompilerView.xaml
+++ b/Wabbajack/Views/Compilers/CompilerView.xaml
@@ -2,6 +2,7 @@
x:Class="Wabbajack.CompilerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:common="clr-namespace:Wabbajack.Common;assembly=Wabbajack.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:Wabbajack"
@@ -241,12 +242,29 @@
Margin="5"
Visibility="{Binding Compiling, Converter={StaticResource bool2VisibilityConverter}, FallbackValue=Hidden}">
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
diff --git a/Wabbajack/Views/Installers/InstallationView.xaml b/Wabbajack/Views/Installers/InstallationView.xaml
index 0872b3c5..26edeeff 100644
--- a/Wabbajack/Views/Installers/InstallationView.xaml
+++ b/Wabbajack/Views/Installers/InstallationView.xaml
@@ -2,8 +2,10 @@
x:Class="Wabbajack.InstallationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:common="clr-namespace:Wabbajack.Common;assembly=Wabbajack.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
+ xmlns:lib="clr-namespace:Wabbajack.Lib;assembly=Wabbajack.Lib"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:InstallerVM}"
@@ -344,12 +346,59 @@
Margin="5,0,5,5"
Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}, FallbackValue=Hidden}">
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Wabbajack/Views/Interventions/ConfirmationInterventionView.xaml b/Wabbajack/Views/Interventions/ConfirmationInterventionView.xaml
new file mode 100644
index 00000000..328ec7e7
--- /dev/null
+++ b/Wabbajack/Views/Interventions/ConfirmationInterventionView.xaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Wabbajack/Views/Interventions/ConfirmationInterventionView.xaml.cs b/Wabbajack/Views/Interventions/ConfirmationInterventionView.xaml.cs
new file mode 100644
index 00000000..0b28abc6
--- /dev/null
+++ b/Wabbajack/Views/Interventions/ConfirmationInterventionView.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Wabbajack
+{
+ ///
+ /// Interaction logic for ConfirmationInterventionView.xaml
+ ///
+ public partial class ConfirmationInterventionView : UserControl
+ {
+ public ConfirmationInterventionView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj
index 2a35ef84..de6035b3 100644
--- a/Wabbajack/Wabbajack.csproj
+++ b/Wabbajack/Wabbajack.csproj
@@ -172,6 +172,9 @@
MSBuild:Compile
Designer
+
+ ConfirmationInterventionView.xaml
+
CpuView.xaml
@@ -259,6 +262,10 @@
WebBrowserView.xaml
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -529,5 +536,8 @@
+
+
+
\ No newline at end of file