Browse Source

Merge pull request #13 from ionite34/launcher-output

pull/5/head
Ionite 2 years ago committed by GitHub
parent
commit
f5d6d92e3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      StabilityMatrix/App.xaml.cs
  2. BIN
      StabilityMatrix/Assets/Icon.ico
  3. 13
      StabilityMatrix/Exceptions/ProcessException.cs
  4. 2
      StabilityMatrix/Helper/ISettingsManager.cs
  5. 40
      StabilityMatrix/Helper/ProcessRunner.cs
  6. 24
      StabilityMatrix/Helper/SettingsManager.cs
  7. 50
      StabilityMatrix/LaunchPage.xaml
  8. 18
      StabilityMatrix/LaunchPage.xaml.cs
  9. 1
      StabilityMatrix/MainWindow.xaml
  10. 21
      StabilityMatrix/Models/InstalledPackage.cs
  11. 7
      StabilityMatrix/Models/Settings.cs
  12. 155
      StabilityMatrix/PyRunner.cs
  13. 90
      StabilityMatrix/SettingsPage.xaml
  14. 7
      StabilityMatrix/StabilityMatrix.csproj
  15. 115
      StabilityMatrix/ViewModels/LaunchViewModel.cs
  16. 34
      StabilityMatrix/ViewModels/SettingsViewModel.cs

22
StabilityMatrix/App.xaml.cs

@ -1,5 +1,9 @@
using System.Windows;
using System;
using System.IO;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using Refit;
using StabilityMatrix.Api;
using StabilityMatrix.Helper;
@ -7,6 +11,7 @@ using StabilityMatrix.Services;
using StabilityMatrix.ViewModels;
using Wpf.Ui.Contracts;
using Wpf.Ui.Services;
using SetupBuilderExtensions = NLog.SetupBuilderExtensions;
namespace StabilityMatrix
{
@ -25,9 +30,24 @@ namespace StabilityMatrix
serviceCollection.AddTransient<InstallPage>();
serviceCollection.AddTransient<MainWindowViewModel>();
serviceCollection.AddSingleton<SettingsViewModel>();
serviceCollection.AddSingleton<LaunchViewModel>();
serviceCollection.AddSingleton<IContentDialogService, ContentDialogService>();
serviceCollection.AddSingleton<ISnackbarService, SnackbarService>();
serviceCollection.AddSingleton<ISettingsManager, SettingsManager>();
serviceCollection.AddRefitClient<IGithubApi>();
var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log.txt");
var logConfig = new NLog.Config.LoggingConfiguration();
var fileTarget = new NLog.Targets.FileTarget("logfile") {FileName = logPath};
logConfig.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, fileTarget);
NLog.LogManager.Configuration = logConfig;
serviceCollection.AddLogging(log =>
{
log.ClearProviders();
log.SetMinimumLevel(LogLevel.Trace);
log.AddNLog(logConfig);
});
var provider = serviceCollection.BuildServiceProvider();
var window = provider.GetRequiredService<MainWindow>();

BIN
StabilityMatrix/Assets/Icon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

13
StabilityMatrix/Exceptions/ProcessException.cs

@ -0,0 +1,13 @@
using System;
namespace StabilityMatrix.Exceptions;
/// <summary>
/// Exception that is thrown when a process fails.
/// </summary>
public class ProcessException: Exception
{
public ProcessException(string message) : base(message)
{
}
}

2
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);
}

40
StabilityMatrix/Helper/ProcessRunner.cs

@ -1,13 +1,19 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using StabilityMatrix.Exceptions;
namespace StabilityMatrix.Helper;
public static class ProcessRunner
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static async Task<string> 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 +30,7 @@ public static class ProcessRunner
public static Process StartProcess(string fileName, string arguments, Action<string?>? outputDataReceived = null)
{
Logger.Trace($"Starting process '{fileName}' with arguments '{arguments}'");
var process = new Process();
process.StartInfo.FileName = fileName;
process.StartInfo.Arguments = arguments;
@ -45,4 +52,37 @@ public static class ProcessRunner
return process;
}
/// <summary>
/// Check if the process exited with the expected exit code.
/// </summary>
/// <param name="process">Process to check.</param>
/// <param name="expectedExitCode">Expected exit code.</param>
/// <exception cref="ProcessException">Thrown if exit code does not match expected value.</exception>
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);
}
}
/// <summary>
/// Waits for process to exit, then validates exit code.
/// </summary>
/// <param name="process">Process to check.</param>
/// <param name="expectedExitCode">Expected exit code.</param>
/// <param name="cancelToken">Cancellation token.</param>
/// <exception cref="ProcessException">Thrown if exit code does not match expected value.</exception>
public static async Task WaitForExitConditionAsync(Process process, int expectedExitCode = 0, CancellationToken cancelToken = default)
{
await process.WaitForExitAsync(cancelToken);
await ValidateExitConditionAsync(process, expectedExitCode);
}
}

