From cc6a571845dd3ed22bd0a8afa1e8de5fa4a1169c Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 12:54:37 -0400 Subject: [PATCH 01/23] Add deps NLog, NLog.Extensions.Logging --- StabilityMatrix/StabilityMatrix.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/StabilityMatrix/StabilityMatrix.csproj b/StabilityMatrix/StabilityMatrix.csproj index 331d8519..d2110a9a 100644 --- a/StabilityMatrix/StabilityMatrix.csproj +++ b/StabilityMatrix/StabilityMatrix.csproj @@ -10,6 +10,8 @@ + + From 23488d277f042b2f85c6a15a16220d82e8c0fe7f Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 13:51:00 -0400 Subject: [PATCH 02/23] Simplify PyRunner organization --- StabilityMatrix/PyRunner.cs | 88 +++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/StabilityMatrix/PyRunner.cs b/StabilityMatrix/PyRunner.cs index 5f2ff6dc..4f091830 100644 --- a/StabilityMatrix/PyRunner.cs +++ b/StabilityMatrix/PyRunner.cs @@ -9,8 +9,11 @@ using StabilityMatrix.Helper; namespace StabilityMatrix; +internal record struct PyVersionInfo(int Major, int Minor, int Micro, string ReleaseLevel, int Serial); + internal static class PyRunner { + private const string private const string RelativeDllPath = @"Assets\Python310\python310.dll"; private const string RelativeExePath = @"Assets\Python310\python.exe"; private const string RelativePipExePath = @"Assets\Python310\Scripts\pip.exe"; @@ -25,6 +28,11 @@ internal static class PyRunner private static readonly SemaphoreSlim PyRunning = new(1, 1); + /// + /// Initializes the Python runtime using the embedded dll. + /// Can be called with no effect after initialization. + /// + /// public static async Task Initialize() { if (PythonEngine.IsInitialized) return; @@ -35,11 +43,18 @@ internal static class PyRunner throw new FileNotFoundException("Python DLL not found", DllPath); } Runtime.PythonDLL = DllPath; - PythonEngine.Initialize(); PythonEngine.BeginAllowThreads(); - await RedirectPythonOutput(); + // Redirect stdout and stderr + StdOutStream = new PyIOStream(); + StdErrStream = new PyIOStream(); + await RunInThreadWithLock(() => + { + dynamic sys = Py.Import("sys"); + sys.stdout = StdOutStream; + sys.stderr = StdErrStream; + }); } /// @@ -76,24 +91,26 @@ internal static class PyRunner } } - // Redirect Python output - private static async Task RedirectPythonOutput() + /// + /// Run a Function with PyRunning lock as a Task with GIL. + /// + /// Function to run. + /// Time limit for waiting on PyRunning lock. + /// Cancellation token. + /// cancelToken was canceled, or waitTimeout expired. + private static async Task RunInThreadWithLock(Func func, TimeSpan? waitTimeout = null, CancellationToken cancelToken = default) { - StdOutStream = new PyIOStream(); - StdErrStream = new PyIOStream(); - - await PyRunning.WaitAsync(); + // Wait to acquire PyRunning lock + await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false); try { - await Task.Run(() => + return await Task.Run(() => { using (Py.GIL()) { - dynamic sys = Py.Import("sys"); - sys.stdout = StdOutStream; - sys.stderr = StdErrStream; + return func(); } - }); + }, cancelToken); } finally { @@ -101,12 +118,47 @@ internal static class PyRunner } } + /// + /// Run an Action with PyRunning lock as a Task with GIL. + /// + /// Action to run. + /// Time limit for waiting on PyRunning lock. + /// Cancellation token. + /// cancelToken was canceled, or waitTimeout expired. + private static async Task RunInThreadWithLock(Action action, TimeSpan? waitTimeout = null, CancellationToken cancelToken = default) + { + // Wait to acquire PyRunning lock + await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false); + try + { + await Task.Run(() => + { + using (Py.GIL()) + { + action(); + } + }, cancelToken); + } + finally + { + PyRunning.Release(); + } + } + /// /// Evaluate Python expression and return its value as a string /// - /// - /// - public static async Task Eval(string code) + /// + public static async Task Eval(string expression) + { + return await Eval(expression); + } + + /// + /// Evaluate Python expression and return its value + /// + /// + public static async Task Eval(string expression) { await PyRunning.WaitAsync(); try @@ -115,8 +167,8 @@ internal static class PyRunner { using (Py.GIL()) { - var result = PythonEngine.Eval(code); - return result.ToString(CultureInfo.InvariantCulture); + var result = PythonEngine.Eval(expression); + return result.As(); } }); } From d7464269864dc7853773a234f3bfb32f9d9539ac Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 14:04:36 -0400 Subject: [PATCH 03/23] Add NLog to DI --- StabilityMatrix/App.xaml.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/StabilityMatrix/App.xaml.cs b/StabilityMatrix/App.xaml.cs index 4a617b3b..fce181ce 100644 --- a/StabilityMatrix/App.xaml.cs +++ b/StabilityMatrix/App.xaml.cs @@ -1,5 +1,7 @@ using System.Windows; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NLog.Extensions.Logging; using Refit; using StabilityMatrix.Api; using StabilityMatrix.Helper; @@ -29,6 +31,13 @@ namespace StabilityMatrix serviceCollection.AddSingleton(); serviceCollection.AddRefitClient(); + serviceCollection.AddLogging(log => + { + log.ClearProviders(); + log.SetMinimumLevel(LogLevel.Trace); + log.AddNLog(); + }); + var provider = serviceCollection.BuildServiceProvider(); var window = provider.GetRequiredService(); window.Show(); From 8178be4d4196d7ca1e73755064036dce2ea8ac42 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 14:04:52 -0400 Subject: [PATCH 04/23] Add Logger and traces to ProcessRunner --- StabilityMatrix/Helper/ProcessRunner.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/StabilityMatrix/Helper/ProcessRunner.cs b/StabilityMatrix/Helper/ProcessRunner.cs index d78f0b7e..9cbf41da 100644 --- a/StabilityMatrix/Helper/ProcessRunner.cs +++ b/StabilityMatrix/Helper/ProcessRunner.cs @@ -1,13 +1,17 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using NLog; namespace StabilityMatrix.Helper; public static class ProcessRunner { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + public static async Task GetProcessOutputAsync(string fileName, string arguments) { + Logger.Trace($"Starting process '{fileName}' with arguments '{arguments}'"); using var process = new Process(); process.StartInfo.FileName = fileName; process.StartInfo.Arguments = arguments; @@ -24,6 +28,7 @@ public static class ProcessRunner public static Process StartProcess(string fileName, string arguments, Action? outputDataReceived = null) { + Logger.Trace($"Starting process '{fileName}' with arguments '{arguments}'"); var process = new Process(); process.StartInfo.FileName = fileName; process.StartInfo.Arguments = arguments; From ec86a7f6a105ae0cd5733c62bfa1761043bad6b5 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 14:07:47 -0400 Subject: [PATCH 05/23] Refactor direct GIL calls to RunInThreadWithLock --- StabilityMatrix/PyRunner.cs | 44 ++++++++++--------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/StabilityMatrix/PyRunner.cs b/StabilityMatrix/PyRunner.cs index 4f091830..ebea5499 100644 --- a/StabilityMatrix/PyRunner.cs +++ b/StabilityMatrix/PyRunner.cs @@ -23,8 +23,8 @@ internal static class PyRunner public static string PipExePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RelativePipExePath); public static string GetPipPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RelativeGetPipPath); - public static PyIOStream StdOutStream; - public static PyIOStream StdErrStream; + public static PyIOStream? StdOutStream; + public static PyIOStream? StdErrStream; private static readonly SemaphoreSlim PyRunning = new(1, 1); @@ -158,46 +158,24 @@ internal static class PyRunner /// Evaluate Python expression and return its value /// /// - public static async Task Eval(string expression) + public static Task Eval(string expression) { - await PyRunning.WaitAsync(); - try - { - return await Task.Run(() => - { - using (Py.GIL()) - { - var result = PythonEngine.Eval(expression); - return result.As(); - } - }); - } - finally + return RunInThreadWithLock(() => { - PyRunning.Release(); - } + var result = PythonEngine.Eval(expression); + return result.As(); + }); } /// /// Execute Python code without returning a value /// /// - public static async Task Exec(string code) + public static Task Exec(string code) { - await PyRunning.WaitAsync(); - try - { - await Task.Run(() => - { - using (Py.GIL()) - { - PythonEngine.Exec(code); - } - }); - } - finally + return RunInThreadWithLock(() => { - PyRunning.Release(); - } + PythonEngine.Exec(code); + }); } } \ No newline at end of file From 311ad727e711f10efb9121645d1ce98e3cc6c4a2 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 14:08:14 -0400 Subject: [PATCH 06/23] Add logger and traces to PyRunner --- StabilityMatrix/PyRunner.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix/PyRunner.cs b/StabilityMatrix/PyRunner.cs index ebea5499..0ff3a6fc 100644 --- a/StabilityMatrix/PyRunner.cs +++ b/StabilityMatrix/PyRunner.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; +using NLog; using Python.Runtime; using StabilityMatrix.Helper; @@ -13,7 +14,8 @@ internal record struct PyVersionInfo(int Major, int Minor, int Micro, string Rel internal static class PyRunner { - private const string + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private const string RelativeDllPath = @"Assets\Python310\python310.dll"; private const string RelativeExePath = @"Assets\Python310\python.exe"; private const string RelativePipExePath = @"Assets\Python310\Scripts\pip.exe"; @@ -36,6 +38,8 @@ internal static class PyRunner public static async Task Initialize() { if (PythonEngine.IsInitialized) return; + + Logger.Trace($"Initializing Python runtime with DLL '{DllPath}'"); // Check PythonDLL exists if (!File.Exists(DllPath)) @@ -62,7 +66,6 @@ internal static class PyRunner /// public static async Task SetupPip() { - Debug.WriteLine($"Process '{ExePath}' starting '{GetPipPath}'"); var pythonProc = ProcessRunner.StartProcess(ExePath, GetPipPath); await pythonProc.WaitForExitAsync(); // Check return code From f5395621fe931437737097138cca725ab708fd6a Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 14:46:23 -0400 Subject: [PATCH 07/23] Add exit condition validators to ProcessRunner --- StabilityMatrix/Helper/ProcessRunner.cs | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/StabilityMatrix/Helper/ProcessRunner.cs b/StabilityMatrix/Helper/ProcessRunner.cs index 9cbf41da..109f033d 100644 --- a/StabilityMatrix/Helper/ProcessRunner.cs +++ b/StabilityMatrix/Helper/ProcessRunner.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using NLog; +using StabilityMatrix.Exceptions; namespace StabilityMatrix.Helper; @@ -50,4 +52,37 @@ public static class ProcessRunner return process; } + + /// + /// Check if the process exited with the expected exit code. + /// + /// Process to check. + /// Expected exit code. + /// Thrown if exit code does not match expected value. + public static async Task ValidateExitConditionAsync(Process process, int expectedExitCode = 0) + { + var exitCode = process.ExitCode; + if (exitCode != expectedExitCode) + { + var pName = process.StartInfo.FileName; + var stdout = await process.StandardOutput.ReadToEndAsync(); + var stderr = await process.StandardError.ReadToEndAsync(); + var msg = $"Process {pName} failed with exit-code {exitCode}. stdout: '{stdout}', stderr: '{stderr}'"; + Logger.Error(msg); + throw new ProcessException(msg); + } + } + + /// + /// Waits for process to exit, then validates exit code. + /// + /// Process to check. + /// Expected exit code. + /// Cancellation token. + /// Thrown if exit code does not match expected value. + public static async Task WaitForExitConditionAsync(Process process, int expectedExitCode = 0, CancellationToken cancelToken = default) + { + await process.WaitForExitAsync(cancelToken); + await ValidateExitConditionAsync(process, expectedExitCode); + } } From a5db5f4dd8153d6ddfcbcd7d9a3c86d3f8e93e89 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 14:46:46 -0400 Subject: [PATCH 08/23] Add ProcessException --- StabilityMatrix/Exceptions/ProcessException.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 StabilityMatrix/Exceptions/ProcessException.cs diff --git a/StabilityMatrix/Exceptions/ProcessException.cs b/StabilityMatrix/Exceptions/ProcessException.cs new file mode 100644 index 00000000..1613b588 --- /dev/null +++ b/StabilityMatrix/Exceptions/ProcessException.cs @@ -0,0 +1,13 @@ +using System; + +namespace StabilityMatrix.Exceptions; + +/// +/// Exception that is thrown when a process fails. +/// +public class ProcessException: Exception +{ + public ProcessException(string message) : base(message) + { + } +} \ No newline at end of file From 3f55863ad0a974a98c6fa785fde7614f8c69013a Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 14:49:35 -0400 Subject: [PATCH 09/23] Refactor exit code checks to WaitForExitCondition --- StabilityMatrix/PyRunner.cs | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/StabilityMatrix/PyRunner.cs b/StabilityMatrix/PyRunner.cs index 0ff3a6fc..d0deb720 100644 --- a/StabilityMatrix/PyRunner.cs +++ b/StabilityMatrix/PyRunner.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using NLog; using Python.Runtime; +using StabilityMatrix.Exceptions; using StabilityMatrix.Helper; namespace StabilityMatrix; @@ -34,7 +35,7 @@ internal static class PyRunner /// Initializes the Python runtime using the embedded dll. /// Can be called with no effect after initialization. /// - /// + /// Thrown if Python DLL not found. public static async Task Initialize() { if (PythonEngine.IsInitialized) return; @@ -66,16 +67,8 @@ internal static class PyRunner /// public static async Task SetupPip() { - var pythonProc = ProcessRunner.StartProcess(ExePath, GetPipPath); - await pythonProc.WaitForExitAsync(); - // Check return code - var returnCode = pythonProc.ExitCode; - if (returnCode != 0) - { - var output = pythonProc.StandardOutput.ReadToEnd(); - Debug.WriteLine($"Error in get-pip.py: {output}"); - throw new InvalidOperationException($"Running get-pip.py failed with code {returnCode}: {output}"); - } + var p = ProcessRunner.StartProcess(ExePath, GetPipPath); + await ProcessRunner.WaitForExitConditionAsync(p); } /// @@ -83,15 +76,8 @@ internal static class PyRunner /// public static async Task InstallPackage(string package) { - var pipProc = ProcessRunner.StartProcess(PipExePath, $"install {package}"); - await pipProc.WaitForExitAsync(); - // Check return code - var returnCode = pipProc.ExitCode; - if (returnCode != 0) - { - var output = await pipProc.StandardOutput.ReadToEndAsync(); - throw new InvalidOperationException($"Pip install failed with code {returnCode}: {output}"); - } + var p = ProcessRunner.StartProcess(PipExePath, $"install {package}"); + await ProcessRunner.WaitForExitConditionAsync(p); } /// From c8e63753eeaf6fbbd26e0ea301e94a8c44804b7c Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 17:16:10 -0400 Subject: [PATCH 10/23] Update Launch Page and view models --- StabilityMatrix/App.xaml.cs | 1 + StabilityMatrix/Helper/ISettingsManager.cs | 2 + StabilityMatrix/Helper/SettingsManager.cs | 19 +++++ StabilityMatrix/LaunchPage.xaml | 51 ++++++++++- StabilityMatrix/LaunchPage.xaml.cs | 11 ++- StabilityMatrix/Models/InstalledPackage.cs | 21 +++++ StabilityMatrix/Models/Settings.cs | 7 +- StabilityMatrix/ViewModels/LaunchViewModel.cs | 84 +++++++++++++++++++ 8 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 StabilityMatrix/Models/InstalledPackage.cs create mode 100644 StabilityMatrix/ViewModels/LaunchViewModel.cs diff --git a/StabilityMatrix/App.xaml.cs b/StabilityMatrix/App.xaml.cs index fce181ce..11d3f816 100644 --- a/StabilityMatrix/App.xaml.cs +++ b/StabilityMatrix/App.xaml.cs @@ -27,6 +27,7 @@ namespace StabilityMatrix serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddRefitClient(); diff --git a/StabilityMatrix/Helper/ISettingsManager.cs b/StabilityMatrix/Helper/ISettingsManager.cs index 87f02e00..040e4716 100644 --- a/StabilityMatrix/Helper/ISettingsManager.cs +++ b/StabilityMatrix/Helper/ISettingsManager.cs @@ -6,4 +6,6 @@ public interface ISettingsManager { Settings Settings { get; } void SetTheme(string theme); + void AddInstalledPackage(InstalledPackage p); + void SetActiveInstalledPackage(InstalledPackage? p); } \ No newline at end of file diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index 913fa817..bc8e9089 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -37,6 +37,25 @@ public class SettingsManager : ISettingsManager Settings.Theme = theme; SaveSettings(); } + + public void AddInstalledPackage(InstalledPackage p) + { + Settings.InstalledPackages.Add(p); + SaveSettings(); + } + + public void SetActiveInstalledPackage(InstalledPackage? p) + { + if (p == null) + { + Settings.ActiveInstalledPackage = null; + } + else + { + Settings.ActiveInstalledPackage = p.Id; + } + SaveSettings(); + } private void LoadSettings() { diff --git a/StabilityMatrix/LaunchPage.xaml b/StabilityMatrix/LaunchPage.xaml index 92666828..7ce3f9a3 100644 --- a/StabilityMatrix/LaunchPage.xaml +++ b/StabilityMatrix/LaunchPage.xaml @@ -5,11 +5,58 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:local="clr-namespace:StabilityMatrix" + xmlns:viewModels="clr-namespace:StabilityMatrix.ViewModels" + xmlns:models="clr-namespace:StabilityMatrix.Models" Background="{DynamicResource ApplicationBackgroundBrush}" Foreground="{DynamicResource TextFillColorPrimaryBrush}" mc:Ignorable="d" Title="LaunchPage" d:DesignHeight="700" d:DesignWidth="1100"> + - + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/StabilityMatrix/LaunchPage.xaml.cs b/StabilityMatrix/LaunchPage.xaml.cs index 1553bb68..8a1ed2a5 100644 --- a/StabilityMatrix/LaunchPage.xaml.cs +++ b/StabilityMatrix/LaunchPage.xaml.cs @@ -1,11 +1,18 @@ using System.Windows.Controls; +using StabilityMatrix.ViewModels; namespace StabilityMatrix; -public partial class LaunchPage : Page +public sealed partial class LaunchPage : Page { - public LaunchPage() + public LaunchPage(LaunchViewModel viewModel) { InitializeComponent(); + DataContext = viewModel; + } + + private void SelectPackageComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + } } diff --git a/StabilityMatrix/Models/InstalledPackage.cs b/StabilityMatrix/Models/InstalledPackage.cs new file mode 100644 index 00000000..c471b6bf --- /dev/null +++ b/StabilityMatrix/Models/InstalledPackage.cs @@ -0,0 +1,21 @@ +using System; + +namespace StabilityMatrix.Models; + +/// +/// Profile information for a user-installed package. +/// +public class InstalledPackage +{ + // Unique ID for the installation + public Guid Id { get; set; } + // User defined name + public string Name { get; set; } + // Package name + public string PackageName { get; set; } + // Package version + public string PackageVersion { get; set; } + // Install path + public string Path { get; set; } + +} \ No newline at end of file diff --git a/StabilityMatrix/Models/Settings.cs b/StabilityMatrix/Models/Settings.cs index d8b85340..82873dd1 100644 --- a/StabilityMatrix/Models/Settings.cs +++ b/StabilityMatrix/Models/Settings.cs @@ -1,6 +1,11 @@ -namespace StabilityMatrix.Models; +using System; +using System.Collections.Generic; + +namespace StabilityMatrix.Models; public class Settings { public string Theme { get; set; } + public List InstalledPackages { get; set; } + public Guid? ActiveInstalledPackage { get; set; } } diff --git a/StabilityMatrix/ViewModels/LaunchViewModel.cs b/StabilityMatrix/ViewModels/LaunchViewModel.cs new file mode 100644 index 00000000..2e88fc14 --- /dev/null +++ b/StabilityMatrix/ViewModels/LaunchViewModel.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Net.Mime; +using System.Threading.Tasks; +using System.Windows.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using StabilityMatrix.Helper; +using StabilityMatrix.Models; +using Wpf.Ui.Appearance; + +namespace StabilityMatrix.ViewModels; + +public partial class LaunchViewModel : ObservableObject +{ + private readonly ISettingsManager settingsManager; + + [ObservableProperty] + public string consoleInput = ""; + + [ObservableProperty] + public string consoleOutput = ""; + + private InstalledPackage? selectedPackage; + + public InstalledPackage? SelectedPackage + { + get => selectedPackage; + set + { + if (value == selectedPackage) return; + selectedPackage = value; + settingsManager.SetActiveInstalledPackage(value); + OnPropertyChanged(); + } + } + + public ObservableCollection Packages => new(); + + public LaunchViewModel(ISettingsManager settingsManager) + { + this.settingsManager = settingsManager; + var packages = settingsManager.Settings.InstalledPackages; + foreach (var package in packages) + { + Packages.Add(package); + } + + } + + public RelayCommand LaunchCommand => new(() => + { + ConsoleOutput = ""; + + var venv = new PyVenvRunner(@"L:\Image ML\stable-diffusion-webui\venv"); + + var onConsoleOutput = new Action(s => + { + if (s == null) return; + Dispatcher.CurrentDispatcher.Invoke(() => + { + Debug.WriteLine($"process stdout: {s}"); + return ConsoleOutput += s + "\n"; + }); + }); + + var onExit = new Action(i => + { + Dispatcher.CurrentDispatcher.Invoke(() => + { + Debug.WriteLine($"Venv process exited with code {i}"); + return ConsoleOutput += $"Venv process exited with code {i}"; + }); + }); + + const string arg = "\"" + @"L:\Image ML\stable-diffusion-webui\launch.py" + "\""; + // const string arg = "-c \"import sys; print(sys.version_info)\""; + + venv.RunDetached(arg, onConsoleOutput, onExit); + }); +} From bf55a722e4bb41df8d98d0d92842032dfe9bee00 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 17:16:44 -0400 Subject: [PATCH 11/23] Move GetVersionInfo to PyRunner --- StabilityMatrix/PyRunner.cs | 20 +++++++++++++++++++ .../ViewModels/SettingsViewModel.cs | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix/PyRunner.cs b/StabilityMatrix/PyRunner.cs index d0deb720..18f9f53b 100644 --- a/StabilityMatrix/PyRunner.cs +++ b/StabilityMatrix/PyRunner.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; @@ -167,4 +168,23 @@ internal static class PyRunner PythonEngine.Exec(code); }); } + + /// + /// Return the Python version as a PyVersionInfo struct + /// + public static async Task GetVersionInfo() + { + var version = await RunInThreadWithLock(() => + { + dynamic info = PythonEngine.Eval("tuple(__import__('sys').version_info)"); + return new PyVersionInfo( + info[0].As(), + info[1].As(), + info[2].As(), + info[3].As(), + info[4].As() + ); + }); + return version; + } } \ No newline at end of file diff --git a/StabilityMatrix/ViewModels/SettingsViewModel.cs b/StabilityMatrix/ViewModels/SettingsViewModel.cs index b3df26ff..abc29df3 100644 --- a/StabilityMatrix/ViewModels/SettingsViewModel.cs +++ b/StabilityMatrix/ViewModels/SettingsViewModel.cs @@ -66,7 +66,7 @@ public partial class SettingsViewModel : ObservableObject { // Get python version await PyRunner.Initialize(); - var result = await PyRunner.Eval("str(__import__('sys').version_info)"); + var result = await PyRunner.GetVersionInfo(); // Show dialog box var dialog = contentDialogService.CreateDialog(); dialog.Title = "Python version info"; From acaa95469786d98b16eb6a12b6a28556526a7646 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 25 May 2023 14:24:33 -0700 Subject: [PATCH 12/23] Updated Settings stuff to not crash & load packages every time we go back to Launch page --- StabilityMatrix/LaunchPage.xaml | 3 +- StabilityMatrix/LaunchPage.xaml.cs | 10 ++++-- StabilityMatrix/Models/Settings.cs | 2 +- StabilityMatrix/ViewModels/LaunchViewModel.cs | 31 ++++++++++++++----- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/StabilityMatrix/LaunchPage.xaml b/StabilityMatrix/LaunchPage.xaml index 7ce3f9a3..a9891e76 100644 --- a/StabilityMatrix/LaunchPage.xaml +++ b/StabilityMatrix/LaunchPage.xaml @@ -9,6 +9,7 @@ xmlns:models="clr-namespace:StabilityMatrix.Models" Background="{DynamicResource ApplicationBackgroundBrush}" Foreground="{DynamicResource TextFillColorPrimaryBrush}" + Loaded="LaunchPage_OnLoaded" mc:Ignorable="d" Title="LaunchPage" d:DesignHeight="700" d:DesignWidth="1100"> @@ -59,4 +60,4 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> - \ No newline at end of file + diff --git a/StabilityMatrix/LaunchPage.xaml.cs b/StabilityMatrix/LaunchPage.xaml.cs index 8a1ed2a5..db8221a1 100644 --- a/StabilityMatrix/LaunchPage.xaml.cs +++ b/StabilityMatrix/LaunchPage.xaml.cs @@ -1,18 +1,22 @@ -using System.Windows.Controls; +using System.Windows; +using System.Windows.Controls; using StabilityMatrix.ViewModels; namespace StabilityMatrix; public sealed partial class LaunchPage : Page { + private readonly LaunchViewModel viewModel; + public LaunchPage(LaunchViewModel viewModel) { + this.viewModel = viewModel; InitializeComponent(); DataContext = viewModel; } - private void SelectPackageComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + private void LaunchPage_OnLoaded(object sender, RoutedEventArgs e) { - + viewModel.OnLoaded(); } } diff --git a/StabilityMatrix/Models/Settings.cs b/StabilityMatrix/Models/Settings.cs index 82873dd1..be162818 100644 --- a/StabilityMatrix/Models/Settings.cs +++ b/StabilityMatrix/Models/Settings.cs @@ -6,6 +6,6 @@ namespace StabilityMatrix.Models; public class Settings { public string Theme { get; set; } - public List InstalledPackages { get; set; } + public List InstalledPackages { get; set; } = new(); public Guid? ActiveInstalledPackage { get; set; } } diff --git a/StabilityMatrix/ViewModels/LaunchViewModel.cs b/StabilityMatrix/ViewModels/LaunchViewModel.cs index 2e88fc14..1cfe5058 100644 --- a/StabilityMatrix/ViewModels/LaunchViewModel.cs +++ b/StabilityMatrix/ViewModels/LaunchViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net.Mime; using System.Threading.Tasks; using System.Windows.Threading; @@ -43,12 +44,7 @@ public partial class LaunchViewModel : ObservableObject public LaunchViewModel(ISettingsManager settingsManager) { this.settingsManager = settingsManager; - var packages = settingsManager.Settings.InstalledPackages; - foreach (var package in packages) - { - Packages.Add(package); - } - + LoadPackages(); } public RelayCommand LaunchCommand => new(() => @@ -63,7 +59,7 @@ public partial class LaunchViewModel : ObservableObject Dispatcher.CurrentDispatcher.Invoke(() => { Debug.WriteLine($"process stdout: {s}"); - return ConsoleOutput += s + "\n"; + ConsoleOutput += s + "\n"; }); }); @@ -72,7 +68,7 @@ public partial class LaunchViewModel : ObservableObject Dispatcher.CurrentDispatcher.Invoke(() => { Debug.WriteLine($"Venv process exited with code {i}"); - return ConsoleOutput += $"Venv process exited with code {i}"; + ConsoleOutput += $"Venv process exited with code {i}"; }); }); @@ -81,4 +77,23 @@ public partial class LaunchViewModel : ObservableObject venv.RunDetached(arg, onConsoleOutput, onExit); }); + + public void OnLoaded() + { + LoadPackages(); + } + + private void LoadPackages() + { + var packages = settingsManager.Settings.InstalledPackages; + if (!packages.Any()) + { + return; + } + + foreach (var package in packages) + { + Packages.Add(package); + } + } } From da8e172cc5dc718691361ea0627b80b33ecb26f0 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 25 May 2023 14:29:06 -0700 Subject: [PATCH 13/23] handle collection changed event --- StabilityMatrix/ViewModels/LaunchViewModel.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix/ViewModels/LaunchViewModel.cs b/StabilityMatrix/ViewModels/LaunchViewModel.cs index 1cfe5058..d40ea4f0 100644 --- a/StabilityMatrix/ViewModels/LaunchViewModel.cs +++ b/StabilityMatrix/ViewModels/LaunchViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.IO; @@ -44,9 +45,20 @@ public partial class LaunchViewModel : ObservableObject public LaunchViewModel(ISettingsManager settingsManager) { this.settingsManager = settingsManager; - LoadPackages(); + Packages.CollectionChanged += PackagesOnCollectionChanged; } - + + private void PackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action != NotifyCollectionChangedAction.Add) return; + + var newPackage = e.NewItems?.Cast().FirstOrDefault(); + if (newPackage != null) + { + settingsManager.AddInstalledPackage(newPackage); + } + } + public RelayCommand LaunchCommand => new(() => { ConsoleOutput = ""; From be01f20a81108500f550163ff7d90c85c2b0594e Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 25 May 2023 14:31:40 -0700 Subject: [PATCH 14/23] nevermind --- StabilityMatrix/ViewModels/LaunchViewModel.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/StabilityMatrix/ViewModels/LaunchViewModel.cs b/StabilityMatrix/ViewModels/LaunchViewModel.cs index d40ea4f0..27cd654b 100644 --- a/StabilityMatrix/ViewModels/LaunchViewModel.cs +++ b/StabilityMatrix/ViewModels/LaunchViewModel.cs @@ -45,18 +45,6 @@ public partial class LaunchViewModel : ObservableObject public LaunchViewModel(ISettingsManager settingsManager) { this.settingsManager = settingsManager; - Packages.CollectionChanged += PackagesOnCollectionChanged; - } - - private void PackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action != NotifyCollectionChangedAction.Add) return; - - var newPackage = e.NewItems?.Cast().FirstOrDefault(); - if (newPackage != null) - { - settingsManager.AddInstalledPackage(newPackage); - } } public RelayCommand LaunchCommand => new(() => From 501d1aa2e524fd9cfbcfd01eb713181c63addac0 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 17:37:47 -0400 Subject: [PATCH 15/23] Update LaunchCommand to use selected package --- StabilityMatrix/ViewModels/LaunchViewModel.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/StabilityMatrix/ViewModels/LaunchViewModel.cs b/StabilityMatrix/ViewModels/LaunchViewModel.cs index d40ea4f0..4141432b 100644 --- a/StabilityMatrix/ViewModels/LaunchViewModel.cs +++ b/StabilityMatrix/ViewModels/LaunchViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.Eventing.Reader; using System.IO; using System.Linq; using System.Net.Mime; @@ -59,11 +60,27 @@ public partial class LaunchViewModel : ObservableObject } } - public RelayCommand LaunchCommand => new(() => + public AsyncRelayCommand LaunchCommand => new(async () => { + // Clear console ConsoleOutput = ""; - var venv = new PyVenvRunner(@"L:\Image ML\stable-diffusion-webui\venv"); + if (SelectedPackage == null) + { + ConsoleOutput = "No package selected"; + return; + } + + // Get path from package + var packagePath = SelectedPackage.Path; + var venvPath = Path.Combine(packagePath, "venv"); + + // Setup venv + var venv = new PyVenvRunner(venvPath); + if (!venv.Exists()) + { + await venv.Setup(); + } var onConsoleOutput = new Action(s => { @@ -84,10 +101,9 @@ public partial class LaunchViewModel : ObservableObject }); }); - const string arg = "\"" + @"L:\Image ML\stable-diffusion-webui\launch.py" + "\""; - // const string arg = "-c \"import sys; print(sys.version_info)\""; - - venv.RunDetached(arg, onConsoleOutput, onExit); + var args = "\"" + Path.Combine(packagePath, "launch.py") + "\""; + + venv.RunDetached(args, onConsoleOutput, onExit); }); public void OnLoaded() From e11996a5bed4986c4191794992f51aa5d3375154 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 17:44:08 -0400 Subject: [PATCH 16/23] Fix SettingsManager default --- StabilityMatrix/Helper/SettingsManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index bc8e9089..dbe3a4b5 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -24,12 +24,11 @@ public class SettingsManager : ISettingsManager if (!File.Exists(SettingsPath)) { File.Create(SettingsPath).Close(); - File.WriteAllText(SettingsPath, "{}"); + var defaultSettingsJson = JsonSerializer.Serialize(Settings); + File.WriteAllText(SettingsPath, defaultSettingsJson); } LoadSettings(); - - } public void SetTheme(string theme) From 9682bcf9044e200ae1fb523e93ba9c05c9d9e18a Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 25 May 2023 20:06:17 -0400 Subject: [PATCH 17/23] Add settings debug option for adding installation --- StabilityMatrix/SettingsPage.xaml | 90 ++++++++++--------- .../ViewModels/SettingsViewModel.cs | 35 ++++++++ 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/StabilityMatrix/SettingsPage.xaml b/StabilityMatrix/SettingsPage.xaml index 9c31f8a8..66d50e73 100644 --- a/StabilityMatrix/SettingsPage.xaml +++ b/StabilityMatrix/SettingsPage.xaml @@ -15,46 +15,56 @@ Foreground="{DynamicResource TextFillColorPrimaryBrush}" mc:Ignorable="d"> - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - -