From e0462b5bd1f9d494cfdeac10be85296b45e322fb Mon Sep 17 00:00:00 2001 From: jt Date: Thu, 31 Aug 2023 15:55:23 -0700 Subject: [PATCH] formatting --- .../Controls/LogViewerControl.axaml.cs | 10 +- .../Converters/ChangeColorTypeConverter.cs | 16 +- .../LogViewer/Converters/EventIdConverter.cs | 8 +- .../Core/Extensions/LoggerExtensions.cs | 10 +- .../Logging/DataStoreLoggerConfiguration.cs | 52 +-- .../Core/Logging/ILogDataStoreImpl.cs | 2 +- .../LogViewer/Core/Logging/LogDataStore.cs | 2 +- .../LogViewer/Core/Logging/LogEntryColor.cs | 7 +- .../LogViewer/Core/Logging/LogModel.cs | 12 +- .../ViewModels/LogViewerControlViewModel.cs | 2 +- .../Core/ViewModels/ObservableObject.cs | 13 +- .../LogViewer/Core/ViewModels/ViewModel.cs | 4 +- .../LogViewer/DataStoreLoggerTarget.cs | 36 +- .../LogViewer/Extensions/ServicesExtension.cs | 28 +- .../LogViewer/Logging/LogDataStore.cs | 4 +- .../ViewModels/LogWindowViewModel.cs | 7 +- .../Views/LogWindow.axaml.cs | 11 +- StabilityMatrix.Avalonia/App.axaml | 3 +- .../Controls/BetterContentDialog.cs | 128 +++--- .../DesignData/MockLiteDbContext.cs | 15 +- .../DesignData/MockModelIndexService.cs | 4 +- .../ContentDialogProgressViewModelBase.cs | 6 +- .../ViewModels/Dialogs/EnvVarsViewModel.cs | 2 +- .../ViewModels/Dialogs/InstallerViewModel.cs | 303 ++++++++------ .../Dialogs/OneClickInstallViewModel.cs | 80 ++-- .../Dialogs/PackageImportViewModel.cs | 178 ++++---- .../PackageModificationDialogViewModel.cs | 24 +- .../Dialogs/SelectDataDirectoryViewModel.cs | 93 +++-- .../Dialogs/SelectModelVersionViewModel.cs | 49 ++- .../ViewModels/Dialogs/UpdateViewModel.cs | 64 +-- .../ViewModels/LaunchPageViewModel.cs | 329 ++++++++------- .../PackageManager/PackageCardViewModel.cs | 182 ++++---- .../ViewModels/PackageManagerViewModel.cs | 48 ++- .../ViewModels/SettingsViewModel.cs | 322 ++++++++------- .../PackageModificationDialog.axaml.cs | 18 +- .../Views/LaunchPageView.axaml.cs | 16 +- .../Views/MainWindow.axaml | 1 + .../Database/ILiteDbContext.cs | 3 +- .../Database/LiteDbContext.cs | 115 ++++-- StabilityMatrix.Core/Helper/SharedFolders.cs | 84 ++-- .../Models/Database/GitCommit.cs | 3 +- .../Models/Database/LocalModelFile.cs | 7 +- .../Models/FileInterfaces/FilePath.cs | 75 ++-- .../Models/InstalledPackage.cs | 91 ++-- .../Models/InstalledPackageVersion.cs | 13 +- .../AddInstalledPackageStep.cs | 6 +- .../DownloadPackageVersionStep.cs | 9 +- .../IPackageModificationRunner.cs | 2 +- .../PackageModification/InstallPackageStep.cs | 6 +- .../PackageModificationRunner.cs | 5 +- .../SetupModelFoldersStep.cs | 12 +- .../SetupPrerequisitesStep.cs | 11 +- .../Models/Packages/A3WebUI.cs | 249 +++++------ .../Models/Packages/BaseGitPackage.cs | 194 +++++---- .../Models/Packages/BasePackage.cs | 148 ++++--- .../Models/Packages/ComfyUI.cs | 227 +++++----- .../Models/Packages/DankDiffusion.cs | 45 +- .../Models/Packages/Fooocus.cs | 153 +++---- .../Models/Packages/InvokeAI.cs | 270 ++++++------ .../Models/Packages/UnknownPackage.cs | 84 ++-- .../Models/Packages/VladAutomatic.cs | 387 ++++++++++-------- .../Models/Packages/VoltaML.cs | 226 +++++----- .../Services/IModelIndexService.cs | 2 +- .../Services/ISettingsManager.cs | 11 +- .../Services/ModelIndexService.cs | 71 ++-- .../Services/SettingsManager.cs | 259 +++++++----- .../Services/TrackedDownloadService.cs | 98 +++-- 67 files changed, 2837 insertions(+), 2118 deletions(-) diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml.cs index 4cd3e8b4..9bd727a0 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml.cs @@ -8,8 +8,7 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls; public partial class LogViewerControl : UserControl { - public LogViewerControl() - => InitializeComponent(); + public LogViewerControl() => InitializeComponent(); private ILogDataStoreImpl? vm; private LogModel? item; @@ -17,7 +16,7 @@ public partial class LogViewerControl : UserControl protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); - + if (DataContext is null) return; @@ -45,8 +44,9 @@ public partial class LogViewerControl : UserControl protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); - - if (vm is null) return; + + if (vm is null) + return; vm.DataStore.Entries.CollectionChanged -= OnCollectionChanged; } } diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/ChangeColorTypeConverter.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/ChangeColorTypeConverter.cs index 9816aa25..90d8d6ef 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/ChangeColorTypeConverter.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/ChangeColorTypeConverter.cs @@ -13,13 +13,15 @@ public class ChangeColorTypeConverter : IValueConverter return new SolidColorBrush((Color)(parameter ?? Colors.Black)); var sysDrawColor = (SysDrawColor)value!; - return new SolidColorBrush(Color.FromArgb( - sysDrawColor.A, - sysDrawColor.R, - sysDrawColor.G, - sysDrawColor.B)); + return new SolidColorBrush( + Color.FromArgb(sysDrawColor.A, sysDrawColor.R, sysDrawColor.G, sysDrawColor.B) + ); } - public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - => throw new NotImplementedException(); + public object ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture + ) => throw new NotImplementedException(); } diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/EventIdConverter.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/EventIdConverter.cs index 081ce049..91498a95 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/EventIdConverter.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/EventIdConverter.cs @@ -17,6 +17,10 @@ public class EventIdConverter : IValueConverter } // If not implemented, an error is thrown - public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - => new EventId(0, value?.ToString() ?? string.Empty); + public object ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture + ) => new EventId(0, value?.ToString() ?? string.Empty); } diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Extensions/LoggerExtensions.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Extensions/LoggerExtensions.cs index 7c84fa05..e1f72f18 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Extensions/LoggerExtensions.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Extensions/LoggerExtensions.cs @@ -4,8 +4,14 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Extensions; public static class LoggerExtensions { - public static void Emit(this ILogger logger, EventId eventId, - LogLevel logLevel, string message, Exception? exception = null, params object?[] args) + public static void Emit( + this ILogger logger, + EventId eventId, + LogLevel logLevel, + string message, + Exception? exception = null, + params object?[] args + ) { if (logger is null) return; diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/DataStoreLoggerConfiguration.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/DataStoreLoggerConfiguration.cs index 35d93f0c..4a285f41 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/DataStoreLoggerConfiguration.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/DataStoreLoggerConfiguration.cs @@ -6,42 +6,28 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; public class DataStoreLoggerConfiguration { #region Properties - + public EventId EventId { get; set; } - public Dictionary Colors { get; } = new() - { - [LogLevel.Trace] = new LogEntryColor - { - Foreground = Color.DarkGray - }, - [LogLevel.Debug] = new LogEntryColor - { - Foreground = Color.Gray - }, - [LogLevel.Information] = new LogEntryColor - { - Foreground = Color.WhiteSmoke, - }, - [LogLevel.Warning] = new LogEntryColor - { - Foreground = Color.Orange - }, - [LogLevel.Error] = new LogEntryColor - { - Foreground = Color.White, - Background = Color.OrangeRed - }, - [LogLevel.Critical] = new LogEntryColor - { - Foreground = Color.White, - Background = Color.Red - }, - [LogLevel.None] = new LogEntryColor + public Dictionary Colors { get; } = + new() { - Foreground = Color.Magenta - } - }; + [LogLevel.Trace] = new LogEntryColor { Foreground = Color.DarkGray }, + [LogLevel.Debug] = new LogEntryColor { Foreground = Color.Gray }, + [LogLevel.Information] = new LogEntryColor { Foreground = Color.WhiteSmoke, }, + [LogLevel.Warning] = new LogEntryColor { Foreground = Color.Orange }, + [LogLevel.Error] = new LogEntryColor + { + Foreground = Color.White, + Background = Color.OrangeRed + }, + [LogLevel.Critical] = new LogEntryColor + { + Foreground = Color.White, + Background = Color.Red + }, + [LogLevel.None] = new LogEntryColor { Foreground = Color.Magenta } + }; #endregion } diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/ILogDataStoreImpl.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/ILogDataStoreImpl.cs index 1f3f3811..0c3ee641 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/ILogDataStoreImpl.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/ILogDataStoreImpl.cs @@ -3,4 +3,4 @@ public interface ILogDataStoreImpl { public ILogDataStore DataStore { get; } -} \ No newline at end of file +} diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogDataStore.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogDataStore.cs index 3f32ea16..68e1f48f 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogDataStore.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogDataStore.cs @@ -6,7 +6,7 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; public class LogDataStore : ILogDataStore { public static LogDataStore Instance { get; } = new(); - + #region Fields private static readonly SemaphoreSlim _semaphore = new(initialCount: 1); diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogEntryColor.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogEntryColor.cs index 42c3b6e0..9e2eb4f1 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogEntryColor.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogEntryColor.cs @@ -4,9 +4,7 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; public class LogEntryColor { - public LogEntryColor() - { - } + public LogEntryColor() { } public LogEntryColor(Color foreground, Color background) { @@ -16,5 +14,4 @@ public class LogEntryColor public Color Foreground { get; set; } = Color.Black; public Color Background { get; set; } = Color.Transparent; - -} \ No newline at end of file +} diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogModel.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogModel.cs index af1ddca3..bedfbfc4 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogModel.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogModel.cs @@ -13,11 +13,11 @@ public class LogModel public EventId EventId { get; set; } public object? State { get; set; } - + public string? LoggerName { get; set; } - + public string? CallerClassName { get; set; } - + public string? CallerMemberName { get; set; } public string? Exception { get; set; } @@ -26,8 +26,6 @@ public class LogModel #endregion - public string LoggerDisplayName => - LoggerName? - .Split('.', StringSplitOptions.RemoveEmptyEntries) - .LastOrDefault() ?? ""; + public string LoggerDisplayName => + LoggerName?.Split('.', StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? ""; } diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/LogViewerControlViewModel.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/LogViewerControlViewModel.cs index 1789a0e8..1e4aad20 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/LogViewerControlViewModel.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/LogViewerControlViewModel.cs @@ -18,4 +18,4 @@ public class LogViewerControlViewModel : ViewModel, ILogDataStoreImpl public ILogDataStore DataStore { get; set; } #endregion -} \ No newline at end of file +} diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ObservableObject.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ObservableObject.cs index e6792bea..b800b23f 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ObservableObject.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ObservableObject.cs @@ -5,9 +5,14 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels; public class ObservableObject : INotifyPropertyChanged { - protected bool Set(ref TValue field, TValue newValue, [CallerMemberName] string? propertyName = null) + protected bool Set( + ref TValue field, + TValue newValue, + [CallerMemberName] string? propertyName = null + ) { - if (EqualityComparer.Default.Equals(field, newValue)) return false; + if (EqualityComparer.Default.Equals(field, newValue)) + return false; field = newValue; OnPropertyChanged(propertyName); @@ -16,6 +21,6 @@ public class ObservableObject : INotifyPropertyChanged public event PropertyChangedEventHandler? PropertyChanged; - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ViewModel.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ViewModel.cs index b7b4aec0..f44a0197 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ViewModel.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ViewModel.cs @@ -1,3 +1,5 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels; -public class ViewModel : ObservableObject { /* skip */ } +public class ViewModel + : ObservableObject { /* skip */ +} diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/DataStoreLoggerTarget.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/DataStoreLoggerTarget.cs index 608b7245..22646b04 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/DataStoreLoggerTarget.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/DataStoreLoggerTarget.cs @@ -54,21 +54,27 @@ public class DataStoreLoggerTarget : TargetWithLayout } // add log entry - _dataStore?.AddEntry(new LogModel - { - Timestamp = DateTime.UtcNow, - LogLevel = logLevel, - // do we override the default EventId if it exists? - EventId = eventId.Id == 0 && (_config?.EventId.Id ?? 0) != 0 ? _config!.EventId : eventId, - State = message, - LoggerName = logEvent.LoggerName, - CallerClassName = logEvent.CallerClassName, - CallerMemberName = logEvent.CallerMemberName, - Exception = logEvent.Exception?.Message ?? (logLevel == MsLogLevel.Error ? message : ""), - Color = _config!.Colors[logLevel], - }); - - Debug.WriteLine($"--- [{logLevel.ToString()[..3]}] {message} - {logEvent.Exception?.Message ?? "no error"}"); + _dataStore?.AddEntry( + new LogModel + { + Timestamp = DateTime.UtcNow, + LogLevel = logLevel, + // do we override the default EventId if it exists? + EventId = + eventId.Id == 0 && (_config?.EventId.Id ?? 0) != 0 ? _config!.EventId : eventId, + State = message, + LoggerName = logEvent.LoggerName, + CallerClassName = logEvent.CallerClassName, + CallerMemberName = logEvent.CallerMemberName, + Exception = + logEvent.Exception?.Message ?? (logLevel == MsLogLevel.Error ? message : ""), + Color = _config!.Colors[logLevel], + } + ); + + Debug.WriteLine( + $"--- [{logLevel.ToString()[..3]}] {message} - {logEvent.Exception?.Message ?? "no error"}" + ); } #endregion diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Extensions/ServicesExtension.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Extensions/ServicesExtension.cs index 9ab61604..7c89ee05 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Extensions/ServicesExtension.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Extensions/ServicesExtension.cs @@ -20,25 +20,31 @@ public static class ServicesExtension return services; } - + public static IServiceCollection AddLogViewer( - this IServiceCollection services, - Action configure) + this IServiceCollection services, + Action configure + ) { services.AddSingleton(Core.Logging.LogDataStore.Instance); services.AddSingleton(); services.Configure(configure); - + return services; } - - public static ILoggingBuilder AddNLogTargets(this ILoggingBuilder builder, IConfiguration config) + + public static ILoggingBuilder AddNLogTargets( + this ILoggingBuilder builder, + IConfiguration config + ) { LogManager .Setup() // Register custom Target - .SetupExtensions(extensionBuilder => - extensionBuilder.RegisterTarget("DataStoreLogger")); + .SetupExtensions( + extensionBuilder => + extensionBuilder.RegisterTarget("DataStoreLogger") + ); /*builder .ClearProviders() @@ -56,7 +62,11 @@ public static class ServicesExtension return builder; } - public static ILoggingBuilder AddNLogTargets(this ILoggingBuilder builder, IConfiguration config, Action configure) + public static ILoggingBuilder AddNLogTargets( + this ILoggingBuilder builder, + IConfiguration config, + Action configure + ) { builder.AddNLogTargets(config); builder.Services.Configure(configure); diff --git a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Logging/LogDataStore.cs b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Logging/LogDataStore.cs index 4dbdbf50..cc9c5d2b 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Logging/LogDataStore.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/LogViewer/Logging/LogDataStore.cs @@ -6,8 +6,8 @@ public class LogDataStore : Core.Logging.LogDataStore { #region Methods - public override async void AddEntry(Core.Logging.LogModel logModel) - => await Dispatcher.UIThread.InvokeAsync(() => base.AddEntry(logModel)); + public override async void AddEntry(Core.Logging.LogModel logModel) => + await Dispatcher.UIThread.InvokeAsync(() => base.AddEntry(logModel)); #endregion } diff --git a/StabilityMatrix.Avalonia.Diagnostics/ViewModels/LogWindowViewModel.cs b/StabilityMatrix.Avalonia.Diagnostics/ViewModels/LogWindowViewModel.cs index 860e2b68..5a40bf78 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/ViewModels/LogWindowViewModel.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/ViewModels/LogWindowViewModel.cs @@ -6,15 +6,14 @@ namespace StabilityMatrix.Avalonia.Diagnostics.ViewModels; public class LogWindowViewModel { public LogViewerControlViewModel LogViewer { get; } - + public LogWindowViewModel(LogViewerControlViewModel logViewer) { LogViewer = logViewer; } - + public static LogWindowViewModel FromServiceProvider(IServiceProvider services) { - return new LogWindowViewModel( - services.GetRequiredService()); + return new LogWindowViewModel(services.GetRequiredService()); } } diff --git a/StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml.cs b/StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml.cs index d3ead407..9a7a703d 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml.cs +++ b/StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml.cs @@ -16,13 +16,18 @@ public partial class LogWindow : Window { return Attach(root, serviceProvider, new KeyGesture(Key.F11)); } - - public static IDisposable Attach(TopLevel root, IServiceProvider serviceProvider, KeyGesture gesture) + + public static IDisposable Attach( + TopLevel root, + IServiceProvider serviceProvider, + KeyGesture gesture + ) { return (root ?? throw new ArgumentNullException(nameof(root))).AddDisposableHandler( KeyDownEvent, PreviewKeyDown, - RoutingStrategies.Tunnel); + RoutingStrategies.Tunnel + ); void PreviewKeyDown(object? sender, KeyEventArgs e) { diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index 3977db27..bf99f45c 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -22,7 +22,8 @@ - + diff --git a/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs b/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs index 43d1a5d6..d5dde1dc 100644 --- a/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs +++ b/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs @@ -20,54 +20,64 @@ public class BetterContentDialog : ContentDialog #region Reflection Shenanigans for setting content dialog result [NotNull] protected static readonly FieldInfo? ResultField = typeof(ContentDialog).GetField( - "_result",BindingFlags.Instance | BindingFlags.NonPublic); - + "_result", + BindingFlags.Instance | BindingFlags.NonPublic + ); + protected ContentDialogResult Result { - get => (ContentDialogResult) ResultField.GetValue(this)!; + get => (ContentDialogResult)ResultField.GetValue(this)!; set => ResultField.SetValue(this, value); } - + [NotNull] protected static readonly MethodInfo? HideCoreMethod = typeof(ContentDialog).GetMethod( - "HideCore", BindingFlags.Instance | BindingFlags.NonPublic); + "HideCore", + BindingFlags.Instance | BindingFlags.NonPublic + ); protected void HideCore() { HideCoreMethod.Invoke(this, null); } - + // Also get button properties to hide on command execution change [NotNull] protected static readonly FieldInfo? PrimaryButtonField = typeof(ContentDialog).GetField( - "_primaryButton", BindingFlags.Instance | BindingFlags.NonPublic); - + "_primaryButton", + BindingFlags.Instance | BindingFlags.NonPublic + ); + protected Button? PrimaryButton { - get => (Button?) PrimaryButtonField.GetValue(this)!; + get => (Button?)PrimaryButtonField.GetValue(this)!; set => PrimaryButtonField.SetValue(this, value); } - + [NotNull] protected static readonly FieldInfo? SecondaryButtonField = typeof(ContentDialog).GetField( - "_secondaryButton", BindingFlags.Instance | BindingFlags.NonPublic); - - protected Button? SecondaryButton + "_secondaryButton", + BindingFlags.Instance | BindingFlags.NonPublic + ); + + protected Button? SecondaryButton { - get => (Button?) SecondaryButtonField.GetValue(this)!; + get => (Button?)SecondaryButtonField.GetValue(this)!; set => SecondaryButtonField.SetValue(this, value); } - + [NotNull] protected static readonly FieldInfo? CloseButtonField = typeof(ContentDialog).GetField( - "_closeButton", BindingFlags.Instance | BindingFlags.NonPublic); - - protected Button? CloseButton + "_closeButton", + BindingFlags.Instance | BindingFlags.NonPublic + ); + + protected Button? CloseButton { - get => (Button?) CloseButtonField.GetValue(this)!; + get => (Button?)CloseButtonField.GetValue(this)!; set => CloseButtonField.SetValue(this, value); } - + static BetterContentDialog() { if (ResultField is null) @@ -84,11 +94,13 @@ public class BetterContentDialog : ContentDialog } } #endregion - + protected override Type StyleKeyOverride { get; } = typeof(ContentDialog); - public static readonly StyledProperty IsFooterVisibleProperty = AvaloniaProperty.Register( - "IsFooterVisible", true); + public static readonly StyledProperty IsFooterVisibleProperty = AvaloniaProperty.Register< + BetterContentDialog, + bool + >("IsFooterVisible", true); public bool IsFooterVisible { @@ -96,9 +108,11 @@ public class BetterContentDialog : ContentDialog set => SetValue(IsFooterVisibleProperty, value); } - public static readonly StyledProperty ContentVerticalScrollBarVisibilityProperty - = AvaloniaProperty.Register( - "ContentScrollBarVisibility", ScrollBarVisibility.Auto); + public static readonly StyledProperty ContentVerticalScrollBarVisibilityProperty = + AvaloniaProperty.Register( + "ContentScrollBarVisibility", + ScrollBarVisibility.Auto + ); public ScrollBarVisibility ContentVerticalScrollBarVisibility { @@ -106,17 +120,17 @@ public class BetterContentDialog : ContentDialog set => SetValue(ContentVerticalScrollBarVisibilityProperty, value); } - public static readonly StyledProperty MinDialogWidthProperty = AvaloniaProperty.Register( - "MinDialogWidth"); + public static readonly StyledProperty MinDialogWidthProperty = + AvaloniaProperty.Register("MinDialogWidth"); public double MinDialogWidth { get => GetValue(MinDialogWidthProperty); set => SetValue(MinDialogWidthProperty, value); } - - public static readonly StyledProperty MaxDialogWidthProperty = AvaloniaProperty.Register( - "MaxDialogWidth"); + + public static readonly StyledProperty MaxDialogWidthProperty = + AvaloniaProperty.Register("MaxDialogWidth"); public double MaxDialogWidth { @@ -124,8 +138,8 @@ public class BetterContentDialog : ContentDialog set => SetValue(MaxDialogWidthProperty, value); } - public static readonly StyledProperty MaxDialogHeightProperty = AvaloniaProperty.Register( - "MaxDialogHeight"); + public static readonly StyledProperty MaxDialogHeightProperty = + AvaloniaProperty.Register("MaxDialogHeight"); public double MaxDialogHeight { @@ -133,15 +147,14 @@ public class BetterContentDialog : ContentDialog set => SetValue(MaxDialogHeightProperty, value); } - public static readonly StyledProperty ContentMarginProperty = AvaloniaProperty.Register( - "ContentMargin"); + public static readonly StyledProperty ContentMarginProperty = + AvaloniaProperty.Register("ContentMargin"); public Thickness ContentMargin { get => GetValue(ContentMarginProperty); set => SetValue(ContentMarginProperty, value); } - public BetterContentDialog() { @@ -156,13 +169,16 @@ public class BetterContentDialog : ContentDialog viewModel.SecondaryButtonClick += OnDialogButtonClick; viewModel.CloseButtonClick += OnDialogButtonClick; } - else if ((Content as Control)?.DataContext is ContentDialogProgressViewModelBase progressViewModel) + else if ( + (Content as Control)?.DataContext + is ContentDialogProgressViewModelBase progressViewModel + ) { progressViewModel.PrimaryButtonClick += OnDialogButtonClick; progressViewModel.SecondaryButtonClick += OnDialogButtonClick; progressViewModel.CloseButtonClick += OnDialogButtonClick; } - + // If commands provided, bind OnCanExecuteChanged to hide buttons // otherwise link visibility to IsEnabled if (PrimaryButton is not null) @@ -176,10 +192,11 @@ public class BetterContentDialog : ContentDialog } else { - PrimaryButton.IsVisible = IsPrimaryButtonEnabled && !string.IsNullOrEmpty(PrimaryButtonText); + PrimaryButton.IsVisible = + IsPrimaryButtonEnabled && !string.IsNullOrEmpty(PrimaryButtonText); } } - + if (SecondaryButton is not null) { if (SecondaryButtonCommand is not null) @@ -191,10 +208,11 @@ public class BetterContentDialog : ContentDialog } else { - SecondaryButton.IsVisible = IsSecondaryButtonEnabled && !string.IsNullOrEmpty(SecondaryButtonText); + SecondaryButton.IsVisible = + IsSecondaryButtonEnabled && !string.IsNullOrEmpty(SecondaryButtonText); } } - + if (CloseButton is not null) { if (CloseButtonCommand is not null) @@ -216,7 +234,7 @@ public class BetterContentDialog : ContentDialog protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); - + TryBindButtons(); } @@ -242,7 +260,7 @@ public class BetterContentDialog : ContentDialog var border = VisualChildren[0] as Border; var panel = border?.Child as Panel; var faBorder = panel?.Children[0] as FABorder; - + // Set dialog bounds if (MaxDialogWidth > 0) { @@ -257,36 +275,38 @@ public class BetterContentDialog : ContentDialog { faBorder!.MaxHeight = MaxDialogHeight; } - + var border2 = faBorder?.Child as Border; // Named Grid 'DialogSpace' - if (border2?.Child is not Grid dialogSpaceGrid) throw new InvalidOperationException("Could not find DialogSpace grid"); - + if (border2?.Child is not Grid dialogSpaceGrid) + throw new InvalidOperationException("Could not find DialogSpace grid"); + var scrollViewer = dialogSpaceGrid.Children[0] as ScrollViewer; var actualBorder = dialogSpaceGrid.Children[1] as Border; - + // Get the parent border, which is what we want to hide if (scrollViewer is null || actualBorder is null) { throw new InvalidOperationException("Could not find parent border"); } - + var subBorder = scrollViewer.Content as Border; var subGrid = subBorder?.Child as Grid; - if (subGrid is null) throw new InvalidOperationException("Could not find sub grid"); + if (subGrid is null) + throw new InvalidOperationException("Could not find sub grid"); var contentControlTitle = subGrid.Children[0] as ContentControl; // Hide title if empty - if (Title is null or string {Length: 0}) + if (Title is null or string { Length: 0 }) { contentControlTitle!.IsVisible = false; } - + // Set footer and scrollbar visibility states actualBorder.IsVisible = IsFooterVisible; scrollViewer.VerticalScrollBarVisibility = ContentVerticalScrollBarVisibility; - + // Also call the vm's OnLoad - if (Content is Control {DataContext: ViewModelBase viewModel}) + if (Content is Control { DataContext: ViewModelBase viewModel }) { viewModel.OnLoaded(); Dispatcher.UIThread.InvokeAsync(viewModel.OnLoadedAsync).SafeFireAndForget(); diff --git a/StabilityMatrix.Avalonia/DesignData/MockLiteDbContext.cs b/StabilityMatrix.Avalonia/DesignData/MockLiteDbContext.cs index 3b56d19a..92e323fe 100644 --- a/StabilityMatrix.Avalonia/DesignData/MockLiteDbContext.cs +++ b/StabilityMatrix.Avalonia/DesignData/MockLiteDbContext.cs @@ -12,11 +12,16 @@ public class MockLiteDbContext : ILiteDbContext { public LiteDatabaseAsync Database => throw new NotImplementedException(); public ILiteCollectionAsync CivitModels => throw new NotImplementedException(); - public ILiteCollectionAsync CivitModelVersions => throw new NotImplementedException(); - public ILiteCollectionAsync CivitModelQueryCache => throw new NotImplementedException(); - public ILiteCollectionAsync LocalModelFiles => throw new NotImplementedException(); - - public Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3) + public ILiteCollectionAsync CivitModelVersions => + throw new NotImplementedException(); + public ILiteCollectionAsync CivitModelQueryCache => + throw new NotImplementedException(); + public ILiteCollectionAsync LocalModelFiles => + throw new NotImplementedException(); + + public Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync( + string hashBlake3 + ) { return Task.FromResult<(CivitModel?, CivitModelVersion?)>((null, null)); } diff --git a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs index ab52abf8..1c501fae 100644 --- a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs +++ b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs @@ -21,7 +21,5 @@ public class MockModelIndexService : IModelIndexService } /// - public void BackgroundRefreshIndex() - { - } + public void BackgroundRefreshIndex() { } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs index eb775e18..e329d085 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs @@ -8,17 +8,17 @@ public class ContentDialogProgressViewModelBase : ProgressViewModel public event EventHandler? PrimaryButtonClick; public event EventHandler? SecondaryButtonClick; public event EventHandler? CloseButtonClick; - + public virtual void OnPrimaryButtonClick() { PrimaryButtonClick?.Invoke(this, ContentDialogResult.Primary); } - + public virtual void OnSecondaryButtonClick() { SecondaryButtonClick?.Invoke(this, ContentDialogResult.Secondary); } - + public virtual void OnCloseButtonClick() { CloseButtonClick?.Invoke(this, ContentDialogResult.None); diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs index 5c96fa80..be29ca59 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs @@ -20,7 +20,7 @@ public partial class EnvVarsViewModel : ContentDialogViewModelBase private ObservableCollection envVars = new(); public DataGridCollectionView EnvVarsView => new(EnvVars); - + [RelayCommand] private void AddRow() { diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs index 808bb078..f41b6dea 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs @@ -31,55 +31,83 @@ using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; - public partial class InstallerViewModel : ContentDialogViewModelBase { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + private readonly ISettingsManager settingsManager; private readonly IPyRunner pyRunner; private readonly IDownloadService downloadService; private readonly INotificationService notificationService; private readonly IPrerequisiteHelper prerequisiteHelper; - - [ObservableProperty] private BasePackage selectedPackage; - [ObservableProperty] private PackageVersion? selectedVersion; - [ObservableProperty] private IReadOnlyList? availablePackages; - [ObservableProperty] private ObservableCollection? availableCommits; - [ObservableProperty] private ObservableCollection? availableVersions; - [ObservableProperty] private GitCommit? selectedCommit; - [ObservableProperty] private string? releaseNotes; - [ObservableProperty] private string latestVersionText = string.Empty; - [ObservableProperty] private bool isAdvancedMode; - [ObservableProperty] private bool showDuplicateWarning; - [ObservableProperty] private string? installName; - [ObservableProperty] private SharedFolderMethod selectedSharedFolderMethod; - + + [ObservableProperty] + private BasePackage selectedPackage; + + [ObservableProperty] + private PackageVersion? selectedVersion; + + [ObservableProperty] + private IReadOnlyList? availablePackages; + + [ObservableProperty] + private ObservableCollection? availableCommits; + + [ObservableProperty] + private ObservableCollection? availableVersions; + + [ObservableProperty] + private GitCommit? selectedCommit; + + [ObservableProperty] + private string? releaseNotes; + + [ObservableProperty] + private string latestVersionText = string.Empty; + + [ObservableProperty] + private bool isAdvancedMode; + + [ObservableProperty] + private bool showDuplicateWarning; + + [ObservableProperty] + private string? installName; + + [ObservableProperty] + private SharedFolderMethod selectedSharedFolderMethod; + [ObservableProperty] [NotifyPropertyChangedFor(nameof(ShowTorchVersionOptions))] private TorchVersion selectedTorchVersion; - + // Version types (release or commit) [ObservableProperty] - [NotifyPropertyChangedFor(nameof(ReleaseLabelText), - nameof(IsReleaseMode), nameof(SelectedVersion))] + [NotifyPropertyChangedFor( + nameof(ReleaseLabelText), + nameof(IsReleaseMode), + nameof(SelectedVersion) + )] private PackageVersionType selectedVersionType = PackageVersionType.Commit; - + [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsReleaseModeAvailable))] - private PackageVersionType availableVersionTypes = + private PackageVersionType availableVersionTypes = PackageVersionType.GithubRelease | PackageVersionType.Commit; - - + public string ReleaseLabelText => IsReleaseMode ? "Version" : "Branch"; public bool IsReleaseMode { get => SelectedVersionType == PackageVersionType.GithubRelease; - set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit; + set => + SelectedVersionType = value + ? PackageVersionType.GithubRelease + : PackageVersionType.Commit; } - public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease); + public bool IsReleaseModeAvailable => + AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease); public bool ShowTorchVersionOptions => SelectedTorchVersion != TorchVersion.None; - + public ProgressViewModel InstallProgress { get; } = new(); public IEnumerable Steps { get; set; } @@ -87,8 +115,10 @@ public partial class InstallerViewModel : ContentDialogViewModelBase ISettingsManager settingsManager, IPackageFactory packageFactory, IPyRunner pyRunner, - IDownloadService downloadService, INotificationService notificationService, - IPrerequisiteHelper prerequisiteHelper) + IDownloadService downloadService, + INotificationService notificationService, + IPrerequisiteHelper prerequisiteHelper + ) { this.settingsManager = settingsManager; this.pyRunner = pyRunner; @@ -97,36 +127,43 @@ public partial class InstallerViewModel : ContentDialogViewModelBase this.prerequisiteHelper = prerequisiteHelper; // AvailablePackages and SelectedPackage - AvailablePackages = new ObservableCollection(packageFactory.GetAllAvailablePackages()); + AvailablePackages = new ObservableCollection( + packageFactory.GetAllAvailablePackages() + ); SelectedPackage = AvailablePackages[0]; } public override void OnLoaded() { - if (AvailablePackages == null) return; + if (AvailablePackages == null) + return; SelectedPackage = AvailablePackages[0]; IsReleaseMode = !SelectedPackage.ShouldIgnoreReleases; } - + public override async Task OnLoadedAsync() { - if (Design.IsDesignMode) return; + if (Design.IsDesignMode) + return; // Check for updates try { var versionOptions = await SelectedPackage.GetAllVersionOptions(); if (IsReleaseMode) { - AvailableVersions = - new ObservableCollection(versionOptions.AvailableVersions); - if (!AvailableVersions.Any()) return; + AvailableVersions = new ObservableCollection( + versionOptions.AvailableVersions + ); + if (!AvailableVersions.Any()) + return; SelectedVersion = AvailableVersions.First(x => !x.IsPrerelease); } else { - AvailableVersions = - new ObservableCollection(versionOptions.AvailableBranches); + AvailableVersions = new ObservableCollection( + versionOptions.AvailableBranches + ); UpdateSelectedVersionToLatestMain(); } @@ -137,14 +174,17 @@ public partial class InstallerViewModel : ContentDialogViewModelBase Logger.Warn("Error getting versions: {Exception}", e.ToString()); } } - + [RelayCommand] private async Task Install() { - var result = await notificationService.TryAsync(ActuallyInstall(), "Could not install package"); + var result = await notificationService.TryAsync( + ActuallyInstall(), + "Could not install package" + ); if (result.IsSuccessful) { - OnPrimaryButtonClick(); + OnPrimaryButtonClick(); } else { @@ -160,41 +200,58 @@ public partial class InstallerViewModel : ContentDialogViewModelBase await dialog.ShowAsync(); } } - + private Task ActuallyInstall() { if (string.IsNullOrWhiteSpace(InstallName)) { - notificationService.Show(new Notification("Package name is empty", - "Please enter a name for the package", NotificationType.Error)); + notificationService.Show( + new Notification( + "Package name is empty", + "Please enter a name for the package", + NotificationType.Error + ) + ); return Task.CompletedTask; } - + var installLocation = Path.Combine(settingsManager.LibraryDir, "Packages", InstallName); var prereqStep = new SetupPrerequisitesStep(prerequisiteHelper, pyRunner); - + var downloadOptions = new DownloadPackageVersionOptions(); var installedVersion = new InstalledPackageVersion(); if (IsReleaseMode) { - downloadOptions.VersionTag = SelectedVersion?.TagName ?? - throw new NullReferenceException("Selected version is null"); + downloadOptions.VersionTag = + SelectedVersion?.TagName + ?? throw new NullReferenceException("Selected version is null"); installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag; } else { - downloadOptions.CommitHash = SelectedCommit?.Sha ?? - throw new NullReferenceException("Selected commit is null"); - installedVersion.InstalledBranch = SelectedVersion?.TagName ?? - throw new NullReferenceException("Selected version is null"); + downloadOptions.CommitHash = + SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null"); + installedVersion.InstalledBranch = + SelectedVersion?.TagName + ?? throw new NullReferenceException("Selected version is null"); installedVersion.InstalledCommitSha = downloadOptions.CommitHash; } - var downloadStep = - new DownloadPackageVersionStep(SelectedPackage, installLocation, downloadOptions); - var installStep = new InstallPackageStep(SelectedPackage, SelectedTorchVersion, installLocation); - var setupModelFoldersStep = new SetupModelFoldersStep(SelectedPackage, - SelectedSharedFolderMethod, installLocation); + var downloadStep = new DownloadPackageVersionStep( + SelectedPackage, + installLocation, + downloadOptions + ); + var installStep = new InstallPackageStep( + SelectedPackage, + SelectedTorchVersion, + installLocation + ); + var setupModelFoldersStep = new SetupModelFoldersStep( + SelectedPackage, + SelectedSharedFolderMethod, + installLocation + ); var package = new InstalledPackage { @@ -210,7 +267,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase }; var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, package); - + var steps = new List { prereqStep, @@ -228,7 +285,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase { OnCloseButtonClick(); } - + private void UpdateSelectedVersionToLatestMain() { if (AvailableVersions is null) @@ -241,34 +298,34 @@ public partial class InstallerViewModel : ContentDialogViewModelBase var version = AvailableVersions.FirstOrDefault(x => x.TagName == "master"); // If not found, try main version ??= AvailableVersions.FirstOrDefault(x => x.TagName == "main"); - + // If still not found, just use the first one version ??= AvailableVersions[0]; - + SelectedVersion = version; } } - + [RelayCommand] private async Task ShowPreview() { var url = SelectedPackage.PreviewImageUri.ToString(); var imageStream = await downloadService.GetImageStreamFromUrl(url); var bitmap = new Bitmap(imageStream); - + var dialog = new ContentDialog { PrimaryButtonText = "Open in Browser", CloseButtonText = "Close", Content = new Image { - Source = bitmap, - Stretch = Stretch.Uniform, + Source = bitmap, + Stretch = Stretch.Uniform, MaxHeight = 500, HorizontalAlignment = HorizontalAlignment.Center } }; - + var result = await dialog.ShowAsync(); if (result == ContentDialogResult.Primary) { @@ -284,10 +341,11 @@ public partial class InstallerViewModel : ContentDialogViewModelBase SelectedVersionType = value; } } - + // When changing branch / release modes, refresh // ReSharper disable once UnusedParameterInPartialMethod - partial void OnSelectedVersionTypeChanged(PackageVersionType value) => OnSelectedPackageChanged(SelectedPackage); + partial void OnSelectedVersionTypeChanged(PackageVersionType value) => + OnSelectedPackageChanged(SelectedPackage); partial void OnSelectedPackageChanged(BasePackage value) { @@ -302,74 +360,81 @@ public partial class InstallerViewModel : ContentDialogViewModelBase SelectedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod; SelectedTorchVersion = SelectedPackage.GetRecommendedTorchVersion(); - if (Design.IsDesignMode) return; - - Dispatcher.UIThread.InvokeAsync(async () => - { - Logger.Debug($"Release mode: {IsReleaseMode}"); - var versionOptions = await value.GetAllVersionOptions(); - - AvailableVersions = IsReleaseMode - ? new ObservableCollection(versionOptions.AvailableVersions) - : new ObservableCollection(versionOptions.AvailableBranches); - - SelectedVersion = AvailableVersions.First(x => !x.IsPrerelease); - ReleaseNotes = SelectedVersion.ReleaseNotesMarkdown; - - Logger.Debug($"Loaded release notes for {ReleaseNotes}"); - - if (!IsReleaseMode) + if (Design.IsDesignMode) + return; + + Dispatcher.UIThread + .InvokeAsync(async () => { - var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList(); - if (commits is null || commits.Count == 0) return; - - AvailableCommits = new ObservableCollection(commits); - SelectedCommit = AvailableCommits[0]; - UpdateSelectedVersionToLatestMain(); - } + Logger.Debug($"Release mode: {IsReleaseMode}"); + var versionOptions = await value.GetAllVersionOptions(); - InstallName = SelectedPackage.DisplayName; - LatestVersionText = IsReleaseMode - ? $"Latest version: {SelectedVersion.TagName}" - : $"Branch: {SelectedVersion.TagName}"; - }).SafeFireAndForget(); + AvailableVersions = IsReleaseMode + ? new ObservableCollection(versionOptions.AvailableVersions) + : new ObservableCollection(versionOptions.AvailableBranches); + + SelectedVersion = AvailableVersions.First(x => !x.IsPrerelease); + ReleaseNotes = SelectedVersion.ReleaseNotesMarkdown; + + Logger.Debug($"Loaded release notes for {ReleaseNotes}"); + + if (!IsReleaseMode) + { + var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList(); + if (commits is null || commits.Count == 0) + return; + + AvailableCommits = new ObservableCollection(commits); + SelectedCommit = AvailableCommits[0]; + UpdateSelectedVersionToLatestMain(); + } + + InstallName = SelectedPackage.DisplayName; + LatestVersionText = IsReleaseMode + ? $"Latest version: {SelectedVersion.TagName}" + : $"Branch: {SelectedVersion.TagName}"; + }) + .SafeFireAndForget(); } - + partial void OnInstallNameChanged(string? value) { - ShowDuplicateWarning = - settingsManager.Settings.InstalledPackages.Any(p => - p.LibraryPath == $"Packages{Path.DirectorySeparatorChar}{value}"); + ShowDuplicateWarning = settingsManager.Settings.InstalledPackages.Any( + p => p.LibraryPath == $"Packages{Path.DirectorySeparatorChar}{value}" + ); } - + partial void OnSelectedVersionChanged(PackageVersion? value) { ReleaseNotes = value?.ReleaseNotesMarkdown ?? string.Empty; - if (value == null) return; - + if (value == null) + return; + SelectedCommit = null; AvailableCommits?.Clear(); - + if (!IsReleaseMode) { Task.Run(async () => - { - try { - var hashes = await SelectedPackage.GetAllCommits(value.TagName); - if (hashes is null) throw new Exception("No commits found"); - - Dispatcher.UIThread.Post(() => + try { - AvailableCommits = new ObservableCollection(hashes); - SelectedCommit = AvailableCommits[0]; - }); - } - catch (Exception e) - { - Logger.Warn($"Error getting commits: {e.Message}"); - } - }).SafeFireAndForget(); + var hashes = await SelectedPackage.GetAllCommits(value.TagName); + if (hashes is null) + throw new Exception("No commits found"); + + Dispatcher.UIThread.Post(() => + { + AvailableCommits = new ObservableCollection(hashes); + SelectedCommit = AvailableCommits[0]; + }); + } + catch (Exception e) + { + Logger.Warn($"Error getting commits: {e.Message}"); + } + }) + .SafeFireAndForget(); } } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs index 40aff597..72782cc8 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs @@ -26,14 +26,27 @@ public partial class OneClickInstallViewModel : ViewModelBase private readonly IPyRunner pyRunner; private readonly ISharedFolders sharedFolders; private const string DefaultPackageName = "stable-diffusion-webui"; - - [ObservableProperty] private string headerText; - [ObservableProperty] private string subHeaderText; - [ObservableProperty] private string subSubHeaderText = string.Empty; - [ObservableProperty] private bool showInstallButton; - [ObservableProperty] private bool isIndeterminate; - [ObservableProperty] private ObservableCollection allPackages; - [ObservableProperty] private BasePackage selectedPackage; + + [ObservableProperty] + private string headerText; + + [ObservableProperty] + private string subHeaderText; + + [ObservableProperty] + private string subSubHeaderText = string.Empty; + + [ObservableProperty] + private bool showInstallButton; + + [ObservableProperty] + private bool isIndeterminate; + + [ObservableProperty] + private ObservableCollection allPackages; + + [ObservableProperty] + private BasePackage selectedPackage; [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsProgressBarVisible))] @@ -41,9 +54,14 @@ public partial class OneClickInstallViewModel : ViewModelBase public bool IsProgressBarVisible => OneClickInstallProgress > 0 || IsIndeterminate; - public OneClickInstallViewModel(ISettingsManager settingsManager, IPackageFactory packageFactory, - IPrerequisiteHelper prerequisiteHelper, ILogger logger, IPyRunner pyRunner, - ISharedFolders sharedFolders) + public OneClickInstallViewModel( + ISettingsManager settingsManager, + IPackageFactory packageFactory, + IPrerequisiteHelper prerequisiteHelper, + ILogger logger, + IPyRunner pyRunner, + ISharedFolders sharedFolders + ) { this.settingsManager = settingsManager; this.packageFactory = packageFactory; @@ -55,9 +73,9 @@ public partial class OneClickInstallViewModel : ViewModelBase HeaderText = "Welcome to Stability Matrix!"; SubHeaderText = "Choose your preferred interface and click Install to get started!"; ShowInstallButton = true; - AllPackages = - new ObservableCollection(this.packageFactory.GetAllAvailablePackages() - .Where(p => p.OfferInOneClickInstaller)); + AllPackages = new ObservableCollection( + this.packageFactory.GetAllAvailablePackages().Where(p => p.OfferInOneClickInstaller) + ); SelectedPackage = AllPackages[0]; } @@ -75,7 +93,7 @@ public partial class OneClickInstallViewModel : ViewModelBase EventManager.Instance.OnOneClickInstallFinished(true); return Task.CompletedTask; } - + private async Task DoInstall() { HeaderText = $"Installing {SelectedPackage.DisplayName}"; @@ -83,11 +101,11 @@ public partial class OneClickInstallViewModel : ViewModelBase var progressHandler = new Progress(progress => { SubHeaderText = $"{progress.Title} {progress.Percentage:N0}%"; - + IsIndeterminate = progress.IsIndeterminate; OneClickInstallProgress = Convert.ToInt32(progress.Percentage); }); - + await prerequisiteHelper.InstallAllIfNecessary(progressHandler); SubHeaderText = "Installing prerequisites..."; @@ -104,7 +122,7 @@ public partial class OneClickInstallViewModel : ViewModelBase IsIndeterminate = false; var libraryDir = settingsManager.LibraryDir; - + // get latest version & download & install SubHeaderText = "Getting latest version..."; var installLocation = Path.Combine(libraryDir, "Packages", SelectedPackage.Name); @@ -112,12 +130,11 @@ public partial class OneClickInstallViewModel : ViewModelBase var downloadVersion = new DownloadPackageVersionOptions(); var installedVersion = new InstalledPackageVersion(); - + var versionOptions = await SelectedPackage.GetAllVersionOptions(); if (versionOptions.AvailableVersions != null && versionOptions.AvailableVersions.Any()) { - downloadVersion.VersionTag = - versionOptions.AvailableVersions.First().TagName; + downloadVersion.VersionTag = versionOptions.AvailableVersions.First().TagName; installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag; } else @@ -125,16 +142,16 @@ public partial class OneClickInstallViewModel : ViewModelBase downloadVersion.BranchName = await SelectedPackage.GetLatestVersion(); installedVersion.InstalledBranch = downloadVersion.BranchName; } - + var torchVersion = SelectedPackage.GetRecommendedTorchVersion(); await DownloadPackage(installLocation, downloadVersion); await InstallPackage(installLocation, torchVersion); - + SubHeaderText = "Setting up shared folder links..."; var recommendedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod; await SelectedPackage.SetupModelFolders(installLocation, recommendedSharedFolderMethod); - + var installedPackage = new InstalledPackage { DisplayName = SelectedPackage.DisplayName, @@ -151,7 +168,7 @@ public partial class OneClickInstallViewModel : ViewModelBase st.Settings.InstalledPackages.Add(installedPackage); st.Settings.ActiveInstalledPackageId = installedPackage.Id; EventManager.Instance.OnInstalledPackagesChanged(); - + HeaderText = "Installation complete!"; SubSubHeaderText = string.Empty; OneClickInstallProgress = 100; @@ -161,15 +178,18 @@ public partial class OneClickInstallViewModel : ViewModelBase await Task.Delay(1000); SubHeaderText = "Proceeding to Launch page in 1 second..."; await Task.Delay(1000); - + // should close dialog EventManager.Instance.OnOneClickInstallFinished(false); } - private async Task DownloadPackage(string installLocation, DownloadPackageVersionOptions versionOptions) + private async Task DownloadPackage( + string installLocation, + DownloadPackageVersionOptions versionOptions + ) { SubHeaderText = "Downloading package..."; - + var progress = new Progress(progress => { IsIndeterminate = progress.IsIndeterminate; @@ -186,7 +206,7 @@ public partial class OneClickInstallViewModel : ViewModelBase { SelectedPackage.ConsoleOutput += (_, output) => SubSubHeaderText = output.Text; SubHeaderText = "Downloading and installing package requirements..."; - + var progress = new Progress(progress => { SubHeaderText = "Downloading and installing package requirements..."; @@ -194,7 +214,7 @@ public partial class OneClickInstallViewModel : ViewModelBase OneClickInstallProgress = Convert.ToInt32(progress.Percentage); EventManager.Instance.OnGlobalProgressChanged(OneClickInstallProgress); }); - + await SelectedPackage.InstallPackage(installLocation, torchVersion, progress); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs index 8e8e4db3..2a0fb300 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs @@ -26,45 +26,58 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; public partial class PackageImportViewModel : ContentDialogViewModelBase { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + private readonly IPackageFactory packageFactory; private readonly ISettingsManager settingsManager; - - [ObservableProperty] private DirectoryPath? packagePath; - [ObservableProperty] private BasePackage? selectedBasePackage; - - public IReadOnlyList AvailablePackages - => packageFactory.GetAllAvailablePackages().ToImmutableArray(); - - [ObservableProperty] private PackageVersion? selectedVersion; - - [ObservableProperty] private ObservableCollection? availableCommits; - [ObservableProperty] private ObservableCollection? availableVersions; - - [ObservableProperty] private GitCommit? selectedCommit; - + + [ObservableProperty] + private DirectoryPath? packagePath; + + [ObservableProperty] + private BasePackage? selectedBasePackage; + + public IReadOnlyList AvailablePackages => + packageFactory.GetAllAvailablePackages().ToImmutableArray(); + + [ObservableProperty] + private PackageVersion? selectedVersion; + + [ObservableProperty] + private ObservableCollection? availableCommits; + + [ObservableProperty] + private ObservableCollection? availableVersions; + + [ObservableProperty] + private GitCommit? selectedCommit; + // Version types (release or commit) [ObservableProperty] - [NotifyPropertyChangedFor(nameof(ReleaseLabelText), - nameof(IsReleaseMode), nameof(SelectedVersion))] + [NotifyPropertyChangedFor( + nameof(ReleaseLabelText), + nameof(IsReleaseMode), + nameof(SelectedVersion) + )] private PackageVersionType selectedVersionType = PackageVersionType.Commit; - + [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsReleaseModeAvailable))] - private PackageVersionType availableVersionTypes = + private PackageVersionType availableVersionTypes = PackageVersionType.GithubRelease | PackageVersionType.Commit; public string ReleaseLabelText => IsReleaseMode ? "Version" : "Branch"; public bool IsReleaseMode { get => SelectedVersionType == PackageVersionType.GithubRelease; - set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit; + set => + SelectedVersionType = value + ? PackageVersionType.GithubRelease + : PackageVersionType.Commit; } - public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease); - - public PackageImportViewModel( - IPackageFactory packageFactory, - ISettingsManager settingsManager) + public bool IsReleaseModeAvailable => + AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease); + + public PackageImportViewModel(IPackageFactory packageFactory, ISettingsManager settingsManager) { this.packageFactory = packageFactory; this.settingsManager = settingsManager; @@ -73,24 +86,28 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase public override async Task OnLoadedAsync() { SelectedBasePackage ??= AvailablePackages[0]; - - if (Design.IsDesignMode) return; + + if (Design.IsDesignMode) + return; // Populate available versions try { var versionOptions = await SelectedBasePackage.GetAllVersionOptions(); if (IsReleaseMode) { - AvailableVersions = - new ObservableCollection(versionOptions.AvailableVersions); - if (!AvailableVersions.Any()) return; + AvailableVersions = new ObservableCollection( + versionOptions.AvailableVersions + ); + if (!AvailableVersions.Any()) + return; SelectedVersion = AvailableVersions[0]; } else { - AvailableVersions = - new ObservableCollection(versionOptions.AvailableBranches); + AvailableVersions = new ObservableCollection( + versionOptions.AvailableBranches + ); UpdateSelectedVersionToLatestMain(); } } @@ -99,12 +116,12 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase Logger.Warn("Error getting versions: {Exception}", e.ToString()); } } - + private static string GetDisplayVersion(string version, string? branch) { return branch == null ? version : $"{branch}@{version[..7]}"; } - + // When available version types change, reset selected version type if not compatible partial void OnAvailableVersionTypesChanged(PackageVersionType value) { @@ -113,11 +130,11 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase SelectedVersionType = value; } } - + // When changing branch / release modes, refresh // ReSharper disable once UnusedParameterInPartialMethod - partial void OnSelectedVersionTypeChanged(PackageVersionType value) - => OnSelectedBasePackageChanged(SelectedBasePackage); + partial void OnSelectedVersionTypeChanged(PackageVersionType value) => + OnSelectedBasePackageChanged(SelectedBasePackage); partial void OnSelectedBasePackageChanged(BasePackage? value) { @@ -127,38 +144,42 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase AvailableCommits?.Clear(); return; } - + AvailableVersions?.Clear(); AvailableCommits?.Clear(); AvailableVersionTypes = SelectedBasePackage.AvailableVersionTypes; - - if (Design.IsDesignMode) return; - - Dispatcher.UIThread.InvokeAsync(async () => - { - Logger.Debug($"Release mode: {IsReleaseMode}"); - var versionOptions = await value.GetAllVersionOptions(); - - AvailableVersions = IsReleaseModeAvailable - ? new ObservableCollection(versionOptions.AvailableVersions) - : new ObservableCollection(versionOptions.AvailableBranches); - - Logger.Debug($"Available versions: {string.Join(", ", AvailableVersions)}"); - SelectedVersion = AvailableVersions[0]; - - if (!IsReleaseMode) + + if (Design.IsDesignMode) + return; + + Dispatcher.UIThread + .InvokeAsync(async () => { - var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList(); - if (commits is null || commits.Count == 0) return; - - AvailableCommits = new ObservableCollection(commits); - SelectedCommit = AvailableCommits[0]; - UpdateSelectedVersionToLatestMain(); - } - }).SafeFireAndForget(); + Logger.Debug($"Release mode: {IsReleaseMode}"); + var versionOptions = await value.GetAllVersionOptions(); + + AvailableVersions = IsReleaseModeAvailable + ? new ObservableCollection(versionOptions.AvailableVersions) + : new ObservableCollection(versionOptions.AvailableBranches); + + Logger.Debug($"Available versions: {string.Join(", ", AvailableVersions)}"); + SelectedVersion = AvailableVersions[0]; + + if (!IsReleaseMode) + { + var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList(); + if (commits is null || commits.Count == 0) + return; + + AvailableCommits = new ObservableCollection(commits); + SelectedCommit = AvailableCommits[0]; + UpdateSelectedVersionToLatestMain(); + } + }) + .SafeFireAndForget(); } - + private void UpdateSelectedVersionToLatestMain() { if (AvailableVersions is null) @@ -171,14 +192,14 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase var version = AvailableVersions.FirstOrDefault(x => x.TagName == "master"); // If not found, try main version ??= AvailableVersions.FirstOrDefault(x => x.TagName == "main"); - + // If still not found, just use the first one version ??= AvailableVersions[0]; - + SelectedVersion = version; } } - + public async Task AddPackageWithCurrentInputs() { if (SelectedBasePackage is null || PackagePath is null) @@ -187,20 +208,19 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase var version = new InstalledPackageVersion(); if (IsReleaseMode) { - version.InstalledReleaseVersion = SelectedVersion?.TagName ?? - throw new NullReferenceException( - "Selected version is null"); + version.InstalledReleaseVersion = + SelectedVersion?.TagName + ?? throw new NullReferenceException("Selected version is null"); } else { - version.InstalledBranch = SelectedVersion?.TagName ?? - throw new NullReferenceException( - "Selected version is null"); - version.InstalledCommitSha = SelectedCommit?.Sha ?? - throw new NullReferenceException( - "Selected commit is null"); + version.InstalledBranch = + SelectedVersion?.TagName + ?? throw new NullReferenceException("Selected version is null"); + version.InstalledCommitSha = + SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null"); } - + var torchVersion = SelectedBasePackage.GetRecommendedTorchVersion(); var sharedFolderRecommendation = SelectedBasePackage.RecommendedSharedFolderMethod; var package = new InstalledPackage @@ -215,17 +235,17 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase PreferredTorchVersion = torchVersion, PreferredSharedFolderMethod = sharedFolderRecommendation }; - + // Recreate venv if it's a BaseGitPackage if (SelectedBasePackage is BaseGitPackage gitPackage) { await gitPackage.SetupVenv(PackagePath, forceRecreate: true); } - + // Reconfigure shared links var recommendedSharedFolderMethod = SelectedBasePackage.RecommendedSharedFolderMethod; await SelectedBasePackage.UpdateModelFolders(PackagePath, recommendedSharedFolderMethod); - + settingsManager.Transaction(s => s.InstalledPackages.Add(package)); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs index 86a940cb..95e88c90 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs @@ -16,14 +16,17 @@ public class PackageModificationDialogViewModel : ContentDialogProgressViewModel private readonly INotificationService notificationService; private readonly IEnumerable steps; - public PackageModificationDialogViewModel(IPackageModificationRunner packageModificationRunner, - INotificationService notificationService, IEnumerable steps) + public PackageModificationDialogViewModel( + IPackageModificationRunner packageModificationRunner, + INotificationService notificationService, + IEnumerable steps + ) { this.packageModificationRunner = packageModificationRunner; this.notificationService = notificationService; this.steps = steps; } - + public ConsoleViewModel Console { get; } = new(); public override async Task OnLoadedAsync() @@ -34,9 +37,12 @@ public class PackageModificationDialogViewModel : ContentDialogProgressViewModel packageModificationRunner.ProgressChanged += PackageModificationRunnerOnProgressChanged; await packageModificationRunner.ExecuteSteps(steps.ToList()); - notificationService.Show("Package Install Completed", - "Package install completed successfully.", NotificationType.Success); - + notificationService.Show( + "Package Install Completed", + "Package install completed successfully.", + NotificationType.Success + ); + OnCloseButtonClick(); } } @@ -46,14 +52,14 @@ public class PackageModificationDialogViewModel : ContentDialogProgressViewModel Text = string.IsNullOrWhiteSpace(e.Title) ? packageModificationRunner.CurrentStep?.ProgressTitle : e.Title; - + Value = e.Percentage; Description = e.Message; IsIndeterminate = e.IsIndeterminate; - if (string.IsNullOrWhiteSpace(e.Message) || e.Message.Equals("Downloading...")) + if (string.IsNullOrWhiteSpace(e.Message) || e.Message.Equals("Downloading...")) return; - + Console.PostLine(e.Message); EventManager.Instance.OnScrollToBottomRequested(); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs index fcdcf647..3e28e0d2 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs @@ -24,33 +24,43 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); public static string DefaultInstallLocation => Compat.AppDataHome; - + private readonly ISettingsManager settingsManager; - + private const string ValidExistingDirectoryText = "Valid existing data directory found"; private const string InvalidDirectoryText = "Directory must be empty or have a valid settings.json file"; private const string NotEnoughFreeSpaceText = "Not enough free space on the selected drive"; - private const string FatWarningText = - "FAT32 / exFAT drives are not supported at this time"; - - [ObservableProperty] private string dataDirectory = DefaultInstallLocation; - [ObservableProperty] private bool isPortableMode; - - [ObservableProperty] private string directoryStatusText = string.Empty; - [ObservableProperty] private bool isStatusBadgeVisible; - [ObservableProperty] private bool isDirectoryValid; - [ObservableProperty] private bool showFatWarning; - - public RefreshBadgeViewModel ValidatorRefreshBadge { get; } = new() - { - State = ProgressState.Inactive, - SuccessToolTipText = ValidExistingDirectoryText, - FailToolTipText = InvalidDirectoryText - }; - + private const string FatWarningText = "FAT32 / exFAT drives are not supported at this time"; + + [ObservableProperty] + private string dataDirectory = DefaultInstallLocation; + + [ObservableProperty] + private bool isPortableMode; + + [ObservableProperty] + private string directoryStatusText = string.Empty; + + [ObservableProperty] + private bool isStatusBadgeVisible; + + [ObservableProperty] + private bool isDirectoryValid; + + [ObservableProperty] + private bool showFatWarning; + + public RefreshBadgeViewModel ValidatorRefreshBadge { get; } = + new() + { + State = ProgressState.Inactive, + SuccessToolTipText = ValidExistingDirectoryText, + FailToolTipText = InvalidDirectoryText + }; + public bool HasOldData => settingsManager.GetOldInstalledPackages().Any(); - + public SelectDataDirectoryViewModel(ISettingsManager settingsManager) { this.settingsManager = settingsManager; @@ -61,17 +71,17 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase { ValidatorRefreshBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget(); } - + // Revalidate on data directory change partial void OnDataDirectoryChanged(string value) { ValidatorRefreshBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget(); } - + private async Task ValidateDataDirectory() { await using var delay = new MinimumDelay(100, 200); - + ShowFatWarning = IsDriveFat(DataDirectory); // Doesn't exist, this is fine as a new install, hide badge @@ -83,17 +93,17 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase } // Otherwise check that a settings.json exists var settingsPath = Path.Combine(DataDirectory, "settings.json"); - + // settings.json exists: Try deserializing it if (File.Exists(settingsPath)) { try { var jsonText = await File.ReadAllTextAsync(settingsPath); - JsonSerializer.Deserialize(jsonText, new JsonSerializerOptions - { - Converters = { new JsonStringEnumConverter() } - }); + JsonSerializer.Deserialize( + jsonText, + new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } + ); // If successful, show existing badge IsStatusBadgeVisible = true; IsDirectoryValid = true; @@ -110,9 +120,9 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase return false; } } - + // No settings.json - + // Check if the directory is %APPDATA%\StabilityMatrix: hide badge and set directory valid if (DataDirectory == DefaultInstallLocation) { @@ -120,7 +130,7 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase IsDirectoryValid = true; return true; } - + // Check if the directory is empty: hide badge and set directory to valid var isEmpty = !Directory.EnumerateFileSystemEntries(DataDirectory).Any(); if (isEmpty) @@ -136,21 +146,20 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase DirectoryStatusText = InvalidDirectoryText; return false; } - + private bool CanPickFolder => App.StorageProvider.CanPickFolder; - + [RelayCommand(CanExecute = nameof(CanPickFolder))] private async Task ShowFolderBrowserDialog() { var provider = App.StorageProvider; - var result = await provider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - Title = "Select Data Folder", - AllowMultiple = false - }); - - if (result.Count != 1) return; - + var result = await provider.OpenFolderPickerAsync( + new FolderPickerOpenOptions { Title = "Select Data Folder", AllowMultiple = false } + ); + + if (result.Count != 1) + return; + DataDirectory = result[0].Path.LocalPath; } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs index 1c8a5107..77eac510 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs @@ -24,22 +24,37 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase public required string Description { get; set; } public required string Title { get; set; } - [ObservableProperty] private Bitmap? previewImage; - [ObservableProperty] private ModelVersionViewModel? selectedVersionViewModel; - [ObservableProperty] private CivitFileViewModel? selectedFile; - [ObservableProperty] private bool isImportEnabled; - [ObservableProperty] private ObservableCollection imageUrls = new(); - [ObservableProperty] private bool canGoToNextImage; - [ObservableProperty] private bool canGoToPreviousImage; - + [ObservableProperty] + private Bitmap? previewImage; + + [ObservableProperty] + private ModelVersionViewModel? selectedVersionViewModel; + + [ObservableProperty] + private CivitFileViewModel? selectedFile; + + [ObservableProperty] + private bool isImportEnabled; + + [ObservableProperty] + private ObservableCollection imageUrls = new(); + + [ObservableProperty] + private bool canGoToNextImage; + + [ObservableProperty] + private bool canGoToPreviousImage; + [ObservableProperty] [NotifyPropertyChangedFor(nameof(DisplayedPageNumber))] private int selectedImageIndex; public int DisplayedPageNumber => SelectedImageIndex + 1; - public SelectModelVersionViewModel(ISettingsManager settingsManager, - IDownloadService downloadService) + public SelectModelVersionViewModel( + ISettingsManager settingsManager, + IDownloadService downloadService + ) { this.settingsManager = settingsManager; this.downloadService = downloadService; @@ -54,12 +69,14 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase partial void OnSelectedVersionViewModelChanged(ModelVersionViewModel? value) { var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled; - var allImages = value?.ModelVersion?.Images?.Where( - img => nsfwEnabled || img.Nsfw == "None")?.Select(x => new ImageSource(x.Url)).ToList(); + var allImages = value + ?.ModelVersion?.Images?.Where(img => nsfwEnabled || img.Nsfw == "None") + ?.Select(x => new ImageSource(x.Url)) + .ToList(); if (allImages == null || !allImages.Any()) { - allImages = new List {new(Assets.NoImage)}; + allImages = new List { new(Assets.NoImage) }; CanGoToNextImage = false; } else @@ -93,14 +110,16 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase public void PreviousImage() { - if (SelectedImageIndex > 0) SelectedImageIndex--; + if (SelectedImageIndex > 0) + SelectedImageIndex--; CanGoToPreviousImage = SelectedImageIndex > 0; CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1; } public void NextImage() { - if (SelectedImageIndex < ImageUrls.Count - 1) SelectedImageIndex++; + if (SelectedImageIndex < ImageUrls.Count - 1) + SelectedImageIndex++; CanGoToPreviousImage = SelectedImageIndex > 0; CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1; } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs index 7f76be2f..4eb562a8 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs @@ -24,18 +24,29 @@ public partial class UpdateViewModel : ContentDialogViewModelBase private readonly IHttpClientFactory httpClientFactory; private readonly IUpdateHelper updateHelper; - [ObservableProperty] private bool isUpdateAvailable; - [ObservableProperty] private UpdateInfo? updateInfo; - - [ObservableProperty] private string? releaseNotes; - [ObservableProperty] private string? updateText; - [ObservableProperty] private int progressValue; - [ObservableProperty] private bool showProgressBar; - + [ObservableProperty] + private bool isUpdateAvailable; + + [ObservableProperty] + private UpdateInfo? updateInfo; + + [ObservableProperty] + private string? releaseNotes; + + [ObservableProperty] + private string? updateText; + + [ObservableProperty] + private int progressValue; + + [ObservableProperty] + private bool showProgressBar; + public UpdateViewModel( ISettingsManager settingsManager, - IHttpClientFactory httpClientFactory, - IUpdateHelper updateHelper) + IHttpClientFactory httpClientFactory, + IUpdateHelper updateHelper + ) { this.settingsManager = settingsManager; this.httpClientFactory = httpClientFactory; @@ -48,19 +59,21 @@ public partial class UpdateViewModel : ContentDialogViewModelBase }; updateHelper.StartCheckingForUpdates().SafeFireAndForget(); } - + public override async Task OnLoadedAsync() { - if (UpdateInfo is null) return; - - UpdateText = $"Stability Matrix v{UpdateInfo.Version} is now available! You currently have v{Compat.AppVersion}. Would you like to update now?"; - + if (UpdateInfo is null) + return; + + UpdateText = + $"Stability Matrix v{UpdateInfo.Version} is now available! You currently have v{Compat.AppVersion}. Would you like to update now?"; + var client = httpClientFactory.CreateClient(); var response = await client.GetAsync(UpdateInfo.ChangelogUrl); if (response.IsSuccessStatusCode) { ReleaseNotes = await response.Content.ReadAsStringAsync(); - + // Formatting for new changelog format // https://keepachangelog.com/en/1.1.0/ if (UpdateInfo.ChangelogUrl.EndsWith(".md", StringComparison.OrdinalIgnoreCase)) @@ -87,20 +100,23 @@ public partial class UpdateViewModel : ContentDialogViewModelBase { return; } - + ShowProgressBar = true; UpdateText = $"Downloading update v{UpdateInfo.Version}..."; - await updateHelper.DownloadUpdate(UpdateInfo, new Progress(report => - { - ProgressValue = Convert.ToInt32(report.Percentage); - })); - + await updateHelper.DownloadUpdate( + UpdateInfo, + new Progress(report => + { + ProgressValue = Convert.ToInt32(report.Percentage); + }) + ); + // On unix, we need to set the executable bit if (Compat.IsUnix) { - File.SetUnixFileMode(UpdateHelper.ExecutablePath, (UnixFileMode) 0x755); + File.SetUnixFileMode(UpdateHelper.ExecutablePath, (UnixFileMode)0x755); } - + UpdateText = "Update complete. Restarting Stability Matrix in 3 seconds..."; await Task.Delay(1000); UpdateText = "Update complete. Restarting Stability Matrix in 2 seconds..."; diff --git a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs index 1ecbca3f..3d3f99f1 100644 --- a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs @@ -48,52 +48,68 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn private readonly ISharedFolders sharedFolders; private readonly ServiceManager dialogFactory; protected readonly IPackageFactory PackageFactory; - + // Regex to match if input contains a yes/no prompt, // i.e "Y/n", "yes/no". Case insensitive. // Separated by / or |. [GeneratedRegex(@"y(/|\|)n|yes(/|\|)no", RegexOptions.IgnoreCase)] private static partial Regex InputYesNoRegex(); - + public override string Title => "Launch"; - public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Rocket, IsFilled = true}; + public override IconSource IconSource => + new SymbolIconSource { Symbol = Symbol.Rocket, IsFilled = true }; public ConsoleViewModel Console { get; } = new(); - - [ObservableProperty] private bool launchButtonVisibility; - [ObservableProperty] private bool stopButtonVisibility; - [ObservableProperty] private bool isLaunchTeachingTipsOpen; - [ObservableProperty] private bool showWebUiButton; - - [ObservableProperty, NotifyPropertyChangedFor(nameof(SelectedBasePackage), - nameof(SelectedPackageExtraCommands))] + + [ObservableProperty] + private bool launchButtonVisibility; + + [ObservableProperty] + private bool stopButtonVisibility; + + [ObservableProperty] + private bool isLaunchTeachingTipsOpen; + + [ObservableProperty] + private bool showWebUiButton; + + [ + ObservableProperty, + NotifyPropertyChangedFor(nameof(SelectedBasePackage), nameof(SelectedPackageExtraCommands)) + ] private InstalledPackage? selectedPackage; - - [ObservableProperty] private ObservableCollection installedPackages = new(); - [ObservableProperty] private BasePackage? runningPackage; + [ObservableProperty] + private ObservableCollection installedPackages = new(); + + [ObservableProperty] + private BasePackage? runningPackage; public virtual BasePackage? SelectedBasePackage => PackageFactory.FindPackageByName(SelectedPackage?.PackageName); - + public IEnumerable SelectedPackageExtraCommands => SelectedBasePackage?.ExtraLaunchCommands ?? Enumerable.Empty(); - + // private bool clearingPackages; private string webUiUrl = string.Empty; - + // Input info-bars - [ObservableProperty] private bool showManualInputPrompt; - [ObservableProperty] private bool showConfirmInputPrompt; - + [ObservableProperty] + private bool showManualInputPrompt; + + [ObservableProperty] + private bool showConfirmInputPrompt; + public LaunchPageViewModel( - ILogger logger, - ISettingsManager settingsManager, + ILogger logger, + ISettingsManager settingsManager, IPackageFactory packageFactory, - IPyRunner pyRunner, - INotificationService notificationService, + IPyRunner pyRunner, + INotificationService notificationService, ISharedFolders sharedFolders, - ServiceManager dialogFactory) + ServiceManager dialogFactory + ) { this.logger = logger; this.settingsManager = settingsManager; @@ -102,11 +118,13 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn this.notificationService = notificationService; this.sharedFolders = sharedFolders; this.dialogFactory = dialogFactory; - - settingsManager.RelayPropertyFor(this, + + settingsManager.RelayPropertyFor( + this, vm => vm.SelectedPackage, - settings => settings.ActiveInstalledPackage); - + settings => settings.ActiveInstalledPackage + ); + EventManager.Instance.PackageLaunchRequested += OnPackageLaunchRequested; EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished; EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged; @@ -130,9 +148,11 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn { if (RunningPackage is not null) { - notificationService.Show("A package is already running", + notificationService.Show( + "A package is already running", "Please stop the current package before launching another.", - NotificationType.Error); + NotificationType.Error + ); return; } @@ -143,15 +163,19 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn public override void OnLoaded() { // Ensure active package either exists or is null - settingsManager.Transaction(s => - { - s.UpdateActiveInstalledPackage(); - }, ignoreMissingLibraryDir: true); - + settingsManager.Transaction( + s => + { + s.UpdateActiveInstalledPackage(); + }, + ignoreMissingLibraryDir: true + ); + // Load installed packages - InstalledPackages = - new ObservableCollection(settingsManager.Settings.InstalledPackages); - + InstalledPackages = new ObservableCollection( + settingsManager.Settings.InstalledPackages + ); + // Load active package SelectedPackage = settingsManager.Settings.ActiveInstalledPackage; } @@ -165,15 +189,18 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn protected virtual async Task LaunchImpl(string? command) { IsLaunchTeachingTipsOpen = false; - + var activeInstall = SelectedPackage; if (activeInstall == null) { // No selected package: error notification - notificationService.Show(new Notification( - message: "You must install and select a package before launching", - title: "No package selected", - type: NotificationType.Error)); + notificationService.Show( + new Notification( + message: "You must install and select a package before launching", + title: "No package selected", + type: NotificationType.Error + ) + ); return; } @@ -186,11 +213,16 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn { logger.LogWarning( "During launch, package name '{PackageName}' did not match a definition", - activeInstallName); - - notificationService.Show(new Notification("Package name invalid", - "Install package name did not match a definition. Please reinstall and let us know about this issue.", - NotificationType.Error)); + activeInstallName + ); + + notificationService.Show( + new Notification( + "Package name invalid", + "Install package name did not match a definition. Please reinstall and let us know about this issue.", + NotificationType.Error + ) + ); return; } @@ -205,13 +237,13 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn .FromDefinitions(definitions, Array.Empty()) .ToImmutableArray(); - var args = cards - .SelectMany(c => c.Options) - .ToList(); - - logger.LogDebug("Setting initial launch args: {Args}", - string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr()))); - + var args = cards.SelectMany(c => c.Options).ToList(); + + logger.LogDebug( + "Setting initial launch args: {Args}", + string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr())) + ); + settingsManager.SaveLaunchArgs(activeInstall.Id, args); } @@ -219,22 +251,24 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn // Get path from package var packagePath = new DirectoryPath(settingsManager.LibraryDir, activeInstall.LibraryPath!); - + // Unpack sitecustomize.py to venv await UnpackSiteCustomize(packagePath.JoinDir("venv")); basePackage.ConsoleOutput += OnProcessOutputReceived; basePackage.Exited += OnProcessExited; basePackage.StartupComplete += RunningPackageOnStartupComplete; - + // Clear console and start update processing await Console.StopUpdatesAsync(); await Console.Clear(); Console.StartUpdates(); // Update shared folder links (in case library paths changed) - await basePackage.UpdateModelFolders(packagePath, - activeInstall.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod); + await basePackage.UpdateModelFolders( + packagePath, + activeInstall.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod + ); // Load user launch args from settings and convert to string var userArgs = settingsManager.GetLaunchArgs(activeInstall.Id); @@ -242,14 +276,16 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn // Join with extras, if any userArgsString = string.Join(" ", userArgsString, basePackage.ExtraLaunchArguments); - + // Use input command if provided, otherwise use package launch command command ??= basePackage.LaunchCommand; - + await basePackage.RunPackage(packagePath, command, userArgsString); RunningPackage = basePackage; - - EventManager.Instance.OnRunningPackageStatusChanged(new PackagePair(activeInstall, basePackage)); + + EventManager.Instance.OnRunningPackageStatusChanged( + new PackagePair(activeInstall, basePackage) + ); } // Unpacks sitecustomize.py to the target venv @@ -293,12 +329,12 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn // Open a config page var userLaunchArgs = settingsManager.GetLaunchArgs(activeInstall.Id); var viewModel = dialogFactory.Get(); - viewModel.Cards = LaunchOptionCard.FromDefinitions(definitions, userLaunchArgs) + viewModel.Cards = LaunchOptionCard + .FromDefinitions(definitions, userLaunchArgs) .ToImmutableArray(); - - logger.LogDebug("Launching config dialog with cards: {CardsCount}", - viewModel.Cards.Count); - + + logger.LogDebug("Launching config dialog with cards: {CardsCount}", viewModel.Cards.Count); + var dialog = new BetterContentDialog { ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled, @@ -309,12 +345,9 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn DefaultButton = ContentDialogButton.Primary, ContentMargin = new Thickness(32, 16), Padding = new Thickness(0, 16), - Content = new LaunchOptionsDialog - { - DataContext = viewModel, - } + Content = new LaunchOptionsDialog { DataContext = viewModel, } }; - + var result = await dialog.ShowAsync(); if (result == ContentDialogResult.Primary) @@ -324,7 +357,7 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn settingsManager.SaveLaunchArgs(activeInstall.Id, args); } } - + // Send user input to running package public async Task SendInput(string input) { @@ -362,7 +395,7 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn ShowConfirmInputPrompt = false; } - + [RelayCommand] private async Task SendManualInput(string input) { @@ -370,71 +403,85 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn Console.PostLine(input); await SendInput(input); } - + public virtual async Task Stop() { - if (RunningPackage is null) return; + if (RunningPackage is null) + return; await RunningPackage.WaitForShutdown(); - + RunningPackage = null; ShowWebUiButton = false; - + Console.PostLine($"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}"); } public void OpenWebUi() { - if (string.IsNullOrEmpty(webUiUrl)) return; - - notificationService.TryAsync(Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)), - "Failed to open URL", $"{webUiUrl}"); + if (string.IsNullOrEmpty(webUiUrl)) + return; + + notificationService.TryAsync( + Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)), + "Failed to open URL", + $"{webUiUrl}" + ); } - + private void OnProcessExited(object? sender, int exitCode) { EventManager.Instance.OnRunningPackageStatusChanged(null); - Dispatcher.UIThread.InvokeAsync(async () => - { - logger.LogTrace("Process exited ({Code}) at {Time:g}", - exitCode, DateTimeOffset.Now); - - // Need to wait for streams to finish before detaching handlers - if (sender is BaseGitPackage {VenvRunner: not null} package) + Dispatcher.UIThread + .InvokeAsync(async () => { - var process = package.VenvRunner.Process; - if (process is not null) + logger.LogTrace( + "Process exited ({Code}) at {Time:g}", + exitCode, + DateTimeOffset.Now + ); + + // Need to wait for streams to finish before detaching handlers + if (sender is BaseGitPackage { VenvRunner: not null } package) { - // Max 5 seconds - var ct = new CancellationTokenSource(5000).Token; - try + var process = package.VenvRunner.Process; + if (process is not null) { - await process.WaitUntilOutputEOF(ct); - } - catch (OperationCanceledException e) - { - logger.LogWarning("Waiting for process EOF timed out: {Message}", e.Message); + // Max 5 seconds + var ct = new CancellationTokenSource(5000).Token; + try + { + await process.WaitUntilOutputEOF(ct); + } + catch (OperationCanceledException e) + { + logger.LogWarning( + "Waiting for process EOF timed out: {Message}", + e.Message + ); + } } } - } - - // Detach handlers - if (sender is BasePackage basePackage) - { - basePackage.ConsoleOutput -= OnProcessOutputReceived; - basePackage.Exited -= OnProcessExited; - basePackage.StartupComplete -= RunningPackageOnStartupComplete; - } - RunningPackage = null; - ShowWebUiButton = false; - - await Console.StopUpdatesAsync(); - - // Need to reset cursor in case its in some weird position - // from progress bars - await Console.ResetWriteCursor(); - Console.PostLine($"{Environment.NewLine}Process finished with exit code {exitCode}"); - - }).SafeFireAndForget(); + + // Detach handlers + if (sender is BasePackage basePackage) + { + basePackage.ConsoleOutput -= OnProcessOutputReceived; + basePackage.Exited -= OnProcessExited; + basePackage.StartupComplete -= RunningPackageOnStartupComplete; + } + RunningPackage = null; + ShowWebUiButton = false; + + await Console.StopUpdatesAsync(); + + // Need to reset cursor in case its in some weird position + // from progress bars + await Console.ResetWriteCursor(); + Console.PostLine( + $"{Environment.NewLine}Process finished with exit code {exitCode}" + ); + }) + .SafeFireAndForget(); } // Callback for processes @@ -448,7 +495,7 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn { OnLoaded(); } - + private void RunningPackageOnStartupComplete(object? sender, string e) { webUiUrl = e.Replace("0.0.0.0", "127.0.0.1"); @@ -463,39 +510,45 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn if (e.CloseReason is WindowCloseReason.WindowClosing) { e.Cancel = true; - + var dialog = CreateExitConfirmDialog(); - Dispatcher.UIThread.InvokeAsync(async () => - { - if ((TaskDialogStandardResult) - await dialog.ShowAsync(true) == TaskDialogStandardResult.Yes) + Dispatcher.UIThread + .InvokeAsync(async () => { - App.Services.GetRequiredService().Hide(); - App.Shutdown(); - } - }).SafeFireAndForget(); + if ( + (TaskDialogStandardResult)await dialog.ShowAsync(true) + == TaskDialogStandardResult.Yes + ) + { + App.Services.GetRequiredService().Hide(); + App.Shutdown(); + } + }) + .SafeFireAndForget(); } } } private static TaskDialog CreateExitConfirmDialog() { - var dialog = DialogHelper.CreateTaskDialog("Confirm Exit", - "Are you sure you want to exit? This will also close the currently running package."); + var dialog = DialogHelper.CreateTaskDialog( + "Confirm Exit", + "Are you sure you want to exit? This will also close the currently running package." + ); dialog.ShowProgressBar = false; dialog.FooterVisibility = TaskDialogFooterVisibility.Never; - + dialog.Buttons = new List { new("Exit", TaskDialogStandardResult.Yes), TaskDialogButton.CancelButton }; dialog.Buttons[0].IsDefault = true; - + return dialog; } - + public void Dispose() { RunningPackage?.Shutdown(); @@ -514,7 +567,7 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn RunningPackage = null; } await Console.DisposeAsync(); - + GC.SuppressFinalize(this); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index 257ef71f..573ea01c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -34,19 +34,29 @@ public partial class PackageCardViewModel : ProgressViewModel private readonly INavigationService navigationService; private readonly ServiceManager vmFactory; - [ObservableProperty] private InstalledPackage? package; - [ObservableProperty] private string? cardImageSource; - [ObservableProperty] private bool isUpdateAvailable; - [ObservableProperty] private string? installedVersion; - [ObservableProperty] private bool isUnknownPackage; - + [ObservableProperty] + private InstalledPackage? package; + + [ObservableProperty] + private string? cardImageSource; + + [ObservableProperty] + private bool isUpdateAvailable; + + [ObservableProperty] + private string? installedVersion; + + [ObservableProperty] + private bool isUnknownPackage; + public PackageCardViewModel( ILogger logger, IPackageFactory packageFactory, - INotificationService notificationService, - ISettingsManager settingsManager, + INotificationService notificationService, + ISettingsManager settingsManager, INavigationService navigationService, - ServiceManager vmFactory) + ServiceManager vmFactory + ) { this.logger = logger; this.packageFactory = packageFactory; @@ -70,10 +80,9 @@ public partial class PackageCardViewModel : ProgressViewModel else { IsUnknownPackage = false; - + var basePackage = packageFactory[value.PackageName]; - CardImageSource = basePackage?.PreviewImageUri.ToString() - ?? Assets.NoImage.ToString(); + CardImageSource = basePackage?.PreviewImageUri.ToString() ?? Assets.NoImage.ToString(); InstalledVersion = value.Version?.DisplayVersion ?? "Unknown"; } } @@ -87,13 +96,13 @@ public partial class PackageCardViewModel : ProgressViewModel { if (Package == null) return; - + settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id); - + navigationService.NavigateTo(new BetterDrillInNavigationTransition()); EventManager.Instance.OnPackageLaunchRequested(Package.Id); } - + public async Task Uninstall() { if (Package?.LibraryPath == null) @@ -104,7 +113,8 @@ public partial class PackageCardViewModel : ProgressViewModel var dialog = new ContentDialog { Title = "Are you sure?", - Content = "This will delete all folders in the package directory, including any generated images in that directory as well as any files you may have added.", + Content = + "This will delete all folders in the package directory, including any generated images in that directory as well as any files you may have added.", PrimaryButtonText = "Yes, delete it", CloseButtonText = "No, keep it", DefaultButton = ContentDialogButton.Primary @@ -119,14 +129,20 @@ public partial class PackageCardViewModel : ProgressViewModel var packagePath = new DirectoryPath(settingsManager.LibraryDir, Package.LibraryPath); var deleteTask = packagePath.DeleteVerboseAsync(logger); - - var taskResult = await notificationService.TryAsync(deleteTask, - "Some files could not be deleted. Please close any open files in the package directory and try again."); + + var taskResult = await notificationService.TryAsync( + deleteTask, + "Some files could not be deleted. Please close any open files in the package directory and try again." + ); if (taskResult.IsSuccessful) { - notificationService.Show(new Notification("Success", - $"Package {Package.DisplayName} uninstalled", - NotificationType.Success)); + notificationService.Show( + new Notification( + "Success", + $"Package {Package.DisplayName} uninstalled", + NotificationType.Success + ) + ); if (!IsUnknownPackage) { @@ -135,62 +151,74 @@ public partial class PackageCardViewModel : ProgressViewModel settings.RemoveInstalledPackageAndUpdateActive(Package); }); } - + EventManager.Instance.OnInstalledPackagesChanged(); } } } - + public async Task Update() { - if (Package is null || IsUnknownPackage) return; - + if (Package is null || IsUnknownPackage) + return; + var basePackage = packageFactory[Package.PackageName!]; if (basePackage == null) { - logger.LogWarning("Could not find package {SelectedPackagePackageName}", - Package.PackageName); - notificationService.Show("Invalid Package type", + logger.LogWarning( + "Could not find package {SelectedPackagePackageName}", + Package.PackageName + ); + notificationService.Show( + "Invalid Package type", $"Package {Package.PackageName.ToRepr()} is not a valid package type", - NotificationType.Error); + NotificationType.Error + ); return; } var packageName = Package.DisplayName ?? Package.PackageName ?? ""; - + Text = $"Updating {packageName}"; IsIndeterminate = true; - + var progressId = Guid.NewGuid(); - EventManager.Instance.OnProgressChanged(new ProgressItem(progressId, - Package.DisplayName ?? Package.PackageName!, - new ProgressReport(0f, isIndeterminate: true, type: ProgressType.Update))); + EventManager.Instance.OnProgressChanged( + new ProgressItem( + progressId, + Package.DisplayName ?? Package.PackageName!, + new ProgressReport(0f, isIndeterminate: true, type: ProgressType.Update) + ) + ); try { var progress = new Progress(progress => { var percent = Convert.ToInt32(progress.Percentage); - + Value = percent; IsIndeterminate = progress.IsIndeterminate; Text = $"Updating {Package.DisplayName}"; - + EventManager.Instance.OnGlobalProgressChanged(percent); - EventManager.Instance.OnProgressChanged(new ProgressItem(progressId, - packageName, progress)); + EventManager.Instance.OnProgressChanged( + new ProgressItem(progressId, packageName, progress) + ); }); - var torchVersion = Package.PreferredTorchVersion ?? - basePackage.GetRecommendedTorchVersion(); - + var torchVersion = + Package.PreferredTorchVersion ?? basePackage.GetRecommendedTorchVersion(); + var updateResult = await basePackage.Update(Package, torchVersion, progress); - + settingsManager.UpdatePackageVersionNumber(Package.Id, updateResult); - notificationService.Show("Update complete", + notificationService.Show( + "Update complete", $"{Package.DisplayName} has been updated to the latest version.", - NotificationType.Success); - + NotificationType.Success + ); + await using (settingsManager.BeginTransaction()) { Package.UpdateAvailable = false; @@ -198,17 +226,30 @@ public partial class PackageCardViewModel : ProgressViewModel IsUpdateAvailable = false; InstalledVersion = updateResult.DisplayVersion ?? "Unknown"; - EventManager.Instance.OnProgressChanged(new ProgressItem(progressId, - packageName, - new ProgressReport(1f, "Update complete", type: ProgressType.Update))); + EventManager.Instance.OnProgressChanged( + new ProgressItem( + progressId, + packageName, + new ProgressReport(1f, "Update complete", type: ProgressType.Update) + ) + ); } catch (Exception e) { logger.LogError(e, "Error Updating Package ({PackageName})", basePackage.Name); - notificationService.ShowPersistent($"Error Updating {Package.DisplayName}", e.Message, NotificationType.Error); - EventManager.Instance.OnProgressChanged(new ProgressItem(progressId, - packageName, - new ProgressReport(0f, "Update failed", type: ProgressType.Update), Failed: true)); + notificationService.ShowPersistent( + $"Error Updating {Package.DisplayName}", + e.Message, + NotificationType.Error + ); + EventManager.Instance.OnProgressChanged( + new ProgressItem( + progressId, + packageName, + new ProgressReport(0f, "Update failed", type: ProgressType.Update), + Failed: true + ) + ); } finally { @@ -220,27 +261,23 @@ public partial class PackageCardViewModel : ProgressViewModel public async Task Import() { - if (!IsUnknownPackage || Design.IsDesignMode) return; + if (!IsUnknownPackage || Design.IsDesignMode) + return; var viewModel = vmFactory.Get(vm => { - vm.PackagePath = - new DirectoryPath(Package?.FullPath ?? throw new InvalidOperationException()); + vm.PackagePath = new DirectoryPath( + Package?.FullPath ?? throw new InvalidOperationException() + ); }); var dialog = new TaskDialog { - Content = new PackageImportDialog - { - DataContext = viewModel - }, + Content = new PackageImportDialog { DataContext = viewModel }, ShowProgressBar = false, Buttons = new List { - new(Resources.Action_Import, TaskDialogStandardResult.Yes) - { - IsDefault = true - }, + new(Resources.Action_Import, TaskDialogStandardResult.Yes) { IsDefault = true }, new(Resources.Action_Cancel, TaskDialogStandardResult.Cancel) } }; @@ -257,7 +294,9 @@ public partial class PackageCardViewModel : ProgressViewModel await using (new MinimumDelay(200, 300)) { - var result = await notificationService.TryAsync(viewModel.AddPackageWithCurrentInputs()); + var result = await notificationService.TryAsync( + viewModel.AddPackageWithCurrentInputs() + ); if (result.IsSuccessful) { EventManager.Instance.OnInstalledPackagesChanged(); @@ -269,18 +308,18 @@ public partial class PackageCardViewModel : ProgressViewModel }; dialog.XamlRoot = App.VisualRoot; - + await dialog.ShowAsync(true); } - + public async Task OpenFolder() { if (string.IsNullOrWhiteSpace(Package?.FullPath)) return; - + await ProcessRunner.OpenFolderBrowser(Package.FullPath); } - + private async Task HasUpdate() { if (Package == null || IsUnknownPackage || Design.IsDesignMode) @@ -290,8 +329,9 @@ public partial class PackageCardViewModel : ProgressViewModel if (basePackage == null) return false; - var canCheckUpdate = Package.LastUpdateCheck == null || - Package.LastUpdateCheck < DateTime.Now.AddMinutes(-15); + var canCheckUpdate = + Package.LastUpdateCheck == null + || Package.LastUpdateCheck < DateTime.Now.AddMinutes(-15); if (!canCheckUpdate) { diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs index 82c4db59..b9deb2ae 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs @@ -84,11 +84,14 @@ public partial class PackageManagerViewModel : PageViewModelBase .Or(unknown) .DeferUntilLoaded() .Bind(Packages) - .Transform(p => dialogFactory.Get(vm => - { - vm.Package = p; - vm.OnLoadedAsync().SafeFireAndForget(); - })) + .Transform( + p => + dialogFactory.Get(vm => + { + vm.Package = p; + vm.OnLoadedAsync().SafeFireAndForget(); + }) + ) .Bind(PackageCards) .Subscribe(); } @@ -107,8 +110,11 @@ public partial class PackageManagerViewModel : PageViewModelBase { if (Design.IsDesignMode) return; - - installedPackages.EditDiff(settingsManager.Settings.InstalledPackages, InstalledPackage.Comparer); + + installedPackages.EditDiff( + settingsManager.Settings.InstalledPackages, + InstalledPackage.Comparer + ); var currentUnknown = await Task.Run(IndexUnknownPackages); unknownInstalledPackages.Edit(s => s.Load(currentUnknown)); @@ -135,8 +141,11 @@ public partial class PackageManagerViewModel : PageViewModelBase if (result == ContentDialogResult.Primary) { var steps = viewModel.Steps; - var packageModificationDialogViewModel = - new PackageModificationDialogViewModel(packageModificationRunner, notificationService, steps); + var packageModificationDialogViewModel = new PackageModificationDialogViewModel( + packageModificationRunner, + notificationService, + steps + ); dialog = new BetterContentDialog { @@ -146,7 +155,10 @@ public partial class PackageManagerViewModel : PageViewModelBase IsPrimaryButtonEnabled = false, IsSecondaryButtonEnabled = false, IsFooterVisible = false, - Content = new PackageModificationDialog {DataContext = packageModificationDialogViewModel} + Content = new PackageModificationDialog + { + DataContext = packageModificationDialogViewModel + } }; await dialog.ShowAsync(); @@ -156,22 +168,24 @@ public partial class PackageManagerViewModel : PageViewModelBase } private IEnumerable IndexUnknownPackages() - { + { var packageDir = new DirectoryPath(settingsManager.LibraryDir).JoinDir("Packages"); if (!packageDir.Exists) { yield break; } - + var currentPackages = settingsManager.Settings.InstalledPackages.ToImmutableArray(); - - foreach (var subDir in packageDir.Info - .EnumerateDirectories() - .Select(info => new DirectoryPath(info))) + + foreach ( + var subDir in packageDir.Info + .EnumerateDirectories() + .Select(info => new DirectoryPath(info)) + ) { var expectedLibraryPath = $"Packages{Path.DirectorySeparatorChar}{subDir.Name}"; - + // Skip if the package is already installed if (currentPackages.Any(p => p.LibraryPath == expectedLibraryPath)) { diff --git a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs index 6def5d69..c40c6bae 100644 --- a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs @@ -43,7 +43,7 @@ namespace StabilityMatrix.Avalonia.ViewModels; public partial class SettingsViewModel : PageViewModelBase { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + private readonly INotificationService notificationService; private readonly ISettingsManager settingsManager; private readonly IPrerequisiteHelper prerequisiteHelper; @@ -51,72 +51,74 @@ public partial class SettingsViewModel : PageViewModelBase private readonly ServiceManager dialogFactory; private readonly ITrackedDownloadService trackedDownloadService; private readonly IModelIndexService modelIndexService; - + public SharedState SharedState { get; } - + public override string Title => "Settings"; - public override IconSource IconSource => new SymbolIconSource {Symbol = Symbol.Settings, IsFilled = true}; - + public override IconSource IconSource => + new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true }; + // ReSharper disable once MemberCanBeMadeStatic.Global - public string AppVersion => $"Version {Compat.AppVersion}" + - (Program.IsDebugBuild ? " (Debug)" : ""); - + public string AppVersion => + $"Version {Compat.AppVersion}" + (Program.IsDebugBuild ? " (Debug)" : ""); + // Theme section - [ObservableProperty] private string? selectedTheme; - - public IReadOnlyList AvailableThemes { get; } = new[] - { - "Light", - "Dark", - "System", - }; + [ObservableProperty] + private string? selectedTheme; + + public IReadOnlyList AvailableThemes { get; } = new[] { "Light", "Dark", "System", }; - [ObservableProperty] private CultureInfo selectedLanguage; + [ObservableProperty] + private CultureInfo selectedLanguage; // ReSharper disable once MemberCanBeMadeStatic.Global public IReadOnlyList AvailableLanguages => Cultures.SupportedCultures; - public IReadOnlyList AnimationScaleOptions { get; } = new[] - { - 0f, - 0.25f, - 0.5f, - 0.75f, - 1f, - 1.25f, - 1.5f, - 1.75f, - 2f, - }; - - [ObservableProperty] private float selectedAnimationScale; - + public IReadOnlyList AnimationScaleOptions { get; } = + new[] { 0f, 0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f, }; + + [ObservableProperty] + private float selectedAnimationScale; + // Shared folder options - [ObservableProperty] private bool removeSymlinksOnShutdown; - + [ObservableProperty] + private bool removeSymlinksOnShutdown; + // Integrations section - [ObservableProperty] private bool isDiscordRichPresenceEnabled; - + [ObservableProperty] + private bool isDiscordRichPresenceEnabled; + // Debug section - [ObservableProperty] private string? debugPaths; - [ObservableProperty] private string? debugCompatInfo; - [ObservableProperty] private string? debugGpuInfo; - + [ObservableProperty] + private string? debugPaths; + + [ObservableProperty] + private string? debugCompatInfo; + + [ObservableProperty] + private string? debugGpuInfo; + // Info section private const int VersionTapCountThreshold = 7; - [ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))] private int versionTapCount; - [ObservableProperty] private bool isVersionTapTeachingTipOpen; - public string VersionFlyoutText => $"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options."; - + + [ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))] + private int versionTapCount; + + [ObservableProperty] + private bool isVersionTapTeachingTipOpen; + public string VersionFlyoutText => + $"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options."; + public SettingsViewModel( - INotificationService notificationService, + INotificationService notificationService, ISettingsManager settingsManager, IPrerequisiteHelper prerequisiteHelper, IPyRunner pyRunner, ServiceManager dialogFactory, SharedState sharedState, - ITrackedDownloadService trackedDownloadService, - IModelIndexService modelIndexService) + ITrackedDownloadService trackedDownloadService, + IModelIndexService modelIndexService + ) { this.notificationService = notificationService; this.settingsManager = settingsManager; @@ -127,29 +129,32 @@ public partial class SettingsViewModel : PageViewModelBase this.modelIndexService = modelIndexService; SharedState = sharedState; - + SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1]; SelectedLanguage = Cultures.GetSupportedCultureOrDefault(settingsManager.Settings.Language); RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown; SelectedAnimationScale = settingsManager.Settings.AnimationScale; - - settingsManager.RelayPropertyFor(this, - vm => vm.SelectedTheme, - settings => settings.Theme); - - settingsManager.RelayPropertyFor(this, + + settingsManager.RelayPropertyFor(this, vm => vm.SelectedTheme, settings => settings.Theme); + + settingsManager.RelayPropertyFor( + this, vm => vm.IsDiscordRichPresenceEnabled, - settings => settings.IsDiscordRichPresenceEnabled); - - settingsManager.RelayPropertyFor(this, + settings => settings.IsDiscordRichPresenceEnabled + ); + + settingsManager.RelayPropertyFor( + this, vm => vm.SelectedAnimationScale, - settings => settings.AnimationScale); + settings => settings.AnimationScale + ); } - + partial void OnSelectedThemeChanged(string? value) { // In case design / tests - if (Application.Current is null) return; + if (Application.Current is null) + return; // Change theme Application.Current.RequestedThemeVariant = value switch { @@ -161,16 +166,16 @@ public partial class SettingsViewModel : PageViewModelBase partial void OnSelectedLanguageChanged(CultureInfo? oldValue, CultureInfo newValue) { - if (oldValue is null || newValue.Name == Cultures.Current.Name) return; + if (oldValue is null || newValue.Name == Cultures.Current.Name) + return; // Set locale if (AvailableLanguages.Contains(newValue)) { - Logger.Info("Changing language from {Old} to {New}", - oldValue, newValue); + Logger.Info("Changing language from {Old} to {New}", oldValue, newValue); Cultures.TrySetSupportedCulture(newValue); settingsManager.Transaction(s => s.Language = newValue.Name); - + var dialog = new BetterContentDialog { Title = Resources.Label_RelaunchRequired, @@ -191,11 +196,14 @@ public partial class SettingsViewModel : PageViewModelBase } else { - Logger.Info("Requested invalid language change from {Old} to {New}", - oldValue, newValue); + Logger.Info( + "Requested invalid language change from {Old} to {New}", + oldValue, + newValue + ); } } - + partial void OnRemoveSymlinksOnShutdownChanged(bool value) { settingsManager.Transaction(s => s.RemoveFolderLinksOnShutdown = value); @@ -205,29 +213,30 @@ public partial class SettingsViewModel : PageViewModelBase { settingsManager.Transaction(s => s.InstalledModelHashes = new HashSet()); await Task.Run(() => settingsManager.IndexCheckpoints()); - notificationService.Show("Checkpoint cache reset", "The checkpoint cache has been reset.", - NotificationType.Success); + notificationService.Show( + "Checkpoint cache reset", + "The checkpoint cache has been reset.", + NotificationType.Success + ); } #region Package Environment - + [RelayCommand] private async Task OpenEnvVarsDialog() { var viewModel = dialogFactory.Get(); - + // Load current settings - var current = settingsManager.Settings.EnvironmentVariables - ?? new Dictionary(); + var current = + settingsManager.Settings.EnvironmentVariables ?? new Dictionary(); viewModel.EnvVars = new ObservableCollection( - current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value))); - + current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value)) + ); + var dialog = new BetterContentDialog { - Content = new EnvVarsDialog - { - DataContext = viewModel - }, + Content = new EnvVarsDialog { DataContext = viewModel }, PrimaryButtonText = "Save", IsPrimaryButtonEnabled = true, CloseButtonText = "Cancel", @@ -275,7 +284,7 @@ public partial class SettingsViewModel : PageViewModelBase dialog.PrimaryButtonText = "Ok"; await dialog.ShowAsync(); } - + #endregion #region System @@ -288,27 +297,29 @@ public partial class SettingsViewModel : PageViewModelBase { if (!Compat.IsWindows) { - notificationService.Show( - "Not supported", "This feature is only supported on Windows."); + notificationService.Show("Not supported", "This feature is only supported on Windows."); return; } - + await using var _ = new MinimumDelay(200, 300); - + var shortcutDir = new DirectoryPath( Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), - "Programs"); + "Programs" + ); var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk"); var appPath = Compat.AppCurrentPath; var iconPath = shortcutDir.JoinFile("Stability Matrix.ico"); await Assets.AppIcon.ExtractTo(iconPath); - - WindowsShortcuts.CreateShortcut( - shortcutLink, appPath, iconPath, "Stability Matrix"); - - notificationService.Show("Added to Start Menu", - "Stability Matrix has been added to the Start Menu.", NotificationType.Success); + + WindowsShortcuts.CreateShortcut(shortcutLink, appPath, iconPath, "Stability Matrix"); + + notificationService.Show( + "Added to Start Menu", + "Stability Matrix has been added to the Start Menu.", + NotificationType.Success + ); } /// @@ -320,54 +331,62 @@ public partial class SettingsViewModel : PageViewModelBase { if (!Compat.IsWindows) { - notificationService.Show( - "Not supported", "This feature is only supported on Windows."); + notificationService.Show("Not supported", "This feature is only supported on Windows."); return; } - + // Confirmation dialog var dialog = new BetterContentDialog { - Title = "This will create a shortcut for Stability Matrix in the Start Menu for all users", + Title = + "This will create a shortcut for Stability Matrix in the Start Menu for all users", Content = "You will be prompted for administrator privileges. Continue?", PrimaryButtonText = "Yes", CloseButtonText = "Cancel", DefaultButton = ContentDialogButton.Primary }; - + if (await dialog.ShowAsync() != ContentDialogResult.Primary) { return; } - + await using var _ = new MinimumDelay(200, 300); - + var shortcutDir = new DirectoryPath( Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), - "Programs"); + "Programs" + ); var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk"); - + var appPath = Compat.AppCurrentPath; var iconPath = shortcutDir.JoinFile("Stability Matrix.ico"); - + // We can't directly write to the targets, so extract to temporary directory first using var tempDir = new TempDirectoryPath(); - + await Assets.AppIcon.ExtractTo(tempDir.JoinFile("Stability Matrix.ico")); WindowsShortcuts.CreateShortcut( - tempDir.JoinFile("Stability Matrix.lnk"), appPath, iconPath, - "Stability Matrix"); - + tempDir.JoinFile("Stability Matrix.lnk"), + appPath, + iconPath, + "Stability Matrix" + ); + // Move to target try { var moveLinkResult = await WindowsElevated.MoveFiles( (tempDir.JoinFile("Stability Matrix.lnk"), shortcutLink), - (tempDir.JoinFile("Stability Matrix.ico"), iconPath)); + (tempDir.JoinFile("Stability Matrix.ico"), iconPath) + ); if (moveLinkResult != 0) { - notificationService.ShowPersistent("Failed to create shortcut", $"Could not copy shortcut", - NotificationType.Error); + notificationService.ShowPersistent( + "Failed to create shortcut", + $"Could not copy shortcut", + NotificationType.Error + ); } } catch (Win32Exception e) @@ -377,9 +396,12 @@ public partial class SettingsViewModel : PageViewModelBase notificationService.Show("Could not create shortcut", "", NotificationType.Warning); return; } - - notificationService.Show("Added to Start Menu", - "Stability Matrix has been added to the Start Menu for all users.", NotificationType.Success); + + notificationService.Show( + "Added to Start Menu", + "Stability Matrix has been added to the Start Menu for all users.", + NotificationType.Success + ); } public async Task PickNewDataDirectory() @@ -390,10 +412,7 @@ public partial class SettingsViewModel : PageViewModelBase IsPrimaryButtonEnabled = false, IsSecondaryButtonEnabled = false, IsFooterVisible = false, - Content = new SelectDataDirectoryDialog - { - DataContext = viewModel - } + Content = new SelectDataDirectoryDialog { DataContext = viewModel } }; var result = await dialog.ShowAsync(); @@ -409,7 +428,7 @@ public partial class SettingsViewModel : PageViewModelBase { settingsManager.SetLibraryPath(viewModel.DataDirectory); } - + // Restart var restartDialog = new BetterContentDialog { @@ -420,14 +439,14 @@ public partial class SettingsViewModel : PageViewModelBase IsSecondaryButtonEnabled = false, }; await restartDialog.ShowAsync(); - + Process.Start(Compat.AppCurrentPath); App.Shutdown(); } } #endregion - + #region Debug Section public void LoadDebugInfo() { @@ -443,12 +462,12 @@ public partial class SettingsViewModel : PageViewModelBase AppData Directory [SpecialFolder.ApplicationData] "{appData}" """; - + // 1. Check portable mode var appDir = Compat.AppCurrentDir; var expectedPortableFile = Path.Combine(appDir, "Data", ".sm-portable"); var isPortableMode = File.Exists(expectedPortableFile); - + DebugCompatInfo = $""" Platform: {Compat.Platform} AppData: {Compat.AppData} @@ -461,24 +480,27 @@ public partial class SettingsViewModel : PageViewModelBase IsLibraryDirSet = {settingsManager.IsLibraryDirSet} IsPortableMode = {settingsManager.IsPortableMode} """; - + // Get Gpu info var gpuInfo = ""; foreach (var (i, gpu) in HardwareHelper.IterGpuInfo().Enumerate()) { - gpuInfo += $"[{i+1}] {gpu}\n"; + gpuInfo += $"[{i + 1}] {gpu}\n"; } DebugGpuInfo = gpuInfo; } - + // Debug buttons [RelayCommand] private void DebugNotification() { - notificationService.Show(new Notification( - title: "Test Notification", - message: "Here is some message", - type: NotificationType.Information)); + notificationService.Show( + new Notification( + title: "Test Notification", + message: "Here is some message", + type: NotificationType.Information + ) + ); } [RelayCommand] @@ -493,8 +515,7 @@ public partial class SettingsViewModel : PageViewModelBase }; var result = await dialog.ShowAsync(); - notificationService.Show(new Notification("Content dialog closed", - $"Result: {result}")); + notificationService.Show(new Notification("Content dialog closed", $"Result: {result}")); } [RelayCommand] @@ -509,20 +530,14 @@ public partial class SettingsViewModel : PageViewModelBase { await modelIndexService.RefreshIndex(); } - + [RelayCommand] private async Task DebugTrackedDownload() { var textFields = new TextBoxField[] { - new() - { - Label = "Url", - }, - new() - { - Label = "File path" - } + new() { Label = "Url", }, + new() { Label = "File path" } }; var dialog = DialogHelper.CreateTextEntryDialog("Add download", "", textFields); @@ -542,10 +557,11 @@ public partial class SettingsViewModel : PageViewModelBase public void OnVersionClick() { // Ignore if already enabled - if (SharedState.IsDebugMode) return; - + if (SharedState.IsDebugMode) + return; + VersionTapCount++; - + switch (VersionTapCount) { // Reached required threshold @@ -555,7 +571,9 @@ public partial class SettingsViewModel : PageViewModelBase // Enable debug options SharedState.IsDebugMode = true; notificationService.Show( - "Debug options enabled", "Warning: Improper use may corrupt application state or cause loss of data."); + "Debug options enabled", + "Warning: Improper use may corrupt application state or cause loss of data." + ); VersionTapCount = 0; break; } @@ -579,8 +597,11 @@ public partial class SettingsViewModel : PageViewModelBase } catch (Exception e) { - notificationService.Show("Failed to read licenses information", - $"{e}", NotificationType.Error); + notificationService.Show( + "Failed to read licenses information", + $"{e}", + NotificationType.Error + ); } } @@ -588,15 +609,17 @@ public partial class SettingsViewModel : PageViewModelBase { // Read licenses.json using var reader = new StreamReader(Assets.LicensesJson.Open()); - var licenses = JsonSerializer - .Deserialize>(reader.ReadToEnd()) ?? - throw new InvalidOperationException("Failed to read licenses.json"); - + var licenses = + JsonSerializer.Deserialize>(reader.ReadToEnd()) + ?? throw new InvalidOperationException("Failed to read licenses.json"); + // Generate markdown var builder = new StringBuilder(); foreach (var license in licenses) { - builder.AppendLine($"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(", ", license.Authors)}"); + builder.AppendLine( + $"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(", ", license.Authors)}" + ); builder.AppendLine(); builder.AppendLine(license.Description); builder.AppendLine(); @@ -608,5 +631,4 @@ public partial class SettingsViewModel : PageViewModelBase } #endregion - } diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs index 7c5f87f2..16a59a56 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs @@ -15,32 +15,34 @@ public partial class PackageModificationDialog : UserControlBase public PackageModificationDialog() { InitializeComponent(); - + var editor = this.FindControl("Console"); if (editor is not null) { var options = new RegistryOptions(ThemeName.DarkPlus); - + // Config hyperlinks editor.TextArea.Options.EnableHyperlinks = true; editor.TextArea.Options.RequireControlModifierForHyperlinkClick = false; editor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Coral; - + var textMate = editor.InstallTextMate(options); var scope = options.GetScopeByLanguageId("log"); - - if (scope is null) throw new InvalidOperationException("Scope is null"); - + + if (scope is null) + throw new InvalidOperationException("Scope is null"); + textMate.SetGrammar(scope); textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus)); } - + EventManager.Instance.ScrollToBottomRequested += (_, _) => { Dispatcher.UIThread.Invoke(() => { var editor = this.FindControl("Console"); - if (editor?.Document == null) return; + if (editor?.Document == null) + return; var line = Math.Max(editor.Document.LineCount - 5, 1); editor.ScrollToLine(line); }); diff --git a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs index fca9f57f..bf5b132d 100644 --- a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs @@ -14,7 +14,7 @@ namespace StabilityMatrix.Avalonia.Views; public partial class LaunchPageView : UserControlBase { private const int LineOffset = 5; - + public LaunchPageView() { InitializeComponent(); @@ -22,17 +22,18 @@ public partial class LaunchPageView : UserControlBase if (editor is not null) { var options = new RegistryOptions(ThemeName.DarkPlus); - + // Config hyperlinks editor.TextArea.Options.EnableHyperlinks = true; editor.TextArea.Options.RequireControlModifierForHyperlinkClick = false; editor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Coral; - + var textMate = editor.InstallTextMate(options); var scope = options.GetScopeByLanguageId("log"); - - if (scope is null) throw new InvalidOperationException("Scope is null"); - + + if (scope is null) + throw new InvalidOperationException("Scope is null"); + textMate.SetGrammar(scope); textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus)); } @@ -55,7 +56,8 @@ public partial class LaunchPageView : UserControlBase Dispatcher.UIThread.Invoke(() => { var editor = this.FindControl("Console"); - if (editor?.Document == null) return; + if (editor?.Document == null) + return; var line = Math.Max(editor.Document.LineCount - LineOffset, 1); editor.ScrollToLine(line); }); diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml b/StabilityMatrix.Avalonia/Views/MainWindow.axaml index 7854ca3d..8434a935 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml @@ -16,6 +16,7 @@ Width="1100" Height="750" Title="Stability Matrix" + FontFamily="San Francisco, Segoe UI, Helvetica Neue, Helvetica, Arial, sans-serif" x:Class="StabilityMatrix.Avalonia.Views.MainWindow"> diff --git a/StabilityMatrix.Core/Database/ILiteDbContext.cs b/StabilityMatrix.Core/Database/ILiteDbContext.cs index ac55191c..e58f1e95 100644 --- a/StabilityMatrix.Core/Database/ILiteDbContext.cs +++ b/StabilityMatrix.Core/Database/ILiteDbContext.cs @@ -7,13 +7,12 @@ namespace StabilityMatrix.Core.Database; public interface ILiteDbContext : IDisposable { LiteDatabaseAsync Database { get; } - + ILiteCollectionAsync CivitModels { get; } ILiteCollectionAsync CivitModelVersions { get; } ILiteCollectionAsync CivitModelQueryCache { get; } ILiteCollectionAsync LocalModelFiles { get; } - Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3); Task UpsertCivitModelAsync(CivitModel civitModel); Task UpsertCivitModelAsync(IEnumerable civitModels); diff --git a/StabilityMatrix.Core/Database/LiteDbContext.cs b/StabilityMatrix.Core/Database/LiteDbContext.cs index f568222e..c11e75e8 100644 --- a/StabilityMatrix.Core/Database/LiteDbContext.cs +++ b/StabilityMatrix.Core/Database/LiteDbContext.cs @@ -21,18 +21,24 @@ public class LiteDbContext : ILiteDbContext // Notification events public event EventHandler? CivitModelsChanged; - + // Collections (Tables) - public ILiteCollectionAsync CivitModels => Database.GetCollection("CivitModels"); - public ILiteCollectionAsync CivitModelVersions => Database.GetCollection("CivitModelVersions"); - public ILiteCollectionAsync CivitModelQueryCache => Database.GetCollection("CivitModelQueryCache"); - public ILiteCollectionAsync GithubCache => Database.GetCollection("GithubCache"); - public ILiteCollectionAsync LocalModelFiles => Database.GetCollection("LocalModelFiles"); - + public ILiteCollectionAsync CivitModels => + Database.GetCollection("CivitModels"); + public ILiteCollectionAsync CivitModelVersions => + Database.GetCollection("CivitModelVersions"); + public ILiteCollectionAsync CivitModelQueryCache => + Database.GetCollection("CivitModelQueryCache"); + public ILiteCollectionAsync GithubCache => + Database.GetCollection("GithubCache"); + public ILiteCollectionAsync LocalModelFiles => + Database.GetCollection("LocalModelFiles"); + public LiteDbContext( ILogger logger, - ISettingsManager settingsManager, - IOptions debugOptions) + ISettingsManager settingsManager, + IOptions debugOptions + ) { this.logger = logger; this.settingsManager = settingsManager; @@ -42,7 +48,7 @@ public class LiteDbContext : ILiteDbContext private LiteDatabaseAsync CreateDatabase() { LiteDatabaseAsync? db = null; - + if (debugOptions.TempDatabase) { db = new LiteDatabaseAsync(":temp:"); @@ -53,54 +59,74 @@ public class LiteDbContext : ILiteDbContext try { var dbPath = Path.Combine(settingsManager.LibraryDir, "StabilityMatrix.db"); - db = new LiteDatabaseAsync(new ConnectionString() - { - Filename = dbPath, - Connection = ConnectionType.Shared, - }); + db = new LiteDatabaseAsync( + new ConnectionString() + { + Filename = dbPath, + Connection = ConnectionType.Shared, + } + ); } catch (IOException e) { - logger.LogWarning("Database in use or not accessible ({Message}), using temporary database", e.Message); + logger.LogWarning( + "Database in use or not accessible ({Message}), using temporary database", + e.Message + ); } } - + // Fallback to temporary database db ??= new LiteDatabaseAsync(":temp:"); // Register reference fields - LiteDBExtensions.Register(m => m.ModelVersions, "CivitModelVersions"); - LiteDBExtensions.Register(e => e.Items, "CivitModels"); + LiteDBExtensions.Register( + m => m.ModelVersions, + "CivitModelVersions" + ); + LiteDBExtensions.Register( + e => e.Items, + "CivitModels" + ); return db; } - - public async Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3) + + public async Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync( + string hashBlake3 + ) { - var version = await CivitModelVersions.Query() - .Where(mv => mv.Files! - .Select(f => f.Hashes) - .Select(hashes => hashes.BLAKE3) - .Any(hash => hash == hashBlake3)) + var version = await CivitModelVersions + .Query() + .Where( + mv => + mv.Files! + .Select(f => f.Hashes) + .Select(hashes => hashes.BLAKE3) + .Any(hash => hash == hashBlake3) + ) .FirstOrDefaultAsync() .ConfigureAwait(false); - - if (version is null) return (null, null); - - var model = await CivitModels.Query() + + if (version is null) + return (null, null); + + var model = await CivitModels + .Query() .Include(m => m.ModelVersions) - .Where(m => m.ModelVersions! - .Select(v => v.Id) - .Any(id => id == version.Id)) - .FirstOrDefaultAsync().ConfigureAwait(false); - + .Where(m => m.ModelVersions!.Select(v => v.Id).Any(id => id == version.Id)) + .FirstOrDefaultAsync() + .ConfigureAwait(false); + return (model, version); } - + public async Task UpsertCivitModelAsync(CivitModel civitModel) { // Insert model versions first then model - var versionsUpdated = await CivitModelVersions.UpsertAsync(civitModel.ModelVersions).ConfigureAwait(false); + var versionsUpdated = await CivitModelVersions + .UpsertAsync(civitModel.ModelVersions) + .ConfigureAwait(false); var updated = await CivitModels.UpsertAsync(civitModel).ConfigureAwait(false); // Notify listeners on any change var anyUpdated = versionsUpdated > 0 || updated; @@ -110,7 +136,7 @@ public class LiteDbContext : ILiteDbContext } return anyUpdated; } - + public async Task UpsertCivitModelAsync(IEnumerable civitModels) { var civitModelsArray = civitModels.ToArray(); @@ -126,7 +152,7 @@ public class LiteDbContext : ILiteDbContext } return anyUpdated; } - + // Add to cache public async Task UpsertCivitModelQueryCacheEntryAsync(CivitModelQueryCacheEntry entry) { @@ -141,13 +167,14 @@ public class LiteDbContext : ILiteDbContext public async Task GetGithubCacheEntry(string? cacheKey) { - if (string.IsNullOrEmpty(cacheKey)) return null; - + if (string.IsNullOrEmpty(cacheKey)) + return null; + if (await GithubCache.FindByIdAsync(cacheKey).ConfigureAwait(false) is { } result) { return result; } - + return null; } @@ -162,9 +189,7 @@ public class LiteDbContext : ILiteDbContext { database.Dispose(); } - catch (ObjectDisposedException) - { - } + catch (ObjectDisposedException) { } database = null; } diff --git a/StabilityMatrix.Core/Helper/SharedFolders.cs b/StabilityMatrix.Core/Helper/SharedFolders.cs index a2c2b753..c25abbce 100644 --- a/StabilityMatrix.Core/Helper/SharedFolders.cs +++ b/StabilityMatrix.Core/Helper/SharedFolders.cs @@ -20,7 +20,7 @@ public class SharedFolders : ISharedFolders this.settingsManager = settingsManager; this.packageFactory = packageFactory; } - + // Platform redirect for junctions / symlinks private static void CreateLinkOrJunction(string junctionDir, string targetDir, bool overwrite) { @@ -36,8 +36,11 @@ public class SharedFolders : ISharedFolders } } - public static void SetupLinks(Dictionary> definitions, - DirectoryPath modelsDirectory, DirectoryPath installDirectory) + public static void SetupLinks( + Dictionary> definitions, + DirectoryPath modelsDirectory, + DirectoryPath installDirectory + ) { foreach (var (folderType, relativePaths) in definitions) { @@ -63,7 +66,9 @@ public class SharedFolders : ISharedFolders // Skip name collisions if (File.Exists(sourceFile)) { - Logger.Warn($"Skipping file {file.FullName} because it already exists in {sourceDir}"); + Logger.Warn( + $"Skipping file {file.FullName} because it already exists in {sourceDir}" + ); continue; } destinationFile.Info.MoveTo(sourceFile); @@ -81,19 +86,25 @@ public class SharedFolders : ISharedFolders { var modelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory); var sharedFolders = basePackage.SharedFolders; - if (sharedFolders == null) return; + if (sharedFolders == null) + return; SetupLinks(sharedFolders, modelsDirectory, installDirectory); } - + /// - /// Deletes junction links and remakes them. Unlike SetupLinksForPackage, + /// Deletes junction links and remakes them. Unlike SetupLinksForPackage, /// this will not copy files from the destination to the source. /// - public static async Task UpdateLinksForPackage(BasePackage basePackage, DirectoryPath modelsDirectory, DirectoryPath installDirectory) + public static async Task UpdateLinksForPackage( + BasePackage basePackage, + DirectoryPath modelsDirectory, + DirectoryPath installDirectory + ) { var sharedFolders = basePackage.SharedFolders; - if (sharedFolders is null) return; - + if (sharedFolders is null) + return; + foreach (var (folderType, relativePaths) in sharedFolders) { foreach (var relativePath in relativePaths) @@ -117,7 +128,8 @@ public class SharedFolders : ISharedFolders if (destinationDir.Info.LinkTarget == sourceDir) { Logger.Info( - $"Skipped updating matching folder link ({destinationDir} -> ({sourceDir})"); + $"Skipped updating matching folder link ({destinationDir} -> ({sourceDir})" + ); return; } @@ -131,8 +143,12 @@ public class SharedFolders : ISharedFolders if (destinationDir.Info.EnumerateFileSystemInfos().Any()) { Logger.Info($"Moving files from {destinationDir} to {sourceDir}"); - await FileTransfers.MoveAllFilesAndDirectories( - destinationDir, sourceDir, overwriteIfHashMatches: true) + await FileTransfers + .MoveAllFilesAndDirectories( + destinationDir, + sourceDir, + overwriteIfHashMatches: true + ) .ConfigureAwait(false); } @@ -154,15 +170,16 @@ public class SharedFolders : ISharedFolders { return; } - + foreach (var (_, relativePaths) in sharedFolders) { foreach (var relativePath in relativePaths) { var destination = Path.GetFullPath(Path.Combine(installPath, relativePath)); // Delete the destination folder if it exists - if (!Directory.Exists(destination)) continue; - + if (!Directory.Exists(destination)) + continue; + Logger.Info($"Deleting junction target {destination}"); Directory.Delete(destination, false); } @@ -174,22 +191,32 @@ public class SharedFolders : ISharedFolders var packages = settingsManager.Settings.InstalledPackages; foreach (var package in packages) { - if (package.PackageName == null) continue; + if (package.PackageName == null) + continue; var basePackage = packageFactory[package.PackageName]; - if (basePackage == null) continue; - if (package.LibraryPath == null) continue; - + if (basePackage == null) + continue; + if (package.LibraryPath == null) + continue; + try { - var sharedFolderMethod = package.PreferredSharedFolderMethod ?? - basePackage.RecommendedSharedFolderMethod; - basePackage.RemoveModelFolderLinks(package.FullPath, sharedFolderMethod) - .GetAwaiter().GetResult(); + var sharedFolderMethod = + package.PreferredSharedFolderMethod + ?? basePackage.RecommendedSharedFolderMethod; + basePackage + .RemoveModelFolderLinks(package.FullPath, sharedFolderMethod) + .GetAwaiter() + .GetResult(); } catch (Exception e) { - Logger.Warn("Failed to remove links for package {Package} " + - "({DisplayName}): {Message}", package.PackageName, package.DisplayName, e.Message); + Logger.Warn( + "Failed to remove links for package {Package} " + "({DisplayName}): {Message}", + package.PackageName, + package.DisplayName, + e.Message + ); } } } @@ -197,8 +224,9 @@ public class SharedFolders : ISharedFolders public void SetupSharedModelFolders() { var modelsDir = settingsManager.ModelsDirectory; - if (string.IsNullOrWhiteSpace(modelsDir)) return; - + if (string.IsNullOrWhiteSpace(modelsDir)) + return; + Directory.CreateDirectory(modelsDir); var allSharedFolderTypes = Enum.GetValues(); foreach (var sharedFolder in allSharedFolderTypes) diff --git a/StabilityMatrix.Core/Models/Database/GitCommit.cs b/StabilityMatrix.Core/Models/Database/GitCommit.cs index 8be8955e..3986f572 100644 --- a/StabilityMatrix.Core/Models/Database/GitCommit.cs +++ b/StabilityMatrix.Core/Models/Database/GitCommit.cs @@ -6,5 +6,6 @@ public class GitCommit { public string? Sha { get; set; } - [JsonIgnore] public string ShortSha => string.IsNullOrWhiteSpace(Sha) ? string.Empty : Sha[..7]; + [JsonIgnore] + public string ShortSha => string.IsNullOrWhiteSpace(Sha) ? string.Empty : Sha[..7]; } diff --git a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs index 1ab984cd..a58d1c6d 100644 --- a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs +++ b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs @@ -22,7 +22,7 @@ public class LocalModelFile /// Optional connected model information. /// public ConnectedModelInfo? ConnectedModelInfo { get; set; } - + /// /// Optional preview image relative path. /// @@ -32,10 +32,11 @@ public class LocalModelFile { return Path.Combine(rootModelDirectory, RelativePath); } - + public string? GetPreviewImageFullPath(string rootModelDirectory) { - return PreviewImageRelativePath == null ? null + return PreviewImageRelativePath == null + ? null : Path.Combine(rootModelDirectory, PreviewImageRelativePath); } diff --git a/StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs b/StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs index a682422d..d5ce5056 100644 --- a/StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs +++ b/StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs @@ -10,6 +10,7 @@ namespace StabilityMatrix.Core.Models.FileInterfaces; public class FilePath : FileSystemPath, IPathObject { private FileInfo? _info; + // ReSharper disable once MemberCanBePrivate.Global [JsonIgnore] public FileInfo Info => _info ??= new FileInfo(FullPath); @@ -23,17 +24,16 @@ public class FilePath : FileSystemPath, IPathObject return Info.Attributes.HasFlag(FileAttributes.ReparsePoint); } } - + [JsonIgnore] public bool Exists => Info.Exists; - + [JsonIgnore] public string Name => Info.Name; - [JsonIgnore] - public string NameWithoutExtension - => Path.GetFileNameWithoutExtension(Info.Name); - + [JsonIgnore] + public string NameWithoutExtension => Path.GetFileNameWithoutExtension(Info.Name); + /// /// Get the directory of the file. /// @@ -44,8 +44,7 @@ public class FilePath : FileSystemPath, IPathObject { try { - return Info.Directory == null ? null - : new DirectoryPath(Info.Directory); + return Info.Directory == null ? null : new DirectoryPath(Info.Directory); } catch (DirectoryNotFoundException) { @@ -54,32 +53,31 @@ public class FilePath : FileSystemPath, IPathObject } } - public FilePath(string path) : base(path) - { - } - - public FilePath(FileInfo fileInfo) : base(fileInfo.FullName) + public FilePath(string path) + : base(path) { } + + public FilePath(FileInfo fileInfo) + : base(fileInfo.FullName) { _info = fileInfo; } - - public FilePath(FileSystemPath path) : base(path) - { - } - - public FilePath(params string[] paths) : base(paths) - { - } + + public FilePath(FileSystemPath path) + : base(path) { } + + public FilePath(params string[] paths) + : base(paths) { } public long GetSize() { Info.Refresh(); return Info.Length; } - + public long GetSize(bool includeSymbolicLinks) { - if (!includeSymbolicLinks && IsSymbolicLink) return 0; + if (!includeSymbolicLinks && IsSymbolicLink) + return 0; return GetSize(); } @@ -87,51 +85,51 @@ public class FilePath : FileSystemPath, IPathObject { return Task.Run(() => GetSize(includeSymbolicLinks)); } - + /// Creates an empty file. public void Create() => File.Create(FullPath).Close(); - + /// Deletes the file public void Delete() => File.Delete(FullPath); - + // Methods specific to files - + /// Read text public string ReadAllText() => File.ReadAllText(FullPath); - + /// Read text asynchronously public Task ReadAllTextAsync(CancellationToken ct = default) { return File.ReadAllTextAsync(FullPath, ct); } - + /// Write text public void WriteAllText(string text) => File.WriteAllText(FullPath, text, Encoding.UTF8); - + /// Write text asynchronously public Task WriteAllTextAsync(string text, CancellationToken ct = default) { return File.WriteAllTextAsync(FullPath, text, Encoding.UTF8, ct); } - + /// Read bytes public byte[] ReadAllBytes() => File.ReadAllBytes(FullPath); - + /// Read bytes asynchronously public Task ReadAllBytesAsync(CancellationToken ct = default) { return File.ReadAllBytesAsync(FullPath, ct); } - + /// Write bytes public void WriteAllBytes(byte[] bytes) => File.WriteAllBytes(FullPath, bytes); - + /// Write bytes asynchronously public Task WriteAllBytesAsync(byte[] bytes, CancellationToken ct = default) { return File.WriteAllBytesAsync(FullPath, bytes, ct); } - + /// /// Move the file to a directory. /// @@ -141,7 +139,7 @@ public class FilePath : FileSystemPath, IPathObject // Return the new path return destinationFile; } - + /// /// Move the file to a directory. /// @@ -151,7 +149,7 @@ public class FilePath : FileSystemPath, IPathObject // Return the new path return directory.JoinFile(this); } - + /// /// Move the file to a target path. /// @@ -161,7 +159,7 @@ public class FilePath : FileSystemPath, IPathObject // Return the new path return destinationFile; } - + /// /// Copy the file to a target path. /// @@ -174,5 +172,6 @@ public class FilePath : FileSystemPath, IPathObject // Implicit conversions to and from string public static implicit operator string(FilePath path) => path.FullPath; + public static implicit operator FilePath(string path) => new(path); } diff --git a/StabilityMatrix.Core/Models/InstalledPackage.cs b/StabilityMatrix.Core/Models/InstalledPackage.cs index 05091b1c..8aa5324e 100644 --- a/StabilityMatrix.Core/Models/InstalledPackage.cs +++ b/StabilityMatrix.Core/Models/InstalledPackage.cs @@ -10,45 +10,48 @@ public class InstalledPackage : IJsonOnDeserialized { // Unique ID for the installation public Guid Id { get; set; } + // User defined name public string? DisplayName { get; set; } + // Package name public string? PackageName { get; set; } - + // Package version [Obsolete("Use Version instead. (Kept for migration)")] public string? PackageVersion { get; set; } - + [Obsolete("Use Version instead. (Kept for migration)")] public string? InstalledBranch { get; set; } - + [Obsolete("Use Version instead. (Kept for migration)")] public string? DisplayVersion { get; set; } - + public InstalledPackageVersion? Version { get; set; } - + // Old type absolute path [Obsolete("Use LibraryPath instead. (Kept for migration)")] public string? Path { get; set; } - + /// /// Relative path from the library root. /// public string? LibraryPath { get; set; } - + /// /// Full path to the package, using LibraryPath and GlobalConfig.LibraryDir. /// [JsonIgnore] - public string? FullPath => LibraryPath != null ? System.IO.Path.Combine(GlobalConfig.LibraryDir, LibraryPath) : null; - + public string? FullPath => + LibraryPath != null ? System.IO.Path.Combine(GlobalConfig.LibraryDir, LibraryPath) : null; + public string? LaunchCommand { get; set; } public List? LaunchArgs { get; set; } public DateTimeOffset? LastUpdateCheck { get; set; } public bool UpdateAvailable { get; set; } public TorchVersion? PreferredTorchVersion { get; set; } public SharedFolderMethod? PreferredSharedFolderMethod { get; set; } - + /// /// Get the path as a relative sub-path of the relative path. /// If not a sub-path, return null. @@ -57,14 +60,16 @@ public class InstalledPackage : IJsonOnDeserialized { var relativePath = System.IO.Path.GetRelativePath(relativeTo, path); // GetRelativePath returns the path if it's not relative - if (relativePath == path) return null; + if (relativePath == path) + return null; // Further check if the path is a sub-path of the library - var isSubPath = relativePath != "." - && relativePath != ".." - && !relativePath.StartsWith(".." + System.IO.Path.DirectorySeparatorChar) - && !System.IO.Path.IsPathRooted(relativePath); + var isSubPath = + relativePath != "." + && relativePath != ".." + && !relativePath.StartsWith(".." + System.IO.Path.DirectorySeparatorChar) + && !System.IO.Path.IsPathRooted(relativePath); return isSubPath ? relativePath : null; - } + } /// /// Migrates the old Path to the new LibraryPath. @@ -76,12 +81,13 @@ public class InstalledPackage : IJsonOnDeserialized #pragma warning disable CS0618 var oldPath = Path; #pragma warning restore CS0618 - if (oldPath == null) return false; - + if (oldPath == null) + return false; + // Check if the path is a sub-path of the library var library = libraryDirectory ?? GlobalConfig.LibraryDir; var relativePath = GetSubPath(library, oldPath); - + // If so we migrate without any IO operations if (relativePath != null) { @@ -105,8 +111,9 @@ public class InstalledPackage : IJsonOnDeserialized #pragma warning disable CS0618 var oldPath = Path; #pragma warning restore CS0618 - if (oldPath == null) return false; - + if (oldPath == null) + return false; + // Check if the path is a sub-path of the library var library = libraryDirectory ?? GlobalConfig.LibraryDir; var relativePath = GetSubPath(library, oldPath); @@ -123,7 +130,8 @@ public class InstalledPackage : IJsonOnDeserialized #pragma warning disable CS0618 var oldPath = Path; #pragma warning restore CS0618 - if (oldPath == null) return; + if (oldPath == null) + return; var libDir = libraryDirectory ?? GlobalConfig.LibraryDir; // if old package Path is same as new library, return @@ -136,27 +144,31 @@ public class InstalledPackage : IJsonOnDeserialized LibraryPath = System.IO.Path.Combine("Packages", DisplayName); return; } - + // Try using pure migration first - if (TryPureMigratePath(libraryDirectory)) return; - + if (TryPureMigratePath(libraryDirectory)) + return; + // If not, we need to move the package directory var packageFolderName = new DirectoryInfo(oldPath).Name; - + // Get the new Library/Packages path var library = libraryDirectory ?? GlobalConfig.LibraryDir; var newPackagesDir = System.IO.Path.Combine(library, "Packages"); - + // Get the new target path var newPackagePath = System.IO.Path.Combine(newPackagesDir, packageFolderName); // Ensure it is not already there, if so, add a suffix until it's not var suffix = 2; while (Directory.Exists(newPackagePath)) { - newPackagePath = System.IO.Path.Combine(newPackagesDir, $"{packageFolderName}-{suffix}"); + newPackagePath = System.IO.Path.Combine( + newPackagesDir, + $"{packageFolderName}-{suffix}" + ); suffix++; } - + // Move the package directory await Task.Run(() => Utilities.CopyDirectory(oldPath, newPackagePath, true)); @@ -169,7 +181,7 @@ public class InstalledPackage : IJsonOnDeserialized public static IEqualityComparer Comparer { get; } = new PropertyComparer(p => p.Id); - + protected bool Equals(InstalledPackage other) { return Id.Equals(other.Id); @@ -177,9 +189,11 @@ public class InstalledPackage : IJsonOnDeserialized public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == this.GetType() && Equals((InstalledPackage) obj); + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + return obj.GetType() == this.GetType() && Equals((InstalledPackage)obj); } public override int GetHashCode() @@ -191,16 +205,15 @@ public class InstalledPackage : IJsonOnDeserialized public void OnDeserialized() { // Handle version migration - if (Version != null) + if (Version != null) return; - if (string.IsNullOrWhiteSpace(InstalledBranch) && !string.IsNullOrWhiteSpace(PackageVersion)) + if ( + string.IsNullOrWhiteSpace(InstalledBranch) && !string.IsNullOrWhiteSpace(PackageVersion) + ) { // release mode - Version = new InstalledPackageVersion - { - InstalledReleaseVersion = PackageVersion - }; + Version = new InstalledPackageVersion { InstalledReleaseVersion = PackageVersion }; } else if (!string.IsNullOrWhiteSpace(PackageVersion)) { diff --git a/StabilityMatrix.Core/Models/InstalledPackageVersion.cs b/StabilityMatrix.Core/Models/InstalledPackageVersion.cs index 4f58f2bd..a3b1c511 100644 --- a/StabilityMatrix.Core/Models/InstalledPackageVersion.cs +++ b/StabilityMatrix.Core/Models/InstalledPackageVersion.cs @@ -12,9 +12,12 @@ public class InstalledPackageVersion public bool IsReleaseMode => string.IsNullOrWhiteSpace(InstalledBranch); [JsonIgnore] - public string DisplayVersion => (IsReleaseMode - ? InstalledReleaseVersion - : string.IsNullOrWhiteSpace(InstalledCommitSha) - ? InstalledBranch - : $"{InstalledBranch}@{InstalledCommitSha[..7]}") ?? string.Empty; + public string DisplayVersion => + ( + IsReleaseMode + ? InstalledReleaseVersion + : string.IsNullOrWhiteSpace(InstalledCommitSha) + ? InstalledBranch + : $"{InstalledBranch}@{InstalledCommitSha[..7]}" + ) ?? string.Empty; } diff --git a/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs b/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs index e791df5e..1b431c16 100644 --- a/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs @@ -8,8 +8,10 @@ public class AddInstalledPackageStep : IPackageStep private readonly ISettingsManager settingsManager; private readonly InstalledPackage newInstalledPackage; - public AddInstalledPackageStep(ISettingsManager settingsManager, - InstalledPackage newInstalledPackage) + public AddInstalledPackageStep( + ISettingsManager settingsManager, + InstalledPackage newInstalledPackage + ) { this.settingsManager = settingsManager; this.newInstalledPackage = newInstalledPackage; diff --git a/StabilityMatrix.Core/Models/PackageModification/DownloadPackageVersionStep.cs b/StabilityMatrix.Core/Models/PackageModification/DownloadPackageVersionStep.cs index fd047a63..f397e77f 100644 --- a/StabilityMatrix.Core/Models/PackageModification/DownloadPackageVersionStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/DownloadPackageVersionStep.cs @@ -9,8 +9,11 @@ public class DownloadPackageVersionStep : IPackageStep private readonly string installPath; private readonly DownloadPackageVersionOptions downloadOptions; - public DownloadPackageVersionStep(BasePackage package, string installPath, - DownloadPackageVersionOptions downloadOptions) + public DownloadPackageVersionStep( + BasePackage package, + string installPath, + DownloadPackageVersionOptions downloadOptions + ) { this.package = package; this.installPath = installPath; @@ -19,6 +22,6 @@ public class DownloadPackageVersionStep : IPackageStep public Task ExecuteAsync(IProgress? progress = null) => package.DownloadPackage(installPath, downloadOptions, progress); - + public string ProgressTitle => "Downloading package..."; } diff --git a/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs b/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs index 4e715b98..a39dfae2 100644 --- a/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs +++ b/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs @@ -9,4 +9,4 @@ public interface IPackageModificationRunner ProgressReport CurrentProgress { get; set; } IPackageStep? CurrentStep { get; set; } event EventHandler? ProgressChanged; -} \ No newline at end of file +} diff --git a/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs b/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs index 4b625712..58562368 100644 --- a/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs @@ -20,11 +20,7 @@ public class InstallPackageStep : IPackageStep { package.ConsoleOutput += (sender, output) => { - progress?.Report(new ProgressReport - { - IsIndeterminate = true, - Message = output.Text - }); + progress?.Report(new ProgressReport { IsIndeterminate = true, Message = output.Text }); }; await package.InstallPackage(installPath, torchVersion, progress).ConfigureAwait(false); } diff --git a/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs b/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs index a837355f..0f884fee 100644 --- a/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs +++ b/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs @@ -11,7 +11,7 @@ public class PackageModificationRunner : IPackageModificationRunner CurrentProgress = report; OnProgressChanged(report); }); - + IsRunning = true; foreach (var step in steps) { @@ -25,7 +25,8 @@ public class PackageModificationRunner : IPackageModificationRunner public bool IsRunning { get; set; } public ProgressReport CurrentProgress { get; set; } public IPackageStep? CurrentStep { get; set; } - + public event EventHandler? ProgressChanged; + protected virtual void OnProgressChanged(ProgressReport e) => ProgressChanged?.Invoke(this, e); } diff --git a/StabilityMatrix.Core/Models/PackageModification/SetupModelFoldersStep.cs b/StabilityMatrix.Core/Models/PackageModification/SetupModelFoldersStep.cs index 5bde7228..6258ed6a 100644 --- a/StabilityMatrix.Core/Models/PackageModification/SetupModelFoldersStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/SetupModelFoldersStep.cs @@ -9,8 +9,11 @@ public class SetupModelFoldersStep : IPackageStep private readonly SharedFolderMethod sharedFolderMethod; private readonly string installPath; - public SetupModelFoldersStep(BasePackage package, SharedFolderMethod sharedFolderMethod, - string installPath) + public SetupModelFoldersStep( + BasePackage package, + SharedFolderMethod sharedFolderMethod, + string installPath + ) { this.package = package; this.sharedFolderMethod = sharedFolderMethod; @@ -19,8 +22,9 @@ public class SetupModelFoldersStep : IPackageStep public async Task ExecuteAsync(IProgress? progress = null) { - progress?.Report(new ProgressReport(-1f, "Setting up shared folder links...", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1f, "Setting up shared folder links...", isIndeterminate: true) + ); await package.SetupModelFolders(installPath, sharedFolderMethod).ConfigureAwait(false); } diff --git a/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs b/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs index 58d8d242..c5b48040 100644 --- a/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs @@ -19,15 +19,16 @@ public class SetupPrerequisitesStep : IPackageStep { // git, vcredist, etc... await prerequisiteHelper.InstallAllIfNecessary(progress).ConfigureAwait(false); - + // python stuff if (!PyRunner.PipInstalled || !PyRunner.VenvInstalled) { - progress?.Report(new ProgressReport(-1f, "Installing Python prerequisites...", - isIndeterminate: true)); - + progress?.Report( + new ProgressReport(-1f, "Installing Python prerequisites...", isIndeterminate: true) + ); + await pyRunner.Initialize().ConfigureAwait(false); - + if (!PyRunner.PipInstalled) { await pyRunner.SetupPip().ConfigureAwait(false); diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index 67cc2741..7d1505a3 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -14,12 +14,12 @@ namespace StabilityMatrix.Core.Models.Packages; public class A3WebUI : BaseGitPackage { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + public override string Name => "stable-diffusion-webui"; public override string DisplayName { get; set; } = "Stable Diffusion WebUI"; public override string Author => "AUTOMATIC1111"; public override string LicenseType => "AGPL-3.0"; - public override string LicenseUrl => + public override string LicenseUrl => "https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/master/LICENSE.txt"; public override string Blurb => "A browser interface based on Gradio library for Stable Diffusion"; @@ -28,114 +28,112 @@ public class A3WebUI : BaseGitPackage new("https://github.com/AUTOMATIC1111/stable-diffusion-webui/raw/master/screenshot.png"); public string RelativeArgsDefinitionScriptPath => "modules.cmd_args"; - public override SharedFolderMethod RecommendedSharedFolderMethod => - SharedFolderMethod.Symlink; + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; - public A3WebUI(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService, - IPrerequisiteHelper prerequisiteHelper) : - base(githubApi, settingsManager, downloadService, prerequisiteHelper) - { - } + public A3WebUI( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } // From https://github.com/AUTOMATIC1111/stable-diffusion-webui/tree/master/models - public override Dictionary> SharedFolders => new() - { - [SharedFolderType.StableDiffusion] = new[] {"models/Stable-diffusion"}, - [SharedFolderType.ESRGAN] = new[] {"models/ESRGAN"}, - [SharedFolderType.RealESRGAN] = new[] {"models/RealESRGAN"}, - [SharedFolderType.SwinIR] = new[] {"models/SwinIR"}, - [SharedFolderType.Lora] = new[] {"models/Lora"}, - [SharedFolderType.LyCORIS] = new[] {"models/LyCORIS"}, - [SharedFolderType.ApproxVAE] = new[] {"models/VAE-approx"}, - [SharedFolderType.VAE] = new[] {"models/VAE"}, - [SharedFolderType.DeepDanbooru] = new[] {"models/deepbooru"}, - [SharedFolderType.Karlo] = new[] {"models/karlo"}, - [SharedFolderType.TextualInversion] = new[] {"embeddings"}, - [SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"}, - [SharedFolderType.ControlNet] = new[] {"models/ControlNet"} - }; - - [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")] - public override List LaunchOptions => new() - { - new() - { - Name = "Host", - Type = LaunchOptionType.String, - DefaultValue = "localhost", - Options = new() {"--host"} - }, + public override Dictionary> SharedFolders => new() { - Name = "Port", - Type = LaunchOptionType.String, - DefaultValue = "7860", - Options = new() {"--port"} - }, + [SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" }, + [SharedFolderType.ESRGAN] = new[] { "models/ESRGAN" }, + [SharedFolderType.RealESRGAN] = new[] { "models/RealESRGAN" }, + [SharedFolderType.SwinIR] = new[] { "models/SwinIR" }, + [SharedFolderType.Lora] = new[] { "models/Lora" }, + [SharedFolderType.LyCORIS] = new[] { "models/LyCORIS" }, + [SharedFolderType.ApproxVAE] = new[] { "models/VAE-approx" }, + [SharedFolderType.VAE] = new[] { "models/VAE" }, + [SharedFolderType.DeepDanbooru] = new[] { "models/deepbooru" }, + [SharedFolderType.Karlo] = new[] { "models/karlo" }, + [SharedFolderType.TextualInversion] = new[] { "embeddings" }, + [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, + [SharedFolderType.ControlNet] = new[] { "models/ControlNet" } + }; + + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")] + public override List LaunchOptions => new() { - Name = "VRAM", - Type = LaunchOptionType.Bool, - InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch + new() { - Level.Low => "--lowvram", - Level.Medium => "--medvram", - _ => null + Name = "Host", + Type = LaunchOptionType.String, + DefaultValue = "localhost", + Options = new() { "--host" } }, - Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" } - }, - new() - { - Name = "Xformers", - Type = LaunchOptionType.Bool, - InitialValue = HardwareHelper.HasNvidiaGpu(), - Options = new() { "--xformers" } - }, - new() - { - Name = "API", - Type = LaunchOptionType.Bool, - InitialValue = true, - Options = new() {"--api"} - }, - new() - { - Name = "Skip Torch CUDA Check", - Type = LaunchOptionType.Bool, - InitialValue = !HardwareHelper.HasNvidiaGpu(), - Options = new() {"--skip-torch-cuda-test"} - }, - new() - { - Name = "Skip Python Version Check", - Type = LaunchOptionType.Bool, - InitialValue = true, - Options = new() {"--skip-python-version-check"} - }, - new() - { - Name = "No Half", - Type = LaunchOptionType.Bool, - Description = "Do not switch the model to 16-bit floats", - InitialValue = HardwareHelper.HasAmdGpu(), - Options = new() {"--no-half"} - }, - LaunchOptionDefinition.Extras - }; - - public override IEnumerable AvailableSharedFolderMethods => new[] - { - SharedFolderMethod.Symlink, - SharedFolderMethod.None - }; + new() + { + Name = "Port", + Type = LaunchOptionType.String, + DefaultValue = "7860", + Options = new() { "--port" } + }, + new() + { + Name = "VRAM", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper + .IterGpuInfo() + .Select(gpu => gpu.MemoryLevel) + .Max() switch + { + Level.Low => "--lowvram", + Level.Medium => "--medvram", + _ => null + }, + Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" } + }, + new() + { + Name = "Xformers", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper.HasNvidiaGpu(), + Options = new() { "--xformers" } + }, + new() + { + Name = "API", + Type = LaunchOptionType.Bool, + InitialValue = true, + Options = new() { "--api" } + }, + new() + { + Name = "Skip Torch CUDA Check", + Type = LaunchOptionType.Bool, + InitialValue = !HardwareHelper.HasNvidiaGpu(), + Options = new() { "--skip-torch-cuda-test" } + }, + new() + { + Name = "Skip Python Version Check", + Type = LaunchOptionType.Bool, + InitialValue = true, + Options = new() { "--skip-python-version-check" } + }, + new() + { + Name = "No Half", + Type = LaunchOptionType.Bool, + Description = "Do not switch the model to 16-bit floats", + InitialValue = HardwareHelper.HasAmdGpu(), + Options = new() { "--no-half" } + }, + LaunchOptionDefinition.Extras + }; - public override IEnumerable AvailableTorchVersions => new[] - { - TorchVersion.Cpu, - TorchVersion.Cuda, - TorchVersion.DirectMl, - TorchVersion.Rocm - }; + public override IEnumerable AvailableSharedFolderMethods => + new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None }; + + public override IEnumerable AvailableTorchVersions => + new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Rocm }; public override async Task GetLatestVersion() { @@ -143,8 +141,11 @@ public class A3WebUI : BaseGitPackage return release.TagName!; } - public override async Task InstallPackage(string installLocation, - TorchVersion torchVersion, IProgress? progress = null) + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false); @@ -173,14 +174,17 @@ public class A3WebUI : BaseGitPackage } // Install requirements file - progress?.Report(new ProgressReport(-1f, "Installing Package Requirements", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1f, "Installing Package Requirements", isIndeterminate: true) + ); Logger.Info("Installing requirements_versions.txt"); - await venvRunner.PipInstall($"-r requirements_versions.txt", OnConsoleOutput) + await venvRunner + .PipInstall($"-r requirements_versions.txt", OnConsoleOutput) .ConfigureAwait(false); - progress?.Report(new ProgressReport(1f, "Installing Package Requirements", - isIndeterminate: false)); + progress?.Report( + new ProgressReport(1f, "Installing Package Requirements", isIndeterminate: false) + ); progress?.Report(new ProgressReport(-1f, "Updating configuration", isIndeterminate: true)); @@ -189,14 +193,18 @@ public class A3WebUI : BaseGitPackage var configPath = Path.Combine(installLocation, "config.json"); if (!File.Exists(configPath)) { - var config = new JsonObject {{"show_progress_type", "TAESD"}}; + var config = new JsonObject { { "show_progress_type", "TAESD" } }; await File.WriteAllTextAsync(configPath, config.ToString()).ConfigureAwait(false); } progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false)); } - public override async Task RunPackage(string installedPackagePath, string command, string arguments) + public override async Task RunPackage( + string installedPackagePath, + string command, + string arguments + ) { await SetupVenv(installedPackagePath).ConfigureAwait(false); @@ -204,14 +212,14 @@ public class A3WebUI : BaseGitPackage { OnConsoleOutput(s); - if (!s.Text.Contains("Running on", StringComparison.OrdinalIgnoreCase)) + if (!s.Text.Contains("Running on", StringComparison.OrdinalIgnoreCase)) return; - + var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)"); var match = regex.Match(s.Text); if (!match.Success) return; - + WebUrl = match.Value; OnStartupComplete(WebUrl); } @@ -221,16 +229,19 @@ public class A3WebUI : BaseGitPackage VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit); } - private async Task InstallRocmTorch(PyVenvRunner venvRunner, - IProgress? progress = null) + private async Task InstallRocmTorch( + PyVenvRunner venvRunner, + IProgress? progress = null + ) { - progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true) + ); - await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput) - .ConfigureAwait(false); + await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput).ConfigureAwait(false); - await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsRocm511, OnConsoleOutput) + await venvRunner + .PipInstall(PyVenvRunner.TorchPipInstallArgsRocm511, OnConsoleOutput) .ConfigureAwait(false); } } diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index 2a0afa1d..83e9cd15 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -21,13 +21,13 @@ namespace StabilityMatrix.Core.Models.Packages; public abstract class BaseGitPackage : BasePackage { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + protected readonly IGithubApiCache GithubApi; protected readonly ISettingsManager SettingsManager; protected readonly IDownloadService DownloadService; protected readonly IPrerequisiteHelper PrerequisiteHelper; public PyVenvRunner? VenvRunner; - + /// /// URL of the hosted web page on launch /// @@ -47,21 +47,23 @@ public abstract class BaseGitPackage : BasePackage if (!string.IsNullOrWhiteSpace(versionOptions.VersionTag)) { - return - $"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.VersionTag}"; + return $"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.VersionTag}"; } if (!string.IsNullOrWhiteSpace(versionOptions.BranchName)) { - return - $"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.BranchName}"; + return $"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.BranchName}"; } - + throw new Exception("No download URL available"); } - protected BaseGitPackage(IGithubApiCache githubApi, ISettingsManager settingsManager, - IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper) + protected BaseGitPackage( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) { GithubApi = githubApi; SettingsManager = settingsManager; @@ -71,51 +73,53 @@ public abstract class BaseGitPackage : BasePackage protected async Task GetLatestRelease(bool includePrerelease = false) { - var releases = await GithubApi - .GetAllReleases(Author, Name) - .ConfigureAwait(false); + var releases = await GithubApi.GetAllReleases(Author, Name).ConfigureAwait(false); return includePrerelease ? releases.First() : releases.First(x => !x.Prerelease); } - + public override Task> GetAllBranches() { return GithubApi.GetAllBranches(Author, Name); } - + public override Task> GetAllReleases() { return GithubApi.GetAllReleases(Author, Name); } - - public override Task?> GetAllCommits(string branch, int page = 1, int perPage = 10) + + public override Task?> GetAllCommits( + string branch, + int page = 1, + int perPage = 10 + ) { return GithubApi.GetAllCommits(Author, Name, branch, page, perPage); } - + public override async Task GetAllVersionOptions() { var packageVersionOptions = new PackageVersionOptions(); - + var allReleases = await GetAllReleases().ConfigureAwait(false); var releasesList = allReleases.ToList(); if (releasesList.Any()) { - packageVersionOptions.AvailableVersions = releasesList.Select(r => - new PackageVersion - { - TagName = r.TagName!, - ReleaseNotesMarkdown = r.Body, - IsPrerelease = r.Prerelease - }); + packageVersionOptions.AvailableVersions = releasesList.Select( + r => + new PackageVersion + { + TagName = r.TagName!, + ReleaseNotesMarkdown = r.Body, + IsPrerelease = r.Prerelease + } + ); } // Branch mode var allBranches = await GetAllBranches().ConfigureAwait(false); - packageVersionOptions.AvailableBranches = allBranches.Select(b => new PackageVersion - { - TagName = $"{b.Name}", - ReleaseNotesMarkdown = string.Empty - }); + packageVersionOptions.AvailableBranches = allBranches.Select( + b => new PackageVersion { TagName = $"{b.Name}", ReleaseNotesMarkdown = string.Empty } + ); return packageVersionOptions; } @@ -125,9 +129,10 @@ public abstract class BaseGitPackage : BasePackage /// [MemberNotNull(nameof(VenvRunner))] public async Task SetupVenv( - string installedPackagePath, + string installedPackagePath, string venvName = "venv", - bool forceRecreate = false) + bool forceRecreate = false + ) { var venvPath = Path.Combine(installedPackagePath, venvName); if (VenvRunner != null) @@ -140,24 +145,25 @@ public abstract class BaseGitPackage : BasePackage WorkingDirectory = installedPackagePath, EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables, }; - + if (!VenvRunner.Exists() || forceRecreate) { await VenvRunner.Setup(forceRecreate).ConfigureAwait(false); } return VenvRunner; } - + public override async Task> GetReleaseTags() { - var allReleases = await GithubApi - .GetAllReleases(Author, Name) - .ConfigureAwait(false); + var allReleases = await GithubApi.GetAllReleases(Author, Name).ConfigureAwait(false); return allReleases; } - public override async Task DownloadPackage(string installLocation, - DownloadPackageVersionOptions versionOptions, IProgress? progress = null) + public override async Task DownloadPackage( + string installLocation, + DownloadPackageVersionOptions versionOptions, + IProgress? progress = null + ) { var downloadUrl = GetDownloadUrl(versionOptions); @@ -173,8 +179,11 @@ public abstract class BaseGitPackage : BasePackage progress?.Report(new ProgressReport(100, message: "Download Complete")); } - public override async Task InstallPackage(string installLocation, - TorchVersion torchVersion, IProgress? progress = null) + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { await UnzipPackage(installLocation, progress).ConfigureAwait(false); File.Delete(DownloadLocation); @@ -186,7 +195,7 @@ public abstract class BaseGitPackage : BasePackage var zipDirName = string.Empty; var totalEntries = zip.Entries.Count; var currentEntry = 0; - + foreach (var entry in zip.Entries) { currentEntry++; @@ -196,20 +205,26 @@ public abstract class BaseGitPackage : BasePackage { zipDirName = entry.FullName; } - - var folderPath = Path.Combine(installLocation, - entry.FullName.Replace(zipDirName, string.Empty)); + + var folderPath = Path.Combine( + installLocation, + entry.FullName.Replace(zipDirName, string.Empty) + ); Directory.CreateDirectory(folderPath); continue; } - - - var destinationPath = Path.GetFullPath(Path.Combine(installLocation, - entry.FullName.Replace(zipDirName, string.Empty))); + + var destinationPath = Path.GetFullPath( + Path.Combine(installLocation, entry.FullName.Replace(zipDirName, string.Empty)) + ); entry.ExtractToFile(destinationPath, true); - - progress?.Report(new ProgressReport(current: Convert.ToUInt64(currentEntry), - total: Convert.ToUInt64(totalEntries))); + + progress?.Report( + new ProgressReport( + current: Convert.ToUInt64(currentEntry), + total: Convert.ToUInt64(totalEntries) + ) + ); } return Task.CompletedTask; @@ -232,8 +247,9 @@ public abstract class BaseGitPackage : BasePackage return UpdateAvailable; } - var allCommits = (await GetAllCommits(currentVersion.InstalledBranch) - .ConfigureAwait(false))?.ToList(); + var allCommits = ( + await GetAllCommits(currentVersion.InstalledBranch).ConfigureAwait(false) + )?.ToList(); if (allCommits == null || !allCommits.Any()) { Logger.Warn("No commits found for {Package}", package.PackageName); @@ -241,7 +257,6 @@ public abstract class BaseGitPackage : BasePackage } var latestCommitHash = allCommits.First().Sha; return latestCommitHash != currentVersion.InstalledCommitSha; - } catch (ApiException e) { @@ -250,34 +265,37 @@ public abstract class BaseGitPackage : BasePackage } } - public override async Task Update(InstalledPackage installedPackage, - TorchVersion torchVersion, IProgress? progress = null, - bool includePrerelease = false) + public override async Task Update( + InstalledPackage installedPackage, + TorchVersion torchVersion, + IProgress? progress = null, + bool includePrerelease = false + ) { - if (installedPackage.Version == null) throw new NullReferenceException("Version is null"); + if (installedPackage.Version == null) + throw new NullReferenceException("Version is null"); if (installedPackage.Version.IsReleaseMode) { var releases = await GetAllReleases().ConfigureAwait(false); var latestRelease = releases.First(x => includePrerelease || !x.Prerelease); - await DownloadPackage(installedPackage.FullPath, - new DownloadPackageVersionOptions {VersionTag = latestRelease.TagName}, - progress) + await DownloadPackage( + installedPackage.FullPath, + new DownloadPackageVersionOptions { VersionTag = latestRelease.TagName }, + progress + ) .ConfigureAwait(false); await InstallPackage(installedPackage.FullPath, torchVersion, progress) .ConfigureAwait(false); - return new InstalledPackageVersion - { - InstalledReleaseVersion = latestRelease.TagName - }; + return new InstalledPackageVersion { InstalledReleaseVersion = latestRelease.TagName }; } // Commit mode - var allCommits = await GetAllCommits( - installedPackage.Version.InstalledBranch).ConfigureAwait(false); + var allCommits = await GetAllCommits(installedPackage.Version.InstalledBranch) + .ConfigureAwait(false); var latestCommit = allCommits?.First(); if (latestCommit is null || string.IsNullOrEmpty(latestCommit.Sha)) @@ -285,8 +303,11 @@ public abstract class BaseGitPackage : BasePackage throw new Exception("No commits found for branch"); } - await DownloadPackage(installedPackage.FullPath, - new DownloadPackageVersionOptions {CommitHash = latestCommit.Sha}, progress) + await DownloadPackage( + installedPackage.FullPath, + new DownloadPackageVersionOptions { CommitHash = latestCommit.Sha }, + progress + ) .ConfigureAwait(false); await InstallPackage(installedPackage.FullPath, torchVersion, progress) .ConfigureAwait(false); @@ -298,29 +319,40 @@ public abstract class BaseGitPackage : BasePackage }; } - public override Task SetupModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task SetupModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { if (sharedFolderMethod == SharedFolderMethod.Symlink && SharedFolders is { } folders) { - StabilityMatrix.Core.Helper.SharedFolders - .SetupLinks(folders, SettingsManager.ModelsDirectory, installDirectory); + StabilityMatrix.Core.Helper.SharedFolders.SetupLinks( + folders, + SettingsManager.ModelsDirectory, + installDirectory + ); } return Task.CompletedTask; } - public override async Task UpdateModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override async Task UpdateModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { if (SharedFolders is not null && sharedFolderMethod == SharedFolderMethod.Symlink) { - await StabilityMatrix.Core.Helper.SharedFolders.UpdateLinksForPackage(this, - SettingsManager.ModelsDirectory, installDirectory).ConfigureAwait(false); + await StabilityMatrix.Core.Helper.SharedFolders + .UpdateLinksForPackage(this, SettingsManager.ModelsDirectory, installDirectory) + .ConfigureAwait(false); } } - public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) + public override Task RemoveModelFolderLinks( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { if (SharedFolders is not null && sharedFolderMethod == SharedFolderMethod.Symlink) { @@ -340,7 +372,7 @@ public abstract class BaseGitPackage : BasePackage } process.StandardInput.WriteLine(input); } - + public virtual async Task SendInputAsync(string input) { var process = VenvRunner?.Process; @@ -361,7 +393,7 @@ public abstract class BaseGitPackage : BasePackage VenvRunner = null; } } - + /// public override async Task WaitForShutdown() { diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs index ff635125..e3706a46 100644 --- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs @@ -21,52 +21,68 @@ public abstract class BasePackage public abstract string LicenseUrl { get; } public virtual string Disclaimer => string.Empty; public virtual bool OfferInOneClickInstaller => true; - + /// /// Primary command to launch the package. 'Launch' buttons uses this. /// public abstract string LaunchCommand { get; } - + /// /// Optional commands (e.g. 'config') that are on the launch button split drop-down. /// public virtual IReadOnlyList ExtraLaunchCommands { get; } = Array.Empty(); - + public abstract Uri PreviewImageUri { get; } public virtual bool ShouldIgnoreReleases => false; public virtual bool UpdateAvailable { get; set; } - public abstract Task DownloadPackage(string installLocation, DownloadPackageVersionOptions versionOptions, - IProgress? progress1); + public abstract Task DownloadPackage( + string installLocation, + DownloadPackageVersionOptions versionOptions, + IProgress? progress1 + ); + + public abstract Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ); - public abstract Task InstallPackage(string installLocation, TorchVersion torchVersion, - IProgress? progress = null); - public abstract Task RunPackage(string installedPackagePath, string command, string arguments); - + public abstract Task CheckForUpdates(InstalledPackage package); - public abstract Task Update(InstalledPackage installedPackage, - TorchVersion torchVersion, IProgress? progress = null, - bool includePrerelease = false); - - public virtual IEnumerable AvailableSharedFolderMethods => new[] - { - SharedFolderMethod.Symlink, - SharedFolderMethod.Configuration, - SharedFolderMethod.None - }; + public abstract Task Update( + InstalledPackage installedPackage, + TorchVersion torchVersion, + IProgress? progress = null, + bool includePrerelease = false + ); + + public virtual IEnumerable AvailableSharedFolderMethods => + new[] + { + SharedFolderMethod.Symlink, + SharedFolderMethod.Configuration, + SharedFolderMethod.None + }; public abstract SharedFolderMethod RecommendedSharedFolderMethod { get; } - public abstract Task SetupModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod); + public abstract Task SetupModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ); - public abstract Task UpdateModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod); + public abstract Task UpdateModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ); - public abstract Task RemoveModelFolderLinks(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod); + public abstract Task RemoveModelFolderLinks( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ); public abstract IEnumerable AvailableTorchVersions { get; } @@ -77,7 +93,7 @@ public abstract class BasePackage { return AvailableTorchVersions.First(); } - + if (HardwareHelper.HasNvidiaGpu() && AvailableTorchVersions.Contains(TorchVersion.Cuda)) { return TorchVersion.Cuda; @@ -88,15 +104,17 @@ public abstract class BasePackage return TorchVersion.Rocm; } - if (HardwareHelper.PreferDirectML() && - AvailableTorchVersions.Contains(TorchVersion.DirectMl)) + if ( + HardwareHelper.PreferDirectML() + && AvailableTorchVersions.Contains(TorchVersion.DirectMl) + ) { return TorchVersion.DirectMl; } return TorchVersion.Cpu; } - + /// /// Shuts down the subprocess, canceling any pending streams. /// @@ -110,16 +128,20 @@ public abstract class BasePackage public abstract List LaunchOptions { get; } public virtual string? ExtraLaunchArguments { get; set; } = null; - + /// /// The shared folders that this package supports. /// Mapping of to the relative paths from the package root. /// public virtual Dictionary>? SharedFolders { get; } - + public abstract Task GetLatestVersion(); public abstract Task GetAllVersionOptions(); - public abstract Task?> GetAllCommits(string branch, int page = 1, int perPage = 10); + public abstract Task?> GetAllCommits( + string branch, + int page = 1, + int perPage = 10 + ); public abstract Task> GetAllBranches(); public abstract Task> GetAllReleases(); public event EventHandler? ConsoleOutput; @@ -127,44 +149,56 @@ public abstract class BasePackage public event EventHandler? StartupComplete; public void OnConsoleOutput(ProcessOutput output) => ConsoleOutput?.Invoke(this, output); + public void OnExit(int exitCode) => Exited?.Invoke(this, exitCode); + public void OnStartupComplete(string url) => StartupComplete?.Invoke(this, url); - - public virtual PackageVersionType AvailableVersionTypes => ShouldIgnoreReleases - ? PackageVersionType.Commit - : PackageVersionType.GithubRelease | PackageVersionType.Commit; - - - - - - protected async Task InstallCudaTorch(PyVenvRunner venvRunner, - IProgress? progress = null) + + public virtual PackageVersionType AvailableVersionTypes => + ShouldIgnoreReleases + ? PackageVersionType.Commit + : PackageVersionType.GithubRelease | PackageVersionType.Commit; + + protected async Task InstallCudaTorch( + PyVenvRunner venvRunner, + IProgress? progress = null + ) { - progress?.Report(new ProgressReport(-1f, "Installing PyTorch for CUDA", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1f, "Installing PyTorch for CUDA", isIndeterminate: true) + ); - await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsCuda, OnConsoleOutput) + await venvRunner + .PipInstall(PyVenvRunner.TorchPipInstallArgsCuda, OnConsoleOutput) .ConfigureAwait(false); await venvRunner.PipInstall("xformers", OnConsoleOutput).ConfigureAwait(false); } - - protected async Task InstallDirectMlTorch(PyVenvRunner venvRunner, - IProgress? progress = null) + + protected async Task InstallDirectMlTorch( + PyVenvRunner venvRunner, + IProgress? progress = null + ) { - progress?.Report(new ProgressReport(-1f, "Installing PyTorch for DirectML", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1f, "Installing PyTorch for DirectML", isIndeterminate: true) + ); - await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsDirectML, OnConsoleOutput) + await venvRunner + .PipInstall(PyVenvRunner.TorchPipInstallArgsDirectML, OnConsoleOutput) .ConfigureAwait(false); } - - protected async Task InstallCpuTorch(PyVenvRunner venvRunner, IProgress? progress = null) + + protected async Task InstallCpuTorch( + PyVenvRunner venvRunner, + IProgress? progress = null + ) { - progress?.Report(new ProgressReport(-1f, "Installing PyTorch for CPU", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1f, "Installing PyTorch for CPU", isIndeterminate: true) + ); - await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsCpu, OnConsoleOutput) + await venvRunner + .PipInstall(PyVenvRunner.TorchPipInstallArgsCpu, OnConsoleOutput) .ConfigureAwait(false); } } diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 0fe74c05..486ed094 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -21,7 +21,7 @@ public class ComfyUI : BaseGitPackage public override string DisplayName { get; set; } = "ComfyUI"; public override string Author => "comfyanonymous"; public override string LicenseType => "GPL-3.0"; - public override string LicenseUrl => + public override string LicenseUrl => "https://github.com/comfyanonymous/ComfyUI/blob/master/LICENSE"; public override string Blurb => "A powerful and modular stable diffusion GUI and backend"; public override string LaunchCommand => "main.py"; @@ -32,77 +32,82 @@ public class ComfyUI : BaseGitPackage public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration; - public ComfyUI(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService, - IPrerequisiteHelper prerequisiteHelper) : - base(githubApi, settingsManager, downloadService, prerequisiteHelper) - { - } + public ComfyUI( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } // https://github.com/comfyanonymous/ComfyUI/blob/master/folder_paths.py#L11 - public override Dictionary> SharedFolders => new() - { - [SharedFolderType.StableDiffusion] = new[] {"models/checkpoints"}, - [SharedFolderType.Diffusers] = new[] {"models/diffusers"}, - [SharedFolderType.Lora] = new[] {"models/loras"}, - [SharedFolderType.CLIP] = new[] {"models/clip"}, - [SharedFolderType.TextualInversion] = new[] {"models/embeddings"}, - [SharedFolderType.VAE] = new[] {"models/vae"}, - [SharedFolderType.ApproxVAE] = new[] {"models/vae_approx"}, - [SharedFolderType.ControlNet] = new[] {"models/controlnet"}, - [SharedFolderType.GLIGEN] = new[] {"models/gligen"}, - [SharedFolderType.ESRGAN] = new[] {"models/upscale_models"}, - [SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"}, - }; - - public override List LaunchOptions => new List - { + public override Dictionary> SharedFolders => new() { - Name = "VRAM", - Type = LaunchOptionType.Bool, - InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch + [SharedFolderType.StableDiffusion] = new[] { "models/checkpoints" }, + [SharedFolderType.Diffusers] = new[] { "models/diffusers" }, + [SharedFolderType.Lora] = new[] { "models/loras" }, + [SharedFolderType.CLIP] = new[] { "models/clip" }, + [SharedFolderType.TextualInversion] = new[] { "models/embeddings" }, + [SharedFolderType.VAE] = new[] { "models/vae" }, + [SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" }, + [SharedFolderType.ControlNet] = new[] { "models/controlnet" }, + [SharedFolderType.GLIGEN] = new[] { "models/gligen" }, + [SharedFolderType.ESRGAN] = new[] { "models/upscale_models" }, + [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, + }; + + public override List LaunchOptions => + new List + { + new() { - Level.Low => "--lowvram", - Level.Medium => "--normalvram", - _ => null + Name = "VRAM", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper + .IterGpuInfo() + .Select(gpu => gpu.MemoryLevel) + .Max() switch + { + Level.Low => "--lowvram", + Level.Medium => "--normalvram", + _ => null + }, + Options = { "--highvram", "--normalvram", "--lowvram", "--novram" } }, - Options = { "--highvram", "--normalvram", "--lowvram", "--novram" } - }, - new() - { - Name = "Use CPU only", - Type = LaunchOptionType.Bool, - InitialValue = !HardwareHelper.HasNvidiaGpu(), - Options = {"--cpu"} - }, - new() - { - Name = "Disable Xformers", - Type = LaunchOptionType.Bool, - InitialValue = !HardwareHelper.HasNvidiaGpu(), - Options = { "--disable-xformers" } - }, - new() - { - Name = "Auto-Launch", - Type = LaunchOptionType.Bool, - Options = { "--auto-launch" } - }, - LaunchOptionDefinition.Extras - }; + new() + { + Name = "Use CPU only", + Type = LaunchOptionType.Bool, + InitialValue = !HardwareHelper.HasNvidiaGpu(), + Options = { "--cpu" } + }, + new() + { + Name = "Disable Xformers", + Type = LaunchOptionType.Bool, + InitialValue = !HardwareHelper.HasNvidiaGpu(), + Options = { "--disable-xformers" } + }, + new() + { + Name = "Auto-Launch", + Type = LaunchOptionType.Bool, + Options = { "--auto-launch" } + }, + LaunchOptionDefinition.Extras + }; public override Task GetLatestVersion() => Task.FromResult("master"); - - public override IEnumerable AvailableTorchVersions => new[] - { - TorchVersion.Cpu, - TorchVersion.Cuda, - TorchVersion.DirectMl, - TorchVersion.Rocm - }; - - public override async Task InstallPackage(string installLocation, - TorchVersion torchVersion, IProgress? progress = null) + + public override IEnumerable AvailableTorchVersions => + new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Rocm }; + + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false); @@ -132,17 +137,21 @@ public class ComfyUI : BaseGitPackage } // Install requirements file - progress?.Report(new ProgressReport(-1, "Installing Package Requirements", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1, "Installing Package Requirements", isIndeterminate: true) + ); Logger.Info("Installing requirements.txt"); await venvRunner.PipInstall($"-r requirements.txt", OnConsoleOutput).ConfigureAwait(false); - progress?.Report(new ProgressReport(1, "Installing Package Requirements", - isIndeterminate: false)); + progress?.Report( + new ProgressReport(1, "Installing Package Requirements", isIndeterminate: false) + ); } - private async Task AutoDetectAndInstallTorch(PyVenvRunner venvRunner, - IProgress? progress = null) + private async Task AutoDetectAndInstallTorch( + PyVenvRunner venvRunner, + IProgress? progress = null + ) { var gpus = HardwareHelper.IterGpuInfo().ToList(); if (gpus.Any(g => g.IsNvidia)) @@ -163,14 +172,18 @@ public class ComfyUI : BaseGitPackage } } - public override async Task RunPackage(string installedPackagePath, string command, string arguments) + public override async Task RunPackage( + string installedPackagePath, + string command, + string arguments + ) { await SetupVenv(installedPackagePath).ConfigureAwait(false); void HandleConsoleOutput(ProcessOutput s) { OnConsoleOutput(s); - + if (s.Text.Contains("To see the GUI go to", StringComparison.OrdinalIgnoreCase)) { var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)"); @@ -191,14 +204,13 @@ public class ComfyUI : BaseGitPackage var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}"; - VenvRunner?.RunDetached( - args.TrimEnd(), - HandleConsoleOutput, - HandleExit); + VenvRunner?.RunDetached(args.TrimEnd(), HandleConsoleOutput, HandleExit); } - public override Task SetupModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task SetupModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { switch (sharedFolderMethod) { @@ -223,19 +235,22 @@ public class ComfyUI : BaseGitPackage File.WriteAllText(extraPathsYamlPath, string.Empty); } var yaml = File.ReadAllText(extraPathsYamlPath); - var comfyModelPaths = deserializer.Deserialize(yaml) ?? - // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract - // cuz it can actually be null lol - new ComfyModelPathsYaml(); - + var comfyModelPaths = + deserializer.Deserialize(yaml) + ?? + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract + // cuz it can actually be null lol + new ComfyModelPathsYaml(); + comfyModelPaths.StabilityMatrix ??= new ComfyModelPathsYaml.SmData(); comfyModelPaths.StabilityMatrix.Checkpoints = Path.Combine(modelsDir, "StableDiffusion"); comfyModelPaths.StabilityMatrix.Vae = Path.Combine(modelsDir, "VAE"); - comfyModelPaths.StabilityMatrix.Loras = $"{Path.Combine(modelsDir, "Lora")}\n" + - $"{Path.Combine(modelsDir, "LyCORIS")}"; - comfyModelPaths.StabilityMatrix.UpscaleModels = $"{Path.Combine(modelsDir, "ESRGAN")}\n" + - $"{Path.Combine(modelsDir, "RealESRGAN")}\n" + - $"{Path.Combine(modelsDir, "SwinIR")}"; + comfyModelPaths.StabilityMatrix.Loras = + $"{Path.Combine(modelsDir, "Lora")}\n" + $"{Path.Combine(modelsDir, "LyCORIS")}"; + comfyModelPaths.StabilityMatrix.UpscaleModels = + $"{Path.Combine(modelsDir, "ESRGAN")}\n" + + $"{Path.Combine(modelsDir, "RealESRGAN")}\n" + + $"{Path.Combine(modelsDir, "SwinIR")}"; comfyModelPaths.StabilityMatrix.Embeddings = Path.Combine(modelsDir, "TextualInversion"); comfyModelPaths.StabilityMatrix.Hypernetworks = Path.Combine(modelsDir, "Hypernetwork"); comfyModelPaths.StabilityMatrix.Controlnet = Path.Combine(modelsDir, "ControlNet"); @@ -243,7 +258,7 @@ public class ComfyUI : BaseGitPackage comfyModelPaths.StabilityMatrix.Diffusers = Path.Combine(modelsDir, "Diffusers"); comfyModelPaths.StabilityMatrix.Gligen = Path.Combine(modelsDir, "GLIGEN"); comfyModelPaths.StabilityMatrix.VaeApprox = Path.Combine(modelsDir, "ApproxVAE"); - + var serializer = new SerializerBuilder() .WithNamingConvention(UnderscoredNamingConvention.Instance) .Build(); @@ -253,33 +268,39 @@ public class ComfyUI : BaseGitPackage return Task.CompletedTask; } - public override Task UpdateModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) => - SetupModelFolders(installDirectory, sharedFolderMethod); + public override Task UpdateModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) => SetupModelFolders(installDirectory, sharedFolderMethod); - public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task RemoveModelFolderLinks( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { return sharedFolderMethod switch { SharedFolderMethod.Configuration => Task.CompletedTask, SharedFolderMethod.None => Task.CompletedTask, - SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, - sharedFolderMethod), + SharedFolderMethod.Symlink + => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), _ => Task.CompletedTask }; } - private async Task InstallRocmTorch(PyVenvRunner venvRunner, - IProgress? progress = null) + private async Task InstallRocmTorch( + PyVenvRunner venvRunner, + IProgress? progress = null + ) { - progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true) + ); - await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput) - .ConfigureAwait(false); + await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput).ConfigureAwait(false); - await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsRocm542, OnConsoleOutput) + await venvRunner + .PipInstall(PyVenvRunner.TorchPipInstallArgsRocm542, OnConsoleOutput) .ConfigureAwait(false); } diff --git a/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs b/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs index d22dfb03..00b206a0 100644 --- a/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs +++ b/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs @@ -7,28 +7,26 @@ namespace StabilityMatrix.Core.Models.Packages; public class DankDiffusion : BaseGitPackage { - public DankDiffusion(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService, - IPrerequisiteHelper prerequisiteHelper) : - base(githubApi, settingsManager, downloadService, prerequisiteHelper) - { - } + public DankDiffusion( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } public override string Name => "dank-diffusion"; public override string DisplayName { get; set; } = "Dank Diffusion"; public override string Author => "mohnjiles"; public override string LicenseType => "AGPL-3.0"; - public override string LicenseUrl => + public override string LicenseUrl => "https://github.com/LykosAI/StabilityMatrix/blob/main/LICENSE"; public override string Blurb => "A dank interface for diffusion"; public override string LaunchCommand => "test"; - public override SharedFolderMethod RecommendedSharedFolderMethod => - SharedFolderMethod.Symlink; - - public override IReadOnlyList ExtraLaunchCommands => new[] - { - "test-config", - }; - + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; + + public override IReadOnlyList ExtraLaunchCommands => new[] { "test-config", }; + public override Uri PreviewImageUri { get; } public override Task RunPackage(string installedPackagePath, string command, string arguments) @@ -36,20 +34,26 @@ public class DankDiffusion : BaseGitPackage throw new NotImplementedException(); } - public override Task SetupModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task SetupModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { throw new NotImplementedException(); } - public override Task UpdateModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task UpdateModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { throw new NotImplementedException(); } - public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task RemoveModelFolderLinks( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { throw new NotImplementedException(); } @@ -57,6 +61,7 @@ public class DankDiffusion : BaseGitPackage public override IEnumerable AvailableTorchVersions { get; } public override List LaunchOptions { get; } + public override Task GetLatestVersion() { throw new NotImplementedException(); diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs index 5bde4fdf..9ff0c7d2 100644 --- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs @@ -10,11 +10,13 @@ namespace StabilityMatrix.Core.Models.Packages; public class Fooocus : BaseGitPackage { - public Fooocus(IGithubApiCache githubApi, ISettingsManager settingsManager, - IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper) : base(githubApi, - settingsManager, downloadService, prerequisiteHelper) - { - } + public Fooocus( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } public override string Name => "Fooocus"; public override string DisplayName { get; set; } = "Fooocus"; @@ -28,76 +30,76 @@ public class Fooocus : BaseGitPackage public override string LaunchCommand => "launch.py"; public override Uri PreviewImageUri => - new("https://user-images.githubusercontent.com/19834515/261830306-f79c5981-cf80-4ee3-b06b-3fef3f8bfbc7.png"); + new( + "https://user-images.githubusercontent.com/19834515/261830306-f79c5981-cf80-4ee3-b06b-3fef3f8bfbc7.png" + ); - public override List LaunchOptions => new() - { - new LaunchOptionDefinition + public override List LaunchOptions => + new() { - Name = "Port", - Type = LaunchOptionType.String, - Description = "Sets the listen port", - Options = {"--port"} - }, - new LaunchOptionDefinition - { - Name = "Share", - Type = LaunchOptionType.Bool, - Description = "Set whether to share on Gradio", - Options = {"--share"} - }, - new LaunchOptionDefinition + new LaunchOptionDefinition + { + Name = "Port", + Type = LaunchOptionType.String, + Description = "Sets the listen port", + Options = { "--port" } + }, + new LaunchOptionDefinition + { + Name = "Share", + Type = LaunchOptionType.Bool, + Description = "Set whether to share on Gradio", + Options = { "--share" } + }, + new LaunchOptionDefinition + { + Name = "Listen", + Type = LaunchOptionType.String, + Description = "Set the listen interface", + Options = { "--listen" } + }, + LaunchOptionDefinition.Extras + }; + + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; + + public override IEnumerable AvailableSharedFolderMethods => + new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None }; + + public override Dictionary> SharedFolders => + new() { - Name = "Listen", - Type = LaunchOptionType.String, - Description = "Set the listen interface", - Options = {"--listen"} - }, - LaunchOptionDefinition.Extras - }; - - public override SharedFolderMethod RecommendedSharedFolderMethod => - SharedFolderMethod.Symlink; - - public override IEnumerable AvailableSharedFolderMethods => new[] - { - SharedFolderMethod.Symlink, - SharedFolderMethod.None - }; + [SharedFolderType.StableDiffusion] = new[] { "models/checkpoints" }, + [SharedFolderType.Diffusers] = new[] { "models/diffusers" }, + [SharedFolderType.Lora] = new[] { "models/loras" }, + [SharedFolderType.CLIP] = new[] { "models/clip" }, + [SharedFolderType.TextualInversion] = new[] { "models/embeddings" }, + [SharedFolderType.VAE] = new[] { "models/vae" }, + [SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" }, + [SharedFolderType.ControlNet] = new[] { "models/controlnet" }, + [SharedFolderType.GLIGEN] = new[] { "models/gligen" }, + [SharedFolderType.ESRGAN] = new[] { "models/upscale_models" }, + [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" } + }; + + public override IEnumerable AvailableTorchVersions => + new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.Rocm }; - public override Dictionary> SharedFolders => new() - { - [SharedFolderType.StableDiffusion] = new[] {"models/checkpoints"}, - [SharedFolderType.Diffusers] = new[] {"models/diffusers"}, - [SharedFolderType.Lora] = new[] {"models/loras"}, - [SharedFolderType.CLIP] = new[] {"models/clip"}, - [SharedFolderType.TextualInversion] = new[] {"models/embeddings"}, - [SharedFolderType.VAE] = new[] {"models/vae"}, - [SharedFolderType.ApproxVAE] = new[] {"models/vae_approx"}, - [SharedFolderType.ControlNet] = new[] {"models/controlnet"}, - [SharedFolderType.GLIGEN] = new[] {"models/gligen"}, - [SharedFolderType.ESRGAN] = new[] {"models/upscale_models"}, - [SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"} - }; - - public override IEnumerable AvailableTorchVersions => new[] - { - TorchVersion.Cpu, - TorchVersion.Cuda, - TorchVersion.Rocm - }; - public override async Task GetLatestVersion() { var release = await GetLatestRelease().ConfigureAwait(false); return release.TagName!; } - public override async Task InstallPackage(string installLocation, - TorchVersion torchVersion, IProgress? progress = null) + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false); - var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false); + var venvRunner = await SetupVenv(installLocation, forceRecreate: true) + .ConfigureAwait(false); progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true)); @@ -120,22 +122,30 @@ public class Fooocus : BaseGitPackage await venvRunner .PipInstall( $"torch==2.0.1 torchvision==0.15.2 --extra-index-url https://download.pytorch.org/whl/{torchVersionStr}", - OnConsoleOutput).ConfigureAwait(false); + OnConsoleOutput + ) + .ConfigureAwait(false); - progress?.Report(new ProgressReport(-1f, "Installing requirements...", - isIndeterminate: true)); - await venvRunner.PipInstall("-r requirements_versions.txt", OnConsoleOutput) + progress?.Report( + new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true) + ); + await venvRunner + .PipInstall("-r requirements_versions.txt", OnConsoleOutput) .ConfigureAwait(false); } - public override async Task RunPackage(string installedPackagePath, string command, string arguments) + public override async Task RunPackage( + string installedPackagePath, + string command, + string arguments + ) { await SetupVenv(installedPackagePath).ConfigureAwait(false); void HandleConsoleOutput(ProcessOutput s) { OnConsoleOutput(s); - + if (s.Text.Contains("Use the app with", StringComparison.OrdinalIgnoreCase)) { var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)"); @@ -156,9 +166,6 @@ public class Fooocus : BaseGitPackage var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}"; - VenvRunner?.RunDetached( - args.TrimEnd(), - HandleConsoleOutput, - HandleExit); + VenvRunner?.RunDetached(args.TrimEnd(), HandleConsoleOutput, HandleExit); } } diff --git a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs index 0dd329c6..5f71f3ba 100644 --- a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs +++ b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs @@ -21,114 +21,112 @@ public class InvokeAI : BaseGitPackage public override string Author => "invoke-ai"; public override string LicenseType => "Apache-2.0"; - public override string LicenseUrl => - "https://github.com/invoke-ai/InvokeAI/blob/main/LICENSE"; + public override string LicenseUrl => "https://github.com/invoke-ai/InvokeAI/blob/main/LICENSE"; public override string Blurb => "Professional Creative Tools for Stable Diffusion"; public override string LaunchCommand => "invokeai-web"; - public override IReadOnlyList ExtraLaunchCommands => new[] - { - "invokeai-configure", - "invokeai-merge", - "invokeai-metadata", - "invokeai-model-install", - "invokeai-node-cli", - "invokeai-ti", - "invokeai-update", - }; - - public override Uri PreviewImageUri => new( - "https://raw.githubusercontent.com/invoke-ai/InvokeAI/main/docs/assets/canvas_preview.png"); + public override IReadOnlyList ExtraLaunchCommands => + new[] + { + "invokeai-configure", + "invokeai-merge", + "invokeai-metadata", + "invokeai-model-install", + "invokeai-node-cli", + "invokeai-ti", + "invokeai-update", + }; + + public override Uri PreviewImageUri => + new( + "https://raw.githubusercontent.com/invoke-ai/InvokeAI/main/docs/assets/canvas_preview.png" + ); public override bool ShouldIgnoreReleases => true; - public override IEnumerable AvailableSharedFolderMethods => new[] - { - SharedFolderMethod.Symlink, - SharedFolderMethod.None - }; - public override SharedFolderMethod RecommendedSharedFolderMethod => - SharedFolderMethod.Symlink; + public override IEnumerable AvailableSharedFolderMethods => + new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None }; + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; public InvokeAI( IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService, - IPrerequisiteHelper prerequisiteHelper) : - base(githubApi, settingsManager, downloadService, prerequisiteHelper) - { - } - - public override Dictionary> SharedFolders => new() - { - [SharedFolderType.StableDiffusion] = new[] { RelativeRootPath + "/autoimport/main" }, - [SharedFolderType.Lora] = new[] { RelativeRootPath + "/autoimport/lora" }, - [SharedFolderType.TextualInversion] = new[] { RelativeRootPath + "/autoimport/embedding" }, - [SharedFolderType.ControlNet] = new[] { RelativeRootPath + "/autoimport/controlnet" }, - }; + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } - // https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CONFIGURATION.md - public override List LaunchOptions => new List - { + public override Dictionary> SharedFolders => new() { - Name = "Host", - Type = LaunchOptionType.String, - DefaultValue = "localhost", - Options = new List {"--host"} - }, - new() - { - Name = "Port", - Type = LaunchOptionType.String, - DefaultValue = "9090", - Options = new List {"--port"} - }, - new() - { - Name = "Allow Origins", - Description = "List of host names or IP addresses that are allowed to connect to the " + - "InvokeAI API in the format ['host1','host2',...]", - Type = LaunchOptionType.String, - DefaultValue = "[]", - Options = new List {"--allow-origins"} - }, - new() - { - Name = "Always use CPU", - Type = LaunchOptionType.Bool, - Options = new List {"--always_use_cpu"} - }, - new() - { - Name = "Precision", - Type = LaunchOptionType.Bool, - Options = new List + [SharedFolderType.StableDiffusion] = new[] { RelativeRootPath + "/autoimport/main" }, + [SharedFolderType.Lora] = new[] { RelativeRootPath + "/autoimport/lora" }, + [SharedFolderType.TextualInversion] = new[] { - "--precision auto", - "--precision float16", - "--precision float32", - } - }, - new() + RelativeRootPath + "/autoimport/embedding" + }, + [SharedFolderType.ControlNet] = new[] { RelativeRootPath + "/autoimport/controlnet" }, + }; + + // https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CONFIGURATION.md + public override List LaunchOptions => + new List { - Name = "Aggressively free up GPU memory after each operation", - Type = LaunchOptionType.Bool, - Options = new List {"--free_gpu_mem"} - }, - LaunchOptionDefinition.Extras - }; + new() + { + Name = "Host", + Type = LaunchOptionType.String, + DefaultValue = "localhost", + Options = new List { "--host" } + }, + new() + { + Name = "Port", + Type = LaunchOptionType.String, + DefaultValue = "9090", + Options = new List { "--port" } + }, + new() + { + Name = "Allow Origins", + Description = + "List of host names or IP addresses that are allowed to connect to the " + + "InvokeAI API in the format ['host1','host2',...]", + Type = LaunchOptionType.String, + DefaultValue = "[]", + Options = new List { "--allow-origins" } + }, + new() + { + Name = "Always use CPU", + Type = LaunchOptionType.Bool, + Options = new List { "--always_use_cpu" } + }, + new() + { + Name = "Precision", + Type = LaunchOptionType.Bool, + Options = new List + { + "--precision auto", + "--precision float16", + "--precision float32", + } + }, + new() + { + Name = "Aggressively free up GPU memory after each operation", + Type = LaunchOptionType.Bool, + Options = new List { "--free_gpu_mem" } + }, + LaunchOptionDefinition.Extras + }; public override Task GetLatestVersion() => Task.FromResult("main"); - public override IEnumerable AvailableTorchVersions => new[] - { - TorchVersion.Cpu, - TorchVersion.Cuda, - TorchVersion.Rocm, - TorchVersion.Mps - }; + public override IEnumerable AvailableTorchVersions => + new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.Rocm, TorchVersion.Mps }; public override TorchVersion GetRecommendedTorchVersion() { @@ -140,14 +138,20 @@ public class InvokeAI : BaseGitPackage return base.GetRecommendedTorchVersion(); } - public override Task DownloadPackage(string installLocation, - DownloadPackageVersionOptions downloadOptions, IProgress? progress = null) + public override Task DownloadPackage( + string installLocation, + DownloadPackageVersionOptions downloadOptions, + IProgress? progress = null + ) { return Task.CompletedTask; } - public override async Task InstallPackage(string installLocation, TorchVersion torchVersion, - IProgress? progress = null) + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { // Setup venv progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true)); @@ -169,13 +173,13 @@ public class InvokeAI : BaseGitPackage pipCommandArgs = "InvokeAI[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu117"; break; - + case TorchVersion.Rocm: Logger.Info("Starting InvokeAI install (ROCm)..."); pipCommandArgs = "InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.4.2"; break; - + case TorchVersion.Mps: Logger.Info("Starting InvokeAI install (MPS)..."); pipCommandArgs = "InvokeAI --use-pep517"; @@ -190,15 +194,23 @@ public class InvokeAI : BaseGitPackage progress?.Report(new ProgressReport(-1f, "Configuring InvokeAI", isIndeterminate: true)); - await RunInvokeCommand(installLocation, "invokeai-configure", "--yes --skip-sd-weights", - false).ConfigureAwait(false); + await RunInvokeCommand( + installLocation, + "invokeai-configure", + "--yes --skip-sd-weights", + false + ) + .ConfigureAwait(false); progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false)); } - public override async Task Update(InstalledPackage installedPackage, - TorchVersion torchVersion, IProgress? progress = null, - bool includePrerelease = false) + public override async Task Update( + InstalledPackage installedPackage, + TorchVersion torchVersion, + IProgress? progress = null, + bool includePrerelease = false + ) { progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true)); @@ -206,8 +218,10 @@ public class InvokeAI : BaseGitPackage { throw new NullReferenceException("Installed package is missing Path and/or Version"); } - - await using var venvRunner = new PyVenvRunner(Path.Combine(installedPackage.FullPath, "venv")); + + await using var venvRunner = new PyVenvRunner( + Path.Combine(installedPackage.FullPath, "venv") + ); venvRunner.WorkingDirectory = installedPackage.FullPath; venvRunner.EnvironmentVariables = GetEnvVars(installedPackage.FullPath); @@ -251,10 +265,7 @@ public class InvokeAI : BaseGitPackage progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false)); return isReleaseMode - ? new InstalledPackageVersion - { - InstalledReleaseVersion = latestVersion - } + ? new InstalledPackageVersion { InstalledReleaseVersion = latestVersion } : new InstalledPackageVersion { InstalledBranch = installedPackage.Version.InstalledBranch, @@ -262,15 +273,20 @@ public class InvokeAI : BaseGitPackage }; } - public override Task - RunPackage(string installedPackagePath, string command, string arguments) => - RunInvokeCommand(installedPackagePath, command, arguments, true); + public override Task RunPackage( + string installedPackagePath, + string command, + string arguments + ) => RunInvokeCommand(installedPackagePath, command, arguments, true); - private async Task GetUpdateVersion(InstalledPackage installedPackage, bool includePrerelease = false) + private async Task GetUpdateVersion( + InstalledPackage installedPackage, + bool includePrerelease = false + ) { if (installedPackage.Version == null) throw new NullReferenceException("Installed package version is null"); - + if (installedPackage.Version.IsReleaseMode) { var releases = await GetAllReleases().ConfigureAwait(false); @@ -284,8 +300,12 @@ public class InvokeAI : BaseGitPackage return latestCommit.Sha; } - private async Task RunInvokeCommand(string installedPackagePath, string command, - string arguments, bool runDetached) + private async Task RunInvokeCommand( + string installedPackagePath, + string command, + string arguments, + bool runDetached + ) { await SetupVenv(installedPackagePath).ConfigureAwait(false); @@ -304,12 +324,12 @@ public class InvokeAI : BaseGitPackage // Split at ':' to get package and function var split = entryPoint?.Split(':'); - if (split is not {Length: > 1}) + if (split is not { Length: > 1 }) { throw new Exception($"Could not find entry point for InvokeAI: {entryPoint.ToRepr()}"); } - // Compile a startup command according to + // Compile a startup command according to // https://packaging.python.org/en/latest/specifications/entry-points/#use-for-scripts // For invokeai, also patch the shutil.get_terminal_size function to return a fixed value // above the minimum in invokeai.frontend.install.widgets @@ -326,28 +346,30 @@ public class InvokeAI : BaseGitPackage { OnConsoleOutput(s); - if (!s.Text.Contains("running on", StringComparison.OrdinalIgnoreCase)) + if (!s.Text.Contains("running on", StringComparison.OrdinalIgnoreCase)) return; - + var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)"); var match = regex.Match(s.Text); if (!match.Success) return; - + WebUrl = match.Value; OnStartupComplete(WebUrl); } - - VenvRunner.RunDetached($"-c \"{code}\" {arguments}".TrimEnd(), HandleConsoleOutput, OnExit); + + VenvRunner.RunDetached( + $"-c \"{code}\" {arguments}".TrimEnd(), + HandleConsoleOutput, + OnExit + ); } else { - var result = await VenvRunner.Run($"-c \"{code}\" {arguments}".TrimEnd()) + var result = await VenvRunner + .Run($"-c \"{code}\" {arguments}".TrimEnd()) .ConfigureAwait(false); - OnConsoleOutput(new ProcessOutput - { - Text = result.StandardOutput - }); + OnConsoleOutput(new ProcessOutput { Text = result.StandardOutput }); } } diff --git a/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs b/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs index 226d1e1d..cd22eec6 100644 --- a/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs @@ -14,30 +14,32 @@ public class UnknownPackage : BasePackage public override string GithubUrl => ""; public override string LicenseType => "AGPL-3.0"; - public override string LicenseUrl => + public override string LicenseUrl => "https://github.com/LykosAI/StabilityMatrix/blob/main/LICENSE"; public override string Blurb => "A dank interface for diffusion"; public override string LaunchCommand => "test"; - + public override Uri PreviewImageUri => new(""); - - public override IReadOnlyList ExtraLaunchCommands => new[] - { - "test-config", - }; - - public override SharedFolderMethod RecommendedSharedFolderMethod => - SharedFolderMethod.Symlink; - - public override Task DownloadPackage(string installLocation, DownloadPackageVersionOptions versionOptions, - IProgress? progress1) + + public override IReadOnlyList ExtraLaunchCommands => new[] { "test-config", }; + + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; + + public override Task DownloadPackage( + string installLocation, + DownloadPackageVersionOptions versionOptions, + IProgress? progress1 + ) { throw new NotImplementedException(); } /// - public override Task InstallPackage(string installLocation, TorchVersion torchVersion, - IProgress? progress = null) + public override Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { throw new NotImplementedException(); } @@ -48,33 +50,34 @@ public class UnknownPackage : BasePackage } /// - public override Task SetupModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task SetupModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { throw new NotImplementedException(); } /// - public override Task UpdateModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task UpdateModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { throw new NotImplementedException(); } /// - public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) + public override Task RemoveModelFolderLinks( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { throw new NotImplementedException(); } - public override IEnumerable AvailableTorchVersions => new[] - { - TorchVersion.Cuda, - TorchVersion.Cpu, - TorchVersion.Rocm, - TorchVersion.DirectMl - }; + public override IEnumerable AvailableTorchVersions => + new[] { TorchVersion.Cuda, TorchVersion.Cpu, TorchVersion.Rocm, TorchVersion.DirectMl }; /// public override void Shutdown() @@ -95,28 +98,39 @@ public class UnknownPackage : BasePackage } /// - public override Task Update(InstalledPackage installedPackage, - TorchVersion torchVersion, IProgress? progress = null, - bool includePrerelease = false) + public override Task Update( + InstalledPackage installedPackage, + TorchVersion torchVersion, + IProgress? progress = null, + bool includePrerelease = false + ) { throw new NotImplementedException(); } /// - public override Task> GetReleaseTags() => Task.FromResult(Enumerable.Empty()); + public override Task> GetReleaseTags() => + Task.FromResult(Enumerable.Empty()); public override List LaunchOptions => new(); + public override Task GetLatestVersion() => Task.FromResult(string.Empty); public override Task GetAllVersionOptions() => Task.FromResult(new PackageVersionOptions()); /// - public override Task?> GetAllCommits(string branch, int page = 1, int perPage = 10) => Task.FromResult?>(null); + public override Task?> GetAllCommits( + string branch, + int page = 1, + int perPage = 10 + ) => Task.FromResult?>(null); /// - public override Task> GetAllBranches() => Task.FromResult(Enumerable.Empty()); + public override Task> GetAllBranches() => + Task.FromResult(Enumerable.Empty()); /// - public override Task> GetAllReleases() => Task.FromResult(Enumerable.Empty()); + public override Task> GetAllReleases() => + Task.FromResult(Enumerable.Empty()); } diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index 55810340..c363c7c9 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -17,12 +17,12 @@ namespace StabilityMatrix.Core.Models.Packages; public class VladAutomatic : BaseGitPackage { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + public override string Name => "automatic"; public override string DisplayName { get; set; } = "SD.Next Web UI"; public override string Author => "vladmandic"; public override string LicenseType => "AGPL-3.0"; - public override string LicenseUrl => + public override string LicenseUrl => "https://github.com/vladmandic/automatic/blob/master/LICENSE.txt"; public override string Blurb => "Stable Diffusion implementation with advanced features"; public override string LaunchCommand => "launch.py"; @@ -30,129 +30,133 @@ public class VladAutomatic : BaseGitPackage public override Uri PreviewImageUri => new("https://github.com/vladmandic/automatic/raw/master/html/black-orange.jpg"); public override bool ShouldIgnoreReleases => true; - - public override SharedFolderMethod RecommendedSharedFolderMethod => - SharedFolderMethod.Symlink; - public override IEnumerable AvailableTorchVersions => new[] - { - TorchVersion.Cpu, - TorchVersion.Rocm, - TorchVersion.DirectMl, - TorchVersion.Cuda - }; - - public VladAutomatic(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService, - IPrerequisiteHelper prerequisiteHelper) : - base(githubApi, settingsManager, downloadService, prerequisiteHelper) - { - } + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; - // https://github.com/vladmandic/automatic/blob/master/modules/shared.py#L324 - public override Dictionary> SharedFolders => new() - { - [SharedFolderType.StableDiffusion] = new[] {"models/Stable-diffusion"}, - [SharedFolderType.Diffusers] = new[] {"models/Diffusers"}, - [SharedFolderType.VAE] = new[] {"models/VAE"}, - [SharedFolderType.TextualInversion] = new[] {"models/embeddings"}, - [SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"}, - [SharedFolderType.Codeformer] = new[] {"models/Codeformer"}, - [SharedFolderType.GFPGAN] = new[] {"models/GFPGAN"}, - [SharedFolderType.BSRGAN] = new[] {"models/BSRGAN"}, - [SharedFolderType.ESRGAN] = new[] {"models/ESRGAN"}, - [SharedFolderType.RealESRGAN] = new[] {"models/RealESRGAN"}, - [SharedFolderType.ScuNET] = new[] {"models/ScuNET"}, - [SharedFolderType.SwinIR] = new[] {"models/SwinIR"}, - [SharedFolderType.LDSR] = new[] {"models/LDSR"}, - [SharedFolderType.CLIP] = new[] {"models/CLIP"}, - [SharedFolderType.Lora] = new[] {"models/Lora"}, - [SharedFolderType.LyCORIS] = new[] {"models/LyCORIS"}, - [SharedFolderType.ControlNet] = new[] {"models/ControlNet"} - }; + public override IEnumerable AvailableTorchVersions => + new[] { TorchVersion.Cpu, TorchVersion.Rocm, TorchVersion.DirectMl, TorchVersion.Cuda }; - [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")] - public override List LaunchOptions => new() - { - new() - { - Name = "Host", - Type = LaunchOptionType.String, - DefaultValue = "localhost", - Options = new() {"--server-name"} - }, + public VladAutomatic( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } + + // https://github.com/vladmandic/automatic/blob/master/modules/shared.py#L324 + public override Dictionary> SharedFolders => new() { - Name = "Port", - Type = LaunchOptionType.String, - DefaultValue = "7860", - Options = new() {"--port"} - }, + [SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" }, + [SharedFolderType.Diffusers] = new[] { "models/Diffusers" }, + [SharedFolderType.VAE] = new[] { "models/VAE" }, + [SharedFolderType.TextualInversion] = new[] { "models/embeddings" }, + [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, + [SharedFolderType.Codeformer] = new[] { "models/Codeformer" }, + [SharedFolderType.GFPGAN] = new[] { "models/GFPGAN" }, + [SharedFolderType.BSRGAN] = new[] { "models/BSRGAN" }, + [SharedFolderType.ESRGAN] = new[] { "models/ESRGAN" }, + [SharedFolderType.RealESRGAN] = new[] { "models/RealESRGAN" }, + [SharedFolderType.ScuNET] = new[] { "models/ScuNET" }, + [SharedFolderType.SwinIR] = new[] { "models/SwinIR" }, + [SharedFolderType.LDSR] = new[] { "models/LDSR" }, + [SharedFolderType.CLIP] = new[] { "models/CLIP" }, + [SharedFolderType.Lora] = new[] { "models/Lora" }, + [SharedFolderType.LyCORIS] = new[] { "models/LyCORIS" }, + [SharedFolderType.ControlNet] = new[] { "models/ControlNet" } + }; + + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")] + public override List LaunchOptions => new() { - Name = "VRAM", - Type = LaunchOptionType.Bool, - InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch + new() { - Level.Low => "--lowvram", - Level.Medium => "--medvram", - _ => null + Name = "Host", + Type = LaunchOptionType.String, + DefaultValue = "localhost", + Options = new() { "--server-name" } }, - Options = new() { "--lowvram", "--medvram" } - }, - new() - { - Name = "Force use of Intel OneAPI XPU backend", - Type = LaunchOptionType.Bool, - Options = new() { "--use-ipex" } - }, - new() - { - Name = "Use DirectML if no compatible GPU is detected", - Type = LaunchOptionType.Bool, - InitialValue = HardwareHelper.PreferDirectML(), - Options = new() { "--use-directml" } - }, - new() - { - Name = "Force use of Nvidia CUDA backend", - Type = LaunchOptionType.Bool, - InitialValue = HardwareHelper.HasNvidiaGpu(), - Options = new() { "--use-cuda" } - }, - new() - { - Name = "Force use of AMD ROCm backend", - Type = LaunchOptionType.Bool, - InitialValue = HardwareHelper.PreferRocm(), - Options = new() { "--use-rocm" } - }, - new() - { - Name = "CUDA Device ID", - Type = LaunchOptionType.String, - Options = new() { "--device-id" } - }, - new() - { - Name = "API", - Type = LaunchOptionType.Bool, - Options = new() { "--api" } - }, - new() - { - Name = "Debug Logging", - Type = LaunchOptionType.Bool, - Options = new() { "--debug" } - }, - LaunchOptionDefinition.Extras - }; + new() + { + Name = "Port", + Type = LaunchOptionType.String, + DefaultValue = "7860", + Options = new() { "--port" } + }, + new() + { + Name = "VRAM", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper + .IterGpuInfo() + .Select(gpu => gpu.MemoryLevel) + .Max() switch + { + Level.Low => "--lowvram", + Level.Medium => "--medvram", + _ => null + }, + Options = new() { "--lowvram", "--medvram" } + }, + new() + { + Name = "Force use of Intel OneAPI XPU backend", + Type = LaunchOptionType.Bool, + Options = new() { "--use-ipex" } + }, + new() + { + Name = "Use DirectML if no compatible GPU is detected", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper.PreferDirectML(), + Options = new() { "--use-directml" } + }, + new() + { + Name = "Force use of Nvidia CUDA backend", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper.HasNvidiaGpu(), + Options = new() { "--use-cuda" } + }, + new() + { + Name = "Force use of AMD ROCm backend", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper.PreferRocm(), + Options = new() { "--use-rocm" } + }, + new() + { + Name = "CUDA Device ID", + Type = LaunchOptionType.String, + Options = new() { "--device-id" } + }, + new() + { + Name = "API", + Type = LaunchOptionType.Bool, + Options = new() { "--api" } + }, + new() + { + Name = "Debug Logging", + Type = LaunchOptionType.Bool, + Options = new() { "--debug" } + }, + LaunchOptionDefinition.Extras + }; public override string ExtraLaunchArguments => ""; - + public override Task GetLatestVersion() => Task.FromResult("master"); - public override async Task InstallPackage(string installLocation, TorchVersion torchVersion, - IProgress? progress = null) + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { progress?.Report(new ProgressReport(-1f, "Installing package...", isIndeterminate: true)); // Setup venv @@ -166,11 +170,13 @@ public class VladAutomatic : BaseGitPackage { // Run initial install case TorchVersion.Cuda: - await venvRunner.CustomInstall("launch.py --use-cuda --debug --test", OnConsoleOutput) + await venvRunner + .CustomInstall("launch.py --use-cuda --debug --test", OnConsoleOutput) .ConfigureAwait(false); break; case TorchVersion.Rocm: - await venvRunner.CustomInstall("launch.py --use-rocm --debug --test", OnConsoleOutput) + await venvRunner + .CustomInstall("launch.py --use-rocm --debug --test", OnConsoleOutput) .ConfigureAwait(false); break; case TorchVersion.DirectMl: @@ -180,7 +186,8 @@ public class VladAutomatic : BaseGitPackage break; default: // CPU - await venvRunner.CustomInstall("launch.py --debug --test", OnConsoleOutput) + await venvRunner + .CustomInstall("launch.py --debug --test", OnConsoleOutput) .ConfigureAwait(false); break; } @@ -188,11 +195,20 @@ public class VladAutomatic : BaseGitPackage progress?.Report(new ProgressReport(1f, isIndeterminate: false)); } - public override async Task DownloadPackage(string installLocation, - DownloadPackageVersionOptions downloadOptions, IProgress? progress = null) + public override async Task DownloadPackage( + string installLocation, + DownloadPackageVersionOptions downloadOptions, + IProgress? progress = null + ) { - progress?.Report(new ProgressReport(-1f, message: "Downloading package...", - isIndeterminate: true, type: ProgressType.Download)); + progress?.Report( + new ProgressReport( + -1f, + message: "Downloading package...", + isIndeterminate: true, + type: ProgressType.Download + ) + ); var installDir = new DirectoryPath(installLocation); installDir.Create(); @@ -200,22 +216,38 @@ public class VladAutomatic : BaseGitPackage if (!string.IsNullOrWhiteSpace(downloadOptions.CommitHash)) { await PrerequisiteHelper - .RunGit(installDir.Parent ?? "", "clone", "https://github.com/vladmandic/automatic", - installDir.Name).ConfigureAwait(false); + .RunGit( + installDir.Parent ?? "", + "clone", + "https://github.com/vladmandic/automatic", + installDir.Name + ) + .ConfigureAwait(false); - await PrerequisiteHelper.RunGit(installLocation, "checkout", downloadOptions.CommitHash) + await PrerequisiteHelper + .RunGit(installLocation, "checkout", downloadOptions.CommitHash) .ConfigureAwait(false); } else if (!string.IsNullOrWhiteSpace(downloadOptions.BranchName)) { await PrerequisiteHelper - .RunGit(installDir.Parent ?? "", "clone", "-b", downloadOptions.BranchName, - "https://github.com/vladmandic/automatic", installDir.Name) + .RunGit( + installDir.Parent ?? "", + "clone", + "-b", + downloadOptions.BranchName, + "https://github.com/vladmandic/automatic", + installDir.Name + ) .ConfigureAwait(false); } } - public override async Task RunPackage(string installedPackagePath, string command, string arguments) + public override async Task RunPackage( + string installedPackagePath, + string command, + string arguments + ) { await SetupVenv(installedPackagePath).ConfigureAwait(false); @@ -245,35 +277,44 @@ public class VladAutomatic : BaseGitPackage VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, HandleExit); } - public override async Task Update(InstalledPackage installedPackage, - TorchVersion torchVersion, IProgress? progress = null, - bool includePrerelease = false) + public override async Task Update( + InstalledPackage installedPackage, + TorchVersion torchVersion, + IProgress? progress = null, + bool includePrerelease = false + ) { if (installedPackage.Version is null) { throw new Exception("Version is null"); } - progress?.Report(new ProgressReport(-1f, message: "Downloading package update...", - isIndeterminate: true, type: ProgressType.Update)); - - await PrerequisiteHelper.RunGit(installedPackage.FullPath, "checkout", - installedPackage.Version.InstalledBranch).ConfigureAwait(false); + progress?.Report( + new ProgressReport( + -1f, + message: "Downloading package update...", + isIndeterminate: true, + type: ProgressType.Update + ) + ); + + await PrerequisiteHelper + .RunGit(installedPackage.FullPath, "checkout", installedPackage.Version.InstalledBranch) + .ConfigureAwait(false); var venvRunner = new PyVenvRunner(Path.Combine(installedPackage.FullPath!, "venv")); venvRunner.WorkingDirectory = installedPackage.FullPath!; venvRunner.EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables; - await venvRunner.CustomInstall("launch.py --upgrade --test", OnConsoleOutput) + await venvRunner + .CustomInstall("launch.py --upgrade --test", OnConsoleOutput) .ConfigureAwait(false); - try { - var output = - await PrerequisiteHelper - .GetGitOutput(installedPackage.FullPath, "rev-parse", "HEAD") - .ConfigureAwait(false); + var output = await PrerequisiteHelper + .GetGitOutput(installedPackage.FullPath, "rev-parse", "HEAD") + .ConfigureAwait(false); return new InstalledPackageVersion { @@ -287,9 +328,14 @@ public class VladAutomatic : BaseGitPackage } finally { - progress?.Report(new ProgressReport(1f, message: "Update Complete", - isIndeterminate: false, - type: ProgressType.Update)); + progress?.Report( + new ProgressReport( + 1f, + message: "Update Complete", + isIndeterminate: false, + type: ProgressType.Update + ) + ); } return new InstalledPackageVersion @@ -298,7 +344,10 @@ public class VladAutomatic : BaseGitPackage }; } - public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) + public override Task SetupModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) { switch (sharedFolderMethod) { @@ -335,38 +384,56 @@ public class VladAutomatic : BaseGitPackage configRoot["vae_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "VAE"); configRoot["lora_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "Lora"); configRoot["lyco_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "LyCORIS"); - configRoot["embeddings_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "TextualInversion"); - configRoot["hypernetwork_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "Hypernetwork"); - configRoot["codeformer_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "Codeformer"); + configRoot["embeddings_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + "TextualInversion" + ); + configRoot["hypernetwork_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + "Hypernetwork" + ); + configRoot["codeformer_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + "Codeformer" + ); configRoot["gfpgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "GFPGAN"); configRoot["bsrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "BSRGAN"); configRoot["esrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ESRGAN"); - configRoot["realesrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "RealESRGAN"); + configRoot["realesrgan_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + "RealESRGAN" + ); configRoot["scunet_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ScuNET"); configRoot["swinir_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "SwinIR"); configRoot["ldsr_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "LDSR"); configRoot["clip_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "CLIP"); - configRoot["control_net_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ControlNet"); - - var configJsonStr = JsonSerializer.Serialize(configRoot, new JsonSerializerOptions - { - WriteIndented = true - }); + configRoot["control_net_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + "ControlNet" + ); + + var configJsonStr = JsonSerializer.Serialize( + configRoot, + new JsonSerializerOptions { WriteIndented = true } + ); File.WriteAllText(configJsonPath, configJsonStr); return Task.CompletedTask; } - public override Task UpdateModelFolders(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) => - SetupModelFolders(installDirectory, sharedFolderMethod); + public override Task UpdateModelFolders( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) => SetupModelFolders(installDirectory, sharedFolderMethod); - public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod) => + public override Task RemoveModelFolderLinks( + DirectoryPath installDirectory, + SharedFolderMethod sharedFolderMethod + ) => sharedFolderMethod switch { - SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, - sharedFolderMethod), + SharedFolderMethod.Symlink + => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), SharedFolderMethod.None => Task.CompletedTask, _ => Task.CompletedTask }; diff --git a/StabilityMatrix.Core/Models/Packages/VoltaML.cs b/StabilityMatrix.Core/Models/Packages/VoltaML.cs index cdffc092..79daccc2 100644 --- a/StabilityMatrix.Core/Models/Packages/VoltaML.cs +++ b/StabilityMatrix.Core/Models/Packages/VoltaML.cs @@ -14,128 +14,130 @@ public class VoltaML : BaseGitPackage public override string DisplayName { get; set; } = "VoltaML"; public override string Author => "VoltaML"; public override string LicenseType => "GPL-3.0"; - public override string LicenseUrl => + public override string LicenseUrl => "https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/License"; public override string Blurb => "Fast Stable Diffusion with support for AITemplate"; public override string LaunchCommand => "main.py"; - public override Uri PreviewImageUri => new( - "https://github.com/LykosAI/StabilityMatrix/assets/13956642/d9a908ed-5665-41a5-a380-98458f4679a8"); - + public override Uri PreviewImageUri => + new( + "https://github.com/LykosAI/StabilityMatrix/assets/13956642/d9a908ed-5665-41a5-a380-98458f4679a8" + ); + // There are releases but the manager just downloads the latest commit anyways, // so we'll just limit to commit mode to be more consistent public override bool ShouldIgnoreReleases => true; - + public VoltaML( IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService, - IPrerequisiteHelper prerequisiteHelper) : - base(githubApi, settingsManager, downloadService, prerequisiteHelper) - { - } + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } // https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/main.py#L86 - public override Dictionary> SharedFolders => new() - { - [SharedFolderType.StableDiffusion] = new[] {"data/models"}, - [SharedFolderType.Lora] = new[] {"data/lora"}, - [SharedFolderType.TextualInversion] = new[] {"data/textual-inversion"}, - }; - - public override SharedFolderMethod RecommendedSharedFolderMethod => - SharedFolderMethod.Symlink; + public override Dictionary> SharedFolders => + new() + { + [SharedFolderType.StableDiffusion] = new[] { "data/models" }, + [SharedFolderType.Lora] = new[] { "data/lora" }, + [SharedFolderType.TextualInversion] = new[] { "data/textual-inversion" }, + }; - public override IEnumerable AvailableTorchVersions => new[] {TorchVersion.None}; + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; + + public override IEnumerable AvailableTorchVersions => new[] { TorchVersion.None }; + + public override IEnumerable AvailableSharedFolderMethods => + new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None }; - public override IEnumerable AvailableSharedFolderMethods => new[] - { - SharedFolderMethod.Symlink, - SharedFolderMethod.None - }; - // https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/main.py#L45 - public override List LaunchOptions => new List - { - new() + public override List LaunchOptions => + new List { - Name = "Log Level", - Type = LaunchOptionType.Bool, - DefaultValue = "--log-level INFO", - Options = + new() { - "--log-level DEBUG", - "--log-level INFO", - "--log-level WARNING", - "--log-level ERROR", - "--log-level CRITICAL" - } - }, - new() - { - Name = "Use ngrok to expose the API", - Type = LaunchOptionType.Bool, - Options = {"--ngrok"} - }, - new() - { - Name = "Expose the API to the network", - Type = LaunchOptionType.Bool, - Options = {"--host"} - }, - new() - { - Name = "Skip virtualenv check", - Type = LaunchOptionType.Bool, - InitialValue = true, - Options = {"--in-container"} - }, - new() - { - Name = "Force VoltaML to use a specific type of PyTorch distribution", - Type = LaunchOptionType.Bool, - Options = + Name = "Log Level", + Type = LaunchOptionType.Bool, + DefaultValue = "--log-level INFO", + Options = + { + "--log-level DEBUG", + "--log-level INFO", + "--log-level WARNING", + "--log-level ERROR", + "--log-level CRITICAL" + } + }, + new() { - "--pytorch-type cpu", - "--pytorch-type cuda", - "--pytorch-type rocm", - "--pytorch-type directml", - "--pytorch-type intel", - "--pytorch-type vulkan" - } - }, - new() - { - Name = "Run in tandem with the Discord bot", - Type = LaunchOptionType.Bool, - Options = {"--bot"} - }, - new() - { - Name = "Enable Cloudflare R2 bucket upload support", - Type = LaunchOptionType.Bool, - Options = {"--enable-r2"} - }, - new() - { - Name = "Port", - Type = LaunchOptionType.String, - DefaultValue = "5003", - Options = {"--port"} - }, - new() - { - Name = "Only install requirements and exit", - Type = LaunchOptionType.Bool, - Options = {"--install-only"} - }, - LaunchOptionDefinition.Extras - }; + Name = "Use ngrok to expose the API", + Type = LaunchOptionType.Bool, + Options = { "--ngrok" } + }, + new() + { + Name = "Expose the API to the network", + Type = LaunchOptionType.Bool, + Options = { "--host" } + }, + new() + { + Name = "Skip virtualenv check", + Type = LaunchOptionType.Bool, + InitialValue = true, + Options = { "--in-container" } + }, + new() + { + Name = "Force VoltaML to use a specific type of PyTorch distribution", + Type = LaunchOptionType.Bool, + Options = + { + "--pytorch-type cpu", + "--pytorch-type cuda", + "--pytorch-type rocm", + "--pytorch-type directml", + "--pytorch-type intel", + "--pytorch-type vulkan" + } + }, + new() + { + Name = "Run in tandem with the Discord bot", + Type = LaunchOptionType.Bool, + Options = { "--bot" } + }, + new() + { + Name = "Enable Cloudflare R2 bucket upload support", + Type = LaunchOptionType.Bool, + Options = { "--enable-r2" } + }, + new() + { + Name = "Port", + Type = LaunchOptionType.String, + DefaultValue = "5003", + Options = { "--port" } + }, + new() + { + Name = "Only install requirements and exit", + Type = LaunchOptionType.Bool, + Options = { "--install-only" } + }, + LaunchOptionDefinition.Extras + }; public override Task GetLatestVersion() => Task.FromResult("main"); - public override async Task InstallPackage(string installLocation, TorchVersion torchVersion, - IProgress? progress = null) + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null + ) { await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false); @@ -147,24 +149,30 @@ public class VoltaML : BaseGitPackage await venvRunner.Setup(true).ConfigureAwait(false); // Install requirements - progress?.Report(new ProgressReport(-1, "Installing Package Requirements", - isIndeterminate: true)); + progress?.Report( + new ProgressReport(-1, "Installing Package Requirements", isIndeterminate: true) + ); await venvRunner .PipInstall("rich packaging python-dotenv", OnConsoleOutput) .ConfigureAwait(false); - progress?.Report(new ProgressReport(1, "Installing Package Requirements", - isIndeterminate: false)); + progress?.Report( + new ProgressReport(1, "Installing Package Requirements", isIndeterminate: false) + ); } - public override async Task RunPackage(string installedPackagePath, string command, string arguments) + public override async Task RunPackage( + string installedPackagePath, + string command, + string arguments + ) { await SetupVenv(installedPackagePath).ConfigureAwait(false); var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}"; var foundIndicator = false; - + void HandleConsoleOutput(ProcessOutput s) { OnConsoleOutput(s); @@ -178,17 +186,17 @@ public class VoltaML : BaseGitPackage if (!foundIndicator) return; - + var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)"); var match = regex.Match(s.Text); if (!match.Success) return; - + WebUrl = match.Value; OnStartupComplete(WebUrl); foundIndicator = false; } - + VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit); } } diff --git a/StabilityMatrix.Core/Services/IModelIndexService.cs b/StabilityMatrix.Core/Services/IModelIndexService.cs index ceb6784f..2c875271 100644 --- a/StabilityMatrix.Core/Services/IModelIndexService.cs +++ b/StabilityMatrix.Core/Services/IModelIndexService.cs @@ -14,7 +14,7 @@ public interface IModelIndexService /// Get all models of the specified type from the existing index. /// Task> GetModelsOfType(SharedFolderType type); - + /// /// Starts a background task to refresh the local model file index. /// diff --git a/StabilityMatrix.Core/Services/ISettingsManager.cs b/StabilityMatrix.Core/Services/ISettingsManager.cs index f124dcdd..aec86465 100644 --- a/StabilityMatrix.Core/Services/ISettingsManager.cs +++ b/StabilityMatrix.Core/Services/ISettingsManager.cs @@ -23,7 +23,7 @@ public interface ISettingsManager /// Will fire instantly if it is already set. /// void RegisterOnLibraryDirSet(Action handler); - + /// SettingsTransaction BeginTransaction(); @@ -35,14 +35,17 @@ public interface ISettingsManager /// void RelayPropertyFor( - T source, + T source, Expression> sourceProperty, - Expression> settingsProperty) where T : INotifyPropertyChanged; + Expression> settingsProperty + ) + where T : INotifyPropertyChanged; /// void RegisterPropertyChangedHandler( Expression> settingsProperty, - Action onPropertyChanged); + Action onPropertyChanged + ); /// /// Attempts to locate and set the library path diff --git a/StabilityMatrix.Core/Services/ModelIndexService.cs b/StabilityMatrix.Core/Services/ModelIndexService.cs index e3680812..129d2165 100644 --- a/StabilityMatrix.Core/Services/ModelIndexService.cs +++ b/StabilityMatrix.Core/Services/ModelIndexService.cs @@ -38,9 +38,10 @@ public class ModelIndexService : IModelIndexService return await liteDbContext.LocalModelFiles .Query() .Where(m => m.SharedFolderType == type) - .ToArrayAsync().ConfigureAwait(false); + .ToArrayAsync() + .ConfigureAwait(false); } - + /// public async Task RefreshIndex() { @@ -49,19 +50,18 @@ public class ModelIndexService : IModelIndexService // Start var stopwatch = Stopwatch.StartNew(); logger.LogInformation("Refreshing model index..."); - - using var db - = await liteDbContext.Database.BeginTransactionAsync().ConfigureAwait(false); - + + using var db = await liteDbContext.Database.BeginTransactionAsync().ConfigureAwait(false); + var localModelFiles = db.GetCollection("LocalModelFiles")!; await localModelFiles.DeleteAllAsync().ConfigureAwait(false); // Record start of actual indexing var indexStart = stopwatch.Elapsed; - + var added = 0; - + foreach ( var file in modelsDir.Info .EnumerateFiles("*.*", SearchOption.AllDirectories) @@ -73,61 +73,74 @@ public class ModelIndexService : IModelIndexService { continue; } - + var relativePath = Path.GetRelativePath(modelsDir, file); - + // Get shared folder name - var sharedFolderName = relativePath.Split(Path.DirectorySeparatorChar, - StringSplitOptions.RemoveEmptyEntries)[0]; + var sharedFolderName = relativePath.Split( + Path.DirectorySeparatorChar, + StringSplitOptions.RemoveEmptyEntries + )[0]; // Convert to enum var sharedFolderType = Enum.Parse(sharedFolderName, true); - + var localModel = new LocalModelFile { RelativePath = relativePath, SharedFolderType = sharedFolderType, }; - + // Try to find a connected model info var jsonPath = file.Directory!.JoinFile( - new FilePath(file.NameWithoutExtension, ".cm-info.json")); + new FilePath(file.NameWithoutExtension, ".cm-info.json") + ); if (jsonPath.Exists) { var connectedModelInfo = ConnectedModelInfo.FromJson( - await jsonPath.ReadAllTextAsync().ConfigureAwait(false)); - + await jsonPath.ReadAllTextAsync().ConfigureAwait(false) + ); + localModel.ConnectedModelInfo = connectedModelInfo; } - + // Try to find a preview image var previewImagePath = LocalModelFile.SupportedImageExtensions - .Select(ext => file.Directory!.JoinFile($"{file.NameWithoutExtension}.preview{ext}") - ).FirstOrDefault(path => path.Exists); + .Select( + ext => file.Directory!.JoinFile($"{file.NameWithoutExtension}.preview{ext}") + ) + .FirstOrDefault(path => path.Exists); if (previewImagePath != null) { - localModel.PreviewImageRelativePath = Path.GetRelativePath(modelsDir, previewImagePath); + localModel.PreviewImageRelativePath = Path.GetRelativePath( + modelsDir, + previewImagePath + ); } - + // Insert into database await localModelFiles.InsertAsync(localModel).ConfigureAwait(false); - + added++; } - + // Record end of actual indexing var indexEnd = stopwatch.Elapsed; - + await db.CommitAsync().ConfigureAwait(false); - + // End stopwatch.Stop(); var indexDuration = indexEnd - indexStart; var dbDuration = stopwatch.Elapsed - indexDuration; - - logger.LogInformation("Model index refreshed with {Entries} entries, took {IndexDuration:F1}ms ({DbDuration:F1}ms db)", - added, indexDuration.TotalMilliseconds, dbDuration.TotalMilliseconds); + + logger.LogInformation( + "Model index refreshed with {Entries} entries, took {IndexDuration:F1}ms ({DbDuration:F1}ms db)", + added, + indexDuration.TotalMilliseconds, + dbDuration.TotalMilliseconds + ); } /// diff --git a/StabilityMatrix.Core/Services/SettingsManager.cs b/StabilityMatrix.Core/Services/SettingsManager.cs index c0c9c364..076f1034 100644 --- a/StabilityMatrix.Core/Services/SettingsManager.cs +++ b/StabilityMatrix.Core/Services/SettingsManager.cs @@ -20,10 +20,16 @@ public class SettingsManager : ISettingsManager private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly ReaderWriterLockSlim FileLock = new(); - private static readonly string GlobalSettingsPath = Path.Combine(Compat.AppDataHome, "global.json"); - - private readonly string? originalEnvPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process); - + private static readonly string GlobalSettingsPath = Path.Combine( + Compat.AppDataHome, + "global.json" + ); + + private readonly string? originalEnvPath = Environment.GetEnvironmentVariable( + "PATH", + EnvironmentVariableTarget.Process + ); + // Library properties public bool IsPortableMode { get; private set; } private string? libraryDir; @@ -50,10 +56,10 @@ public class SettingsManager : ISettingsManager private string SettingsPath => Path.Combine(LibraryDir, "settings.json"); public string ModelsDirectory => Path.Combine(LibraryDir, "Models"); public string DownloadsDirectory => Path.Combine(LibraryDir, ".downloads"); - + public Settings Settings { get; private set; } = new(); - - public event EventHandler? LibraryDirChanged; + + public event EventHandler? LibraryDirChanged; public event EventHandler? SettingsPropertyChanged; /// @@ -66,7 +72,7 @@ public class SettingsManager : ISettingsManager } LibraryDirChanged += Handler; - + return; void Handler(object? sender, string dir) @@ -75,17 +81,19 @@ public class SettingsManager : ISettingsManager handler(dir); } } - + /// public SettingsTransaction BeginTransaction() { if (!IsLibraryDirSet) { - throw new InvalidOperationException("LibraryDir not set when BeginTransaction was called"); + throw new InvalidOperationException( + "LibraryDir not set when BeginTransaction was called" + ); } return new SettingsTransaction(this, SaveSettingsAsync); } - + /// public void Transaction(Action func, bool ignoreMissingLibraryDir = false) { @@ -102,97 +110,111 @@ public class SettingsManager : ISettingsManager func(transaction.Settings); transaction.Dispose(); } - + /// public void Transaction(Expression> expression, TValue value) { if (expression.Body is not MemberExpression memberExpression) { throw new ArgumentException( - $"Expression must be a member expression, not {expression.Body.NodeType}"); + $"Expression must be a member expression, not {expression.Body.NodeType}" + ); } var propertyInfo = memberExpression.Member as PropertyInfo; if (propertyInfo == null) { throw new ArgumentException( - $"Expression member must be a property, not {memberExpression.Member.MemberType}"); + $"Expression member must be a property, not {memberExpression.Member.MemberType}" + ); } - + var name = propertyInfo.Name; - + // Set value using var transaction = BeginTransaction(); propertyInfo.SetValue(transaction.Settings, value); - + // Invoke property changed event SettingsPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } /// public void RelayPropertyFor( - T source, + T source, Expression> sourceProperty, - Expression> settingsProperty) where T : INotifyPropertyChanged + Expression> settingsProperty + ) + where T : INotifyPropertyChanged { var sourceGetter = sourceProperty.Compile(); var (propertyName, assigner) = Expressions.GetAssigner(sourceProperty); var sourceSetter = assigner.Compile(); - + var settingsGetter = settingsProperty.Compile(); var (targetPropertyName, settingsAssigner) = Expressions.GetAssigner(settingsProperty); var settingsSetter = settingsAssigner.Compile(); - + var sourceTypeName = source.GetType().Name; - + // Update source when settings change SettingsPropertyChanged += (_, args) => { - if (args.PropertyName != propertyName) return; - + if (args.PropertyName != propertyName) + return; + Logger.Trace( - "[RelayPropertyFor] " + - "Settings.{TargetProperty:l} -> {SourceType:l}.{SourceProperty:l}", - targetPropertyName, sourceTypeName, propertyName); - + "[RelayPropertyFor] " + + "Settings.{TargetProperty:l} -> {SourceType:l}.{SourceProperty:l}", + targetPropertyName, + sourceTypeName, + propertyName + ); + sourceSetter(source, settingsGetter(Settings)); }; - + // Set and Save settings when source changes source.PropertyChanged += (_, args) => { - if (args.PropertyName != propertyName) return; - + if (args.PropertyName != propertyName) + return; + Logger.Trace( - "[RelayPropertyFor] " + - "{SourceType:l}.{SourceProperty:l} -> Settings.{TargetProperty:l}", - sourceTypeName, propertyName, targetPropertyName); - + "[RelayPropertyFor] " + + "{SourceType:l}.{SourceProperty:l} -> Settings.{TargetProperty:l}", + sourceTypeName, + propertyName, + targetPropertyName + ); + settingsSetter(Settings, sourceGetter(source)); SaveSettingsAsync().SafeFireAndForget(); - + // Invoke property changed event SettingsPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }; } - + /// public void RegisterPropertyChangedHandler( Expression> settingsProperty, - Action onPropertyChanged) + Action onPropertyChanged + ) { var settingsGetter = settingsProperty.Compile(); var (propertyName, _) = Expressions.GetAssigner(settingsProperty); - + // Invoke handler when settings change SettingsPropertyChanged += (_, args) => { - if (args.PropertyName != propertyName) return; - + if (args.PropertyName != propertyName) + return; + onPropertyChanged(settingsGetter(Settings)); }; } - + /// /// Attempts to locate and set the library path /// Return true if found, false otherwise @@ -209,18 +231,21 @@ public class SettingsManager : ISettingsManager LoadSettings(); return true; } - + // 2. Check %APPDATA%/StabilityMatrix/library.json FilePath libraryJsonFile = Compat.AppDataHome + "library.json"; - if (!libraryJsonFile.Exists) return false; - + if (!libraryJsonFile.Exists) + return false; + try { var libraryJson = libraryJsonFile.ReadAllText(); var librarySettings = JsonSerializer.Deserialize(libraryJson); - - if (!string.IsNullOrWhiteSpace(librarySettings?.LibraryPath) - && Directory.Exists(librarySettings?.LibraryPath)) + + if ( + !string.IsNullOrWhiteSpace(librarySettings?.LibraryPath) + && Directory.Exists(librarySettings?.LibraryPath) + ) { LibraryDir = librarySettings.LibraryPath; SetStaticLibraryPaths(); @@ -252,13 +277,16 @@ public class SettingsManager : ISettingsManager var libraryJsonFile = Compat.AppDataHome.JoinFile("library.json"); var library = new LibrarySettings { LibraryPath = path }; - var libraryJson = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true }); + var libraryJson = JsonSerializer.Serialize( + library, + new JsonSerializerOptions { WriteIndented = true } + ); libraryJsonFile.WriteAllText(libraryJson); - + // actually create the LibraryPath directory Directory.CreateDirectory(path); - } - + } + /// /// Enable and create settings files for portable mode /// Creates the ./Data directory and the `.sm-portable` marker file @@ -280,18 +308,21 @@ public class SettingsManager : ISettingsManager /// public IEnumerable GetOldInstalledPackages() { - var oldSettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "StabilityMatrix", "settings.json"); + var oldSettingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "StabilityMatrix", + "settings.json" + ); if (!File.Exists(oldSettingsPath)) yield break; - + var oldSettingsJson = File.ReadAllText(oldSettingsPath); - var oldSettings = JsonSerializer.Deserialize(oldSettingsJson, new JsonSerializerOptions - { - Converters = { new JsonStringEnumConverter() } - }); - + var oldSettings = JsonSerializer.Deserialize( + oldSettingsJson, + new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } + ); + // Absolute paths are old formats requiring migration #pragma warning disable CS0618 var oldPackages = oldSettings?.InstalledPackages.Where(package => package.Path != null); @@ -299,7 +330,7 @@ public class SettingsManager : ISettingsManager if (oldPackages == null) yield break; - + foreach (var package in oldPackages) { yield return package; @@ -308,24 +339,27 @@ public class SettingsManager : ISettingsManager public Guid GetOldActivePackageId() { - var oldSettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "StabilityMatrix", "settings.json"); + var oldSettingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "StabilityMatrix", + "settings.json" + ); if (!File.Exists(oldSettingsPath)) return default; - + var oldSettingsJson = File.ReadAllText(oldSettingsPath); - var oldSettings = JsonSerializer.Deserialize(oldSettingsJson, new JsonSerializerOptions - { - Converters = { new JsonStringEnumConverter() } - }); + var oldSettings = JsonSerializer.Deserialize( + oldSettingsJson, + new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } + ); if (oldSettings == null) return default; - + return oldSettings.ActiveInstalledPackageId ?? default; } - + public void AddPathExtension(string pathExtension) { Settings.PathExtensions ??= new List(); @@ -337,13 +371,14 @@ public class SettingsManager : ISettingsManager { return string.Join(";", Settings.PathExtensions ?? new List()); } - + /// /// Insert path extensions to the front of the PATH environment variable /// public void InsertPathExtensions() { - if (Settings.PathExtensions == null) return; + if (Settings.PathExtensions == null) + return; var toInsert = GetPathExtensionsAsString(); // Append the original path, if any if (originalEnvPath != null) @@ -364,21 +399,23 @@ public class SettingsManager : ISettingsManager package.Version = newVersion; SaveSettings(); } - + public void SetLastUpdateCheck(InstalledPackage package) { - var installedPackage = Settings.InstalledPackages.First(p => p.DisplayName == package.DisplayName); + var installedPackage = Settings.InstalledPackages.First( + p => p.DisplayName == package.DisplayName + ); installedPackage.LastUpdateCheck = package.LastUpdateCheck; installedPackage.UpdateAvailable = package.UpdateAvailable; SaveSettings(); } - + public List GetLaunchArgs(Guid packageId) { var packageData = Settings.InstalledPackages.FirstOrDefault(x => x.Id == packageId); return packageData?.LaunchArgs ?? new(); } - + public void SaveLaunchArgs(Guid packageId, List launchArgs) { var packageData = Settings.InstalledPackages.FirstOrDefault(x => x.Id == packageId); @@ -392,12 +429,17 @@ public class SettingsManager : ISettingsManager packageData.LaunchArgs = toSave; SaveSettings(); } - + public string? GetActivePackageHost() { - var package = Settings.InstalledPackages.FirstOrDefault(x => x.Id == Settings.ActiveInstalledPackageId); - if (package == null) return null; - var hostOption = package.LaunchArgs?.FirstOrDefault(x => x.Name.ToLowerInvariant() == "host"); + var package = Settings.InstalledPackages.FirstOrDefault( + x => x.Id == Settings.ActiveInstalledPackageId + ); + if (package == null) + return null; + var hostOption = package.LaunchArgs?.FirstOrDefault( + x => x.Name.ToLowerInvariant() == "host" + ); if (hostOption?.OptionValue != null) { return hostOption.OptionValue as string; @@ -407,9 +449,14 @@ public class SettingsManager : ISettingsManager public string? GetActivePackagePort() { - var package = Settings.InstalledPackages.FirstOrDefault(x => x.Id == Settings.ActiveInstalledPackageId); - if (package == null) return null; - var portOption = package.LaunchArgs?.FirstOrDefault(x => x.Name.ToLowerInvariant() == "port"); + var package = Settings.InstalledPackages.FirstOrDefault( + x => x.Id == Settings.ActiveInstalledPackageId + ); + if (package == null) + return null; + var portOption = package.LaunchArgs?.FirstOrDefault( + x => x.Name.ToLowerInvariant() == "port" + ); if (portOption?.OptionValue != null) { return portOption.OptionValue as string; @@ -430,11 +477,12 @@ public class SettingsManager : ISettingsManager } SaveSettings(); } - + public bool IsSharedFolderCategoryVisible(SharedFolderType type) { // False for default - if (type == 0) return false; + if (type == 0) + return false; return Settings.SharedFolderVisibleCategories?.HasFlag(type) ?? false; } @@ -455,7 +503,7 @@ public class SettingsManager : ISettingsManager public void SetEulaAccepted() { - var globalSettings = new GlobalSettings {EulaAccepted = true}; + var globalSettings = new GlobalSettings { EulaAccepted = true }; var json = JsonSerializer.Serialize(globalSettings); File.WriteAllText(GlobalSettingsPath, json); } @@ -471,11 +519,15 @@ public class SettingsManager : ISettingsManager var modelHashes = new HashSet(); var sharedModelDirectory = Path.Combine(LibraryDir, "Models"); - - if (!Directory.Exists(sharedModelDirectory)) return; - - var connectedModelJsons = Directory.GetFiles(sharedModelDirectory, "*.cm-info.json", - SearchOption.AllDirectories); + + if (!Directory.Exists(sharedModelDirectory)) + return; + + var connectedModelJsons = Directory.GetFiles( + sharedModelDirectory, + "*.cm-info.json", + SearchOption.AllDirectories + ); foreach (var jsonFile in connectedModelJsons) { var json = File.ReadAllText(jsonFile); @@ -488,7 +540,7 @@ public class SettingsManager : ISettingsManager } Transaction(s => s.InstalledModelHashes = modelHashes); - + sw.Stop(); Logger.Info($"Indexed {modelHashes.Count} checkpoints in {sw.ElapsedMilliseconds}ms"); } @@ -515,9 +567,10 @@ public class SettingsManager : ISettingsManager var modifiedDefaultSerializerOptions = SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions(); modifiedDefaultSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - Settings = - JsonSerializer.Deserialize(settingsContent, - modifiedDefaultSerializerOptions)!; + Settings = JsonSerializer.Deserialize( + settingsContent, + modifiedDefaultSerializerOptions + )!; } finally { @@ -534,12 +587,15 @@ public class SettingsManager : ISettingsManager { File.Create(SettingsPath).Close(); } - - var json = JsonSerializer.Serialize(Settings, new JsonSerializerOptions - { - WriteIndented = true, - Converters = { new JsonStringEnumConverter() } - }); + + var json = JsonSerializer.Serialize( + Settings, + new JsonSerializerOptions + { + WriteIndented = true, + Converters = { new JsonStringEnumConverter() } + } + ); File.WriteAllText(SettingsPath, json); } finally @@ -553,4 +609,3 @@ public class SettingsManager : ISettingsManager return Task.Run(SaveSettings); } } - diff --git a/StabilityMatrix.Core/Services/TrackedDownloadService.cs b/StabilityMatrix.Core/Services/TrackedDownloadService.cs index cde538e9..780b367f 100644 --- a/StabilityMatrix.Core/Services/TrackedDownloadService.cs +++ b/StabilityMatrix.Core/Services/TrackedDownloadService.cs @@ -14,18 +14,22 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable private readonly ILogger logger; private readonly IDownloadService downloadService; private readonly ISettingsManager settingsManager; - - private readonly ConcurrentDictionary downloads = new(); + + private readonly ConcurrentDictionary< + Guid, + (TrackedDownload Download, FileStream Stream) + > downloads = new(); public IEnumerable Downloads => downloads.Values.Select(x => x.Download); - + /// public event EventHandler? DownloadAdded; - + public TrackedDownloadService( ILogger logger, IDownloadService downloadService, - ISettingsManager settingsManager) + ISettingsManager settingsManager + ) { this.logger = logger; this.downloadService = downloadService; @@ -36,12 +40,13 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable { var downloadsDir = new DirectoryPath(settingsManager.DownloadsDirectory); // Ignore if not exist - if (!downloadsDir.Exists) return; - + if (!downloadsDir.Exists) + return; + LoadInProgressDownloads(downloadsDir); }); } - + private void OnDownloadAdded(TrackedDownload download) { DownloadAdded?.Invoke(this, download); @@ -55,28 +60,32 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable { // Set download service download.SetDownloadService(downloadService); - + // Create json file var downloadsDir = new DirectoryPath(settingsManager.DownloadsDirectory); downloadsDir.Create(); var jsonFile = downloadsDir.JoinFile($"{download.Id}.json"); - var jsonFileStream = jsonFile.Info.Open(FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read); - + var jsonFileStream = jsonFile.Info.Open( + FileMode.CreateNew, + FileAccess.ReadWrite, + FileShare.Read + ); + // Serialize to json var json = JsonSerializer.Serialize(download); jsonFileStream.Write(Encoding.UTF8.GetBytes(json)); jsonFileStream.Flush(); - + // Add to dictionary downloads.TryAdd(download.Id, (download, jsonFileStream)); - + // Connect to state changed event to update json file AttachHandlers(download); - + logger.LogDebug("Added download {Download}", download.FileName); OnDownloadAdded(download); } - + /// /// Update the json file for the download. /// @@ -85,19 +94,19 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable // Serialize to json var json = JsonSerializer.Serialize(download); var jsonBytes = Encoding.UTF8.GetBytes(json); - + // Write to file var (_, fs) = downloads[download.Id]; fs.Seek(0, SeekOrigin.Begin); fs.Write(jsonBytes); fs.Flush(); } - + private void AttachHandlers(TrackedDownload download) { download.ProgressStateChanged += TrackedDownload_OnProgressStateChanged; } - + /// /// Handler when the download's state changes /// @@ -107,10 +116,10 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable { return; } - + // Update json file UpdateJsonForDownload(download); - + // If the download is completed, remove it from the dictionary and delete the json file if (e is ProgressState.Success or ProgressState.Failed or ProgressState.Cancelled) { @@ -118,11 +127,13 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable { downloadInfo.Item2.Dispose(); // Delete json file - new DirectoryPath(settingsManager.DownloadsDirectory).JoinFile($"{download.Id}.json").Delete(); + new DirectoryPath(settingsManager.DownloadsDirectory) + .JoinFile($"{download.Id}.json") + .Delete(); logger.LogDebug("Removed download {Download}", download.FileName); } } - + // On successes, run the continuation action if (e == ProgressState.Success) { @@ -133,13 +144,13 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable } } } - + private void LoadInProgressDownloads(DirectoryPath downloadsDir) { logger.LogDebug("Indexing in-progress downloads at {DownloadsDir}...", downloadsDir); - + var jsonFiles = downloadsDir.Info.EnumerateFiles("*.json", SearchOption.TopDirectoryOnly); - + // Add to dictionary, the file name is the guid foreach (var file in jsonFiles) { @@ -147,10 +158,10 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable try { var fileStream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.Read); - + // Deserialize json and add to dictionary var download = JsonSerializer.Deserialize(fileStream)!; - + // If the download is marked as working, pause it if (download.ProgressState == ProgressState.Working) { @@ -159,23 +170,30 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable else if (download.ProgressState != ProgressState.Inactive) { // If the download is not inactive, skip it - logger.LogWarning("Skipping download {Download} with state {State}", download.FileName, download.ProgressState); + logger.LogWarning( + "Skipping download {Download} with state {State}", + download.FileName, + download.ProgressState + ); fileStream.Dispose(); - + // Delete json file - logger.LogDebug("Deleting json file for {Download} with unsupported state", download.FileName); + logger.LogDebug( + "Deleting json file for {Download} with unsupported state", + download.FileName + ); file.Delete(); continue; } - + download.SetDownloadService(downloadService); - + downloads.TryAdd(download.Id, (download, fileStream)); - + AttachHandlers(download); - + OnDownloadAdded(download); - + logger.LogDebug("Loaded in-progress download {Download}", download.FileName); } catch (Exception e) @@ -197,10 +215,10 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable }; AddDownload(download); - + return download; } - + /// /// Generate a new temp file name that is unique in the given directory. /// In format of "Unconfirmed {id}.smdownload" @@ -213,14 +231,14 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable for (var i = 0; i < 10; i++) { - if (tempFile is {Exists: false}) + if (tempFile is { Exists: false }) { return tempFile.Name; } var id = Random.Shared.Next(1000000, 9999999); tempFile = parentDir.JoinFile($"Unconfirmed {id}.smdownload"); } - + throw new Exception("Failed to generate a unique temp file name."); } @@ -241,7 +259,7 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable } } } - + GC.SuppressFinalize(this); } }