24
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)
@ -37,6 +36,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()
{

50
StabilityMatrix/LaunchPage.xaml

@ -5,11 +5,59 @@
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}"
Loaded="LaunchPage_OnLoaded"
mc:Ignorable="d"
Title="LaunchPage" d:DesignHeight="700" d:DesignWidth="1100">
<Grid>
<ui:Button x:Name="LaunchButton" Content="Launch" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ui:Button
x:Name="LaunchButton"
Grid.Row="0"
Content="Launch"
Command="{Binding LaunchCommand}"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Margin="10,10,0,0"/>
<ComboBox
x:Name="SelectPackageComboBox"
ItemsSource="{Binding InstalledPackages}"
SelectedValue="{Binding SelectedPackage}"
Grid.Row="0" Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Margin="10,10,10,0">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models:InstalledPackage}">
<StackPanel VerticalAlignment="Top" Margin="10,0,0,0">
<TextBlock Text="{Binding Name}" Margin="0,5,0,5" />
<TextBlock Text="{Binding Path}" Margin="0,0,0,5" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<TextBlock Grid.Row="1"
Name="OutputBlock"
Margin="10,10,10,10"
Text="{Binding ConsoleOutput, Mode=OneWay}"
FontFamily="Consolas"
Background="{DynamicResource ControlFillColorDisabledBrush}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid>
</Page>

18
StabilityMatrix/LaunchPage.xaml.cs

@ -1,11 +1,23 @@
using System.Windows.Controls;
using System.Windows;
using System.Windows.Controls;
using StabilityMatrix.ViewModels;
namespace StabilityMatrix;
public partial class LaunchPage : Page
public sealed partial class LaunchPage : Page
{
public LaunchPage()
private readonly LaunchViewModel viewModel;
public LaunchPage(LaunchViewModel viewModel)
{
this.viewModel = viewModel;
InitializeComponent();
DataContext = viewModel;
}
private void LaunchPage_OnLoaded(object sender, RoutedEventArgs e)
{
viewModel.OnLoaded();
SelectPackageComboBox.ItemsSource = viewModel.InstalledPackages;
}
}

1
StabilityMatrix/MainWindow.xaml

@ -6,6 +6,7 @@
xmlns:local="clr-namespace:StabilityMatrix"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
mc:Ignorable="d"
Icon="Assets/Icon.ico"
Background="{DynamicResource ApplicationBackgroundBrush}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
ExtendsContentIntoTitleBar="True"

21
StabilityMatrix/Models/InstalledPackage.cs

@ -0,0 +1,21 @@
using System;
namespace StabilityMatrix.Models;
/// <summary>
/// Profile information for a user-installed package.
/// </summary>
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; }
}

7
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<InstalledPackage> InstalledPackages { get; set; } = new();
public Guid? ActiveInstalledPackage { get; set; }
}

155
StabilityMatrix/PyRunner.cs

