Add fully-injected MainWindowViewModel constructor
Introduce an injected constructor taking IHardwareMonitorService, ILogger, IRemoteWebServer (+ the concrete selection/plot/startup services) and IStartupTracer, with shared settings initialization. A transitional 2-arg constructor still builds the legacy graph for MainWindow until it is DI-resolved; an _ownsServices flag keeps disposal correct across both paths. Break the VM<->RemoteWebServer construction cycle by moving the root provider to RemoteWebServer.SetRootProvider (removing it from the constructor), and make RemoteWebServer.Quit idempotent. Update RemoteWebServer tests for the new ctor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -332,7 +332,7 @@ public class RemoteWebServerTests
|
|||||||
|
|
||||||
private static RemoteWebServer CreateServer(bool authEnabled, string userName, string password)
|
private static RemoteWebServer CreateServer(bool authEnabled, string userName, string password)
|
||||||
{
|
{
|
||||||
return new RemoteWebServer(() => null, Mock.Of<IComputer>(), new object(), "localhost", 8085, authEnabled, userName, RemoteWebServer.ComputeSHA256(password));
|
return new RemoteWebServer(Mock.Of<IComputer>(), new object(), "localhost", 8085, authEnabled, userName, RemoteWebServer.ComputeSHA256(password));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ISensor CreateSensor(string name, SensorType type, Identifier identifier, float? value = null, float? min = null, float? max = null)
|
private static ISensor CreateSensor(string name, SensorType type, Identifier identifier, float? value = null, float? min = null, float? max = null)
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public interface IRemoteWebServer : IDisposable
|
|||||||
|
|
||||||
string UserName { get; set; }
|
string UserName { get; set; }
|
||||||
|
|
||||||
|
void SetRootProvider(Func<ViewModels.SensorTreeItemViewModel?> rootProvider);
|
||||||
|
|
||||||
void SetPassword(string plainPassword);
|
void SetPassword(string plainPassword);
|
||||||
|
|
||||||
bool Start();
|
bool Start();
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ public sealed class RemoteWebServer : IRemoteWebServer
|
|||||||
{
|
{
|
||||||
private readonly IComputer _computer;
|
private readonly IComputer _computer;
|
||||||
private readonly object _sensorReadLock;
|
private readonly object _sensorReadLock;
|
||||||
private readonly Func<SensorTreeItemViewModel?> _rootProvider;
|
private Func<SensorTreeItemViewModel?> _rootProvider = () => null;
|
||||||
private readonly Version _version = typeof(RemoteWebServer).Assembly.GetName().Version ?? new Version(0, 0);
|
private readonly Version _version = typeof(RemoteWebServer).Assembly.GetName().Version ?? new Version(0, 0);
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
private HttpListener? _listener;
|
private HttpListener? _listener;
|
||||||
private Task? _listenerTask;
|
private Task? _listenerTask;
|
||||||
|
private bool _quit;
|
||||||
|
|
||||||
public RemoteWebServer(
|
public RemoteWebServer(
|
||||||
Func<SensorTreeItemViewModel?> rootProvider,
|
|
||||||
IComputer computer,
|
IComputer computer,
|
||||||
object sensorReadLock,
|
object sensorReadLock,
|
||||||
string listenerIp,
|
string listenerIp,
|
||||||
@@ -42,7 +42,6 @@ public sealed class RemoteWebServer : IRemoteWebServer
|
|||||||
string userName,
|
string userName,
|
||||||
string passwordHash)
|
string passwordHash)
|
||||||
{
|
{
|
||||||
_rootProvider = rootProvider;
|
|
||||||
_computer = computer;
|
_computer = computer;
|
||||||
_sensorReadLock = sensorReadLock;
|
_sensorReadLock = sensorReadLock;
|
||||||
ListenerIp = listenerIp;
|
ListenerIp = listenerIp;
|
||||||
@@ -61,6 +60,15 @@ public sealed class RemoteWebServer : IRemoteWebServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the accessor used to read the current sensor tree root. Supplied after construction so the
|
||||||
|
/// server can be created by the container without a constructor dependency on the view model.
|
||||||
|
/// </summary>
|
||||||
|
public void SetRootProvider(Func<SensorTreeItemViewModel?> rootProvider)
|
||||||
|
{
|
||||||
|
_rootProvider = rootProvider;
|
||||||
|
}
|
||||||
|
|
||||||
public bool AuthEnabled { get; set; }
|
public bool AuthEnabled { get; set; }
|
||||||
|
|
||||||
public bool IsRunning => _listener?.IsListening == true;
|
public bool IsRunning => _listener?.IsListening == true;
|
||||||
@@ -137,9 +145,10 @@ public sealed class RemoteWebServer : IRemoteWebServer
|
|||||||
|
|
||||||
public void Quit()
|
public void Quit()
|
||||||
{
|
{
|
||||||
if (PlatformNotSupported)
|
if (PlatformNotSupported || _quit)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_quit = true;
|
||||||
Stop();
|
Stop();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,14 +82,15 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
|
|||||||
TimeSpan.FromHours(24)
|
TimeSpan.FromHours(24)
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly HardwareMonitorService _hardwareMonitor;
|
private readonly IHardwareMonitorService _hardwareMonitor;
|
||||||
private readonly DispatcherQueue _dispatcherQueue;
|
private readonly DispatcherQueue _dispatcherQueue;
|
||||||
private readonly Logger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly PlotTrackingService _plotTracking = new();
|
private readonly PlotTrackingService _plotTracking;
|
||||||
private readonly SensorSelectionService _sensorSelection;
|
private readonly SensorSelectionService _sensorSelection;
|
||||||
private readonly RemoteWebServer _remoteWebServer;
|
private readonly IRemoteWebServer _remoteWebServer;
|
||||||
private readonly StartupService _startupService = new();
|
private readonly StartupService _startupService;
|
||||||
private readonly IStartupTracer _startupTrace;
|
private readonly IStartupTracer _startupTrace;
|
||||||
|
private readonly bool _ownsServices;
|
||||||
private AppThemeMode _themeMode;
|
private AppThemeMode _themeMode;
|
||||||
private int _loggingIntervalIndex;
|
private int _loggingIntervalIndex;
|
||||||
private PlotLocation _plotLocation;
|
private PlotLocation _plotLocation;
|
||||||
@@ -116,28 +117,58 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
|
|||||||
private TemperatureUnit _temperatureUnit;
|
private TemperatureUnit _temperatureUnit;
|
||||||
private int _updateIntervalIndex;
|
private int _updateIntervalIndex;
|
||||||
|
|
||||||
public MainWindowViewModel(AppSettings settings) : this(settings, NoOpStartupTracer.Instance)
|
internal MainWindowViewModel(
|
||||||
|
AppSettings settings,
|
||||||
|
IHardwareMonitorService hardwareMonitor,
|
||||||
|
ILogger logger,
|
||||||
|
SensorSelectionService sensorSelection,
|
||||||
|
IRemoteWebServer remoteWebServer,
|
||||||
|
PlotTrackingService plotTracking,
|
||||||
|
StartupService startupService,
|
||||||
|
IStartupTracer startupTrace)
|
||||||
{
|
{
|
||||||
|
Settings = settings;
|
||||||
|
_startupTrace = startupTrace;
|
||||||
|
_startupTrace.Mark("MainWindowViewModel.Constructor.Begin");
|
||||||
|
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||||
|
_hardwareMonitor = hardwareMonitor;
|
||||||
|
_logger = logger;
|
||||||
|
_sensorSelection = sensorSelection;
|
||||||
|
_remoteWebServer = remoteWebServer;
|
||||||
|
_plotTracking = plotTracking;
|
||||||
|
_startupService = startupService;
|
||||||
|
InitializeFromSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transitional constructor that builds the domain graph the legacy way for callers that do not yet
|
||||||
|
// resolve the view model from the container. Removed once MainWindow is DI-resolved (di-app-invert).
|
||||||
internal MainWindowViewModel(AppSettings settings, IStartupTracer startupTrace)
|
internal MainWindowViewModel(AppSettings settings, IStartupTracer startupTrace)
|
||||||
{
|
{
|
||||||
Settings = settings;
|
Settings = settings;
|
||||||
_startupTrace = startupTrace;
|
_startupTrace = startupTrace;
|
||||||
_startupTrace.Mark("MainWindowViewModel.Constructor.Begin");
|
_startupTrace.Mark("MainWindowViewModel.Constructor.Begin");
|
||||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||||
_hardwareMonitor = new HardwareMonitorService(settings);
|
HardwareMonitorService hardwareMonitor = new(settings);
|
||||||
_logger = new Logger(_hardwareMonitor.Computer);
|
_hardwareMonitor = hardwareMonitor;
|
||||||
|
_logger = new Logger(hardwareMonitor.Computer);
|
||||||
_sensorSelection = new SensorSelectionService(settings);
|
_sensorSelection = new SensorSelectionService(settings);
|
||||||
_remoteWebServer = new RemoteWebServer(
|
_remoteWebServer = new RemoteWebServer(
|
||||||
() => RootItems.FirstOrDefault(),
|
hardwareMonitor.Computer,
|
||||||
_hardwareMonitor.Computer,
|
hardwareMonitor.SensorReadLock,
|
||||||
_hardwareMonitor.SensorReadLock,
|
|
||||||
settings.GetValue("listenerIp", "?"),
|
settings.GetValue("listenerIp", "?"),
|
||||||
settings.GetValue("listenerPort", 8085),
|
settings.GetValue("listenerPort", 8085),
|
||||||
settings.GetValue("authenticationEnabled", false),
|
settings.GetValue("authenticationEnabled", false),
|
||||||
settings.GetValue("authenticationUserName", ""),
|
settings.GetValue("authenticationUserName", ""),
|
||||||
settings.GetValue("authenticationPassword", ""));
|
settings.GetValue("authenticationPassword", ""));
|
||||||
|
_plotTracking = new PlotTrackingService();
|
||||||
|
_startupService = new StartupService();
|
||||||
|
_ownsServices = true;
|
||||||
|
InitializeFromSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeFromSettings(AppSettings settings)
|
||||||
|
{
|
||||||
|
_remoteWebServer.SetRootProvider(() => RootItems.FirstOrDefault());
|
||||||
_hardwareMonitor.TreeRebuilt += HardwareMonitor_TreeRebuilt;
|
_hardwareMonitor.TreeRebuilt += HardwareMonitor_TreeRebuilt;
|
||||||
|
|
||||||
_themeMode = ParseThemeMode(settings.GetValue("theme", "auto"));
|
_themeMode = ParseThemeMode(settings.GetValue("theme", "auto"));
|
||||||
@@ -757,7 +788,8 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
|
|||||||
Settings.SetValue("authenticationUserName", AuthWebServerUserName);
|
Settings.SetValue("authenticationUserName", AuthWebServerUserName);
|
||||||
Settings.SetValue("authenticationPassword", _remoteWebServer.PasswordHash);
|
Settings.SetValue("authenticationPassword", _remoteWebServer.PasswordHash);
|
||||||
_remoteWebServer.Quit();
|
_remoteWebServer.Quit();
|
||||||
_hardwareMonitor.Dispose();
|
if (_ownsServices)
|
||||||
|
_hardwareMonitor.Dispose();
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user