@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using Python.Runtime;
using StabilityMatrix.Exceptions;
using StabilityMatrix.Helper;
namespace StabilityMatrix;
internal record struct PyVersionInfo(int Major, int Minor, int Micro, string ReleaseLevel, int Serial);
internal static class PyRunner
{
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";
@ -20,14 +27,21 @@ 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);
/// <summary>
/// Initializes the Python runtime using the embedded dll.
/// Can be called with no effect after initialization.
/// </summary>
/// <exception cref="FileNotFoundException">Thrown if Python DLL not found.</exception>
public static async Task Initialize()
{
if (PythonEngine.IsInitialized) return;
Logger.Trace($"Initializing Python runtime with DLL '{DllPath}'");
// Check PythonDLL exists
if (!File.Exists(DllPath))
@ -35,11 +49,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;
});
}
/// <summary>
@ -47,17 +68,8 @@ internal static class PyRunner
/// </summary>
public static async Task SetupPip()
{
Debug.WriteLine($"Process '{ExePath}' starting '{GetPipPath}'");
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);
}
/// <summary>
@ -65,35 +77,30 @@ internal static class PyRunner
/// </summary>
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);
}
// Redirect Python output
private static async Task RedirectPythonOutput()
/// <summary>
/// Run a Function with PyRunning lock as a Task with GIL.
/// </summary>
/// <param name="func">Function to run.</param>
/// <param name="waitTimeout">Time limit for waiting on PyRunning lock.</param>
/// <param name="cancelToken">Cancellation token.</param>
/// <exception cref="OperationCanceledException">cancelToken was canceled, or waitTimeout expired.</exception>
private static async Task<T> RunInThreadWithLock<T>(Func<T> 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
{
@ -102,50 +109,82 @@ internal static class PyRunner
}
/// <summary>
/// Evaluate Python expression and return its value as a string
/// Run an Action with PyRunning lock as a Task with GIL.
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public static async Task<string> Eval(string code)
/// <param name="action">Action to run.</param>
/// <param name="waitTimeout">Time limit for waiting on PyRunning lock.</param>
/// <param name="cancelToken">Cancellation token.</param>
/// <exception cref="OperationCanceledException">cancelToken was canceled, or waitTimeout expired.</exception>
private static async Task RunInThreadWithLock(Action action, TimeSpan? waitTimeout = null, CancellationToken cancelToken = default)
{
await PyRunning.WaitAsync();
// Wait to acquire PyRunning lock
await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false);
try
{
return await Task.Run(() =>
await Task.Run(() =>
{
using (Py.GIL())
{
var result = PythonEngine.Eval(code);
return result.ToString(CultureInfo.InvariantCulture);
action();
}
});
}, cancelToken);
}
finally
{
PyRunning.Release();
}
}
/// <summary>
/// Evaluate Python expression and return its value as a string
/// </summary>
/// <param name="expression"></param>
public static async Task<string> Eval(string expression)
{
return await Eval<string>(expression);
}
/// <summary>
/// Evaluate Python expression and return its value
/// </summary>
/// <param name="expression"></param>
public static Task<T> Eval<T>(string expression)
{
return RunInThreadWithLock(() =>
{
var result = PythonEngine.Eval(expression);
return result.As<T>();
});
}
/// <summary>
/// Execute Python code without returning a value
/// </summary>
/// <param name="code"></param>
public static async Task Exec(string code)
public static Task Exec(string code)
{
await PyRunning.WaitAsync();
try
return RunInThreadWithLock(() =>
{
await Task.Run(() =>
{
using (Py.GIL())
{
PythonEngine.Exec(code);
}
});
}
finally
PythonEngine.Exec(code);
});
}
/// <summary>
/// Return the Python version as a PyVersionInfo struct
/// </summary>
public static async Task<PyVersionInfo> GetVersionInfo()
{
var version = await RunInThreadWithLock(() =>
{
PyRunning.Release();
}
dynamic info = PythonEngine.Eval("tuple(__import__('sys').version_info)");
return new PyVersionInfo(
info[0].As<int>(),
info[1].As<int>(),
info[2].As<int>(),
info[3].As<string>(),
info[4].As<int>()
);
});
return version;
}
}

90
StabilityMatrix/SettingsPage.xaml

@ -15,46 +15,56 @@
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
mc:Ignorable="d">
<Grid>
<StackPanel Orientation="Vertical">
<ui:Card Margin="8,16,8,8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Theme" FontWeight="Bold" FontSize="16" Margin="0,8" />
<ComboBox
Margin="8"
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
Width="500" />
</StackPanel>
</ui:Card>
<!-- Scroll view -->
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<Grid>
<StackPanel Orientation="Vertical">
<ui:Card Margin="8,16,8,8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Theme" FontWeight="Bold" FontSize="16" Margin="0,8" />
<ComboBox
Margin="8"
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
Width="500" />
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Debug Info" FontWeight="Bold" FontSize="16" Margin="0,8" />
<TextBlock Margin="8" Text="{Binding GpuInfo, FallbackValue=3dfx Voodoo 5 6000 - 128MB VRAM}"/>
<TextBlock Margin="8" Text="{Binding GitInfo}"/>
<TextBlock Margin="8" Text="{Binding TestProperty, FallbackValue=TestProperty}"/>
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Embedded Python" FontWeight="Bold" FontSize="16" Margin="0,8" />
<!-- Add a button to show messagebox for python version -->
<Button Margin="8" Content="Check Version Info" Command="{Binding PythonVersionCommand}"/>
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Debug Info" FontWeight="Bold" FontSize="16" Margin="0,8" />
<TextBlock Margin="8" Text="{Binding GpuInfo, FallbackValue=3dfx Voodoo 5 6000 - 128MB VRAM}"/>
<TextBlock Margin="8" Text="{Binding GitInfo}"/>
<TextBlock Margin="8" Text="{Binding TestProperty, FallbackValue=TestProperty}"/>
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Embedded Python" FontWeight="Bold" FontSize="16" Margin="0,8" />
<!-- Add a button to show messagebox for python version -->
<Button Margin="8" Content="Check Version Info" Command="{Binding PythonVersionCommand}"/>
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Debug" FontWeight="Bold" FontSize="16" Margin="0,8" />
<Button Margin="8" Content="Add Installation" Command="{Binding AddInstallationCommand}"/>
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Some Other Setting" FontWeight="Bold" FontSize="16" Margin="0,8" />
<ComboBox
Margin="8"
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
Width="500" />
</StackPanel>
</ui:Card>
</StackPanel>
</Grid>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Some Other Setting" FontWeight="Bold" FontSize="16" Margin="0,8" />
<ComboBox
Margin="8"
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
Width="500" />
</StackPanel>
</ui:Card>
</StackPanel>
</Grid>
</ScrollViewer>
</Page>

7
StabilityMatrix/StabilityMatrix.csproj

@ -10,6 +10,9 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="NLog" Version="5.1.4" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.3" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="Refit" Version="6.3.2" />
<PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" />
<PackageReference Include="WPF-UI" Version="3.0.0-preview.2" />
@ -24,6 +27,10 @@
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
<None Remove="Assets\Icon.ico" />
<Content Include="Assets\Icon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

115
StabilityMatrix/ViewModels/LaunchViewModel.cs

@ -0,0 +1,115 @@
using System;
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;
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<InstalledPackage> InstalledPackages = new();
public LaunchViewModel(ISettingsManager settingsManager)
{
this.settingsManager = settingsManager;
}
public AsyncRelayCommand LaunchCommand => new(async () =>
{
// Clear console
ConsoleOutput = "";
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<string?>(s =>
{
if (s == null) return;
Dispatcher.CurrentDispatcher.Invoke(() =>
{
Debug.WriteLine($"process stdout: {s}");
ConsoleOutput += s + "\n";
});
});
var onExit = new Action<int>(i =>
{
Dispatcher.CurrentDispatcher.Invoke(() =>
{
Debug.WriteLine($"Venv process exited with code {i}");
ConsoleOutput += $"Venv process exited with code {i}";
});
});
var args = "\"" + Path.Combine(packagePath, "launch.py") + "\"";
venv.RunDetached(args, onConsoleOutput, onExit);
});
public void OnLoaded()
{
LoadPackages();
}
private void LoadPackages()
{
var packages = settingsManager.Settings.InstalledPackages;
if (!packages.Any())
{
return;
}
foreach (var package in packages)
{
InstalledPackages.Add(package);
}
}
}

34
StabilityMatrix/ViewModels/SettingsViewModel.cs

@ -1,9 +1,13 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Ookii.Dialogs.Wpf;
using StabilityMatrix.Helper;
using StabilityMatrix.Models;
using Wpf.Ui.Appearance;
using Wpf.Ui.Contracts;
@ -66,7 +70,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";
@ -75,6 +79,34 @@ public partial class SettingsViewModel : ObservableObject
await dialog.ShowAsync();
});
public RelayCommand AddInstallationCommand => new(() =>
{
var initialDirectory = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}\\StabilityMatrix\\Packages";
// Show dialog box to choose a folder
var dialog = new VistaFolderBrowserDialog
{
Description = "Select a folder",
UseDescriptionForTitle = true
};
if (dialog.ShowDialog() != true) return;
var path = dialog.SelectedPath;
if (path == null) return;
// Create package
var package = new InstalledPackage
{
Id = Guid.NewGuid(),
Name = Path.GetFileName(path),
Path = path,
PackageName = "dank-diffusion",
PackageVersion = "v1.0.0",
};
// Add package to settings
settingsManager.AddInstalledPackage(package);
});
public async Task OnLoaded()
{
SelectedTheme = string.IsNullOrWhiteSpace(settingsManager.Settings.Theme)

Loading…
Cancel
Save