diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index bb38fcec..9f40327a 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -1,3 +1,7 @@ +#if DEBUG +using StabilityMatrix.Avalonia.Diagnostics.LogViewer; +using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions; +#endif using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -33,8 +37,6 @@ using Refit; using Sentry; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.DesignData; -using StabilityMatrix.Avalonia.Diagnostics.LogViewer; -using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions; using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Models; @@ -68,12 +70,19 @@ namespace StabilityMatrix.Avalonia; public sealed class App : Application { - [NotNull] public static IServiceProvider? Services { get; private set; } - [NotNull] public static Visual? VisualRoot { get; private set; } - [NotNull] public static IStorageProvider? StorageProvider { get; private set; } + [NotNull] + public static IServiceProvider? Services { get; private set; } + + [NotNull] + public static Visual? VisualRoot { get; private set; } + + [NotNull] + public static IStorageProvider? StorageProvider { get; private set; } + // ReSharper disable once MemberCanBePrivate.Global - [NotNull] public static IConfiguration? Config { get; private set; } - + [NotNull] + public static IConfiguration? Config { get; private set; } + // ReSharper disable once MemberCanBePrivate.Global public IClassicDesktopStyleApplicationLifetime? DesktopLifetime => ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; @@ -88,11 +97,11 @@ public sealed class App : Application RequestedThemeVariant = ThemeVariant.Dark; } } - + public override void OnFrameworkInitializationCompleted() { base.OnFrameworkInitializationCompleted(); - + if (Design.IsDesignMode) { DesignData.DesignData.Initialize(); @@ -117,9 +126,10 @@ public sealed class App : Application setupWindow.ShowAsDialog = true; setupWindow.ShowActivated = true; setupWindow.ShowAsyncCts = new CancellationTokenSource(); - - setupWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects ? - ExtendClientAreaChromeHints.NoChrome : ExtendClientAreaChromeHints.PreferSystemChrome; + + setupWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects + ? ExtendClientAreaChromeHints.NoChrome + : ExtendClientAreaChromeHints.PreferSystemChrome; DesktopLifetime.MainWindow = setupWindow; @@ -146,47 +156,55 @@ public sealed class App : Application private void ShowMainWindow() { - if (DesktopLifetime is null) return; - + if (DesktopLifetime is null) + return; + var mainViewModel = Services.GetRequiredService(); - + var mainWindow = Services.GetRequiredService(); mainWindow.DataContext = mainViewModel; - - mainWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects ? - ExtendClientAreaChromeHints.NoChrome : ExtendClientAreaChromeHints.PreferSystemChrome; - + + mainWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects + ? ExtendClientAreaChromeHints.NoChrome + : ExtendClientAreaChromeHints.PreferSystemChrome; + var settingsManager = Services.GetRequiredService(); var windowSettings = settingsManager.Settings.WindowSettings; if (windowSettings != null && !Program.Args.ResetWindowPosition) { mainWindow.Position = new PixelPoint(windowSettings.X, windowSettings.Y); mainWindow.Width = windowSettings.Width; - mainWindow.Height = windowSettings.Height; + mainWindow.Height = windowSettings.Height; } else { mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen; } - + mainWindow.Closing += (_, _) => { - var validWindowPosition = - mainWindow.Screens.All.Any(screen => screen.Bounds.Contains(mainWindow.Position)); + var validWindowPosition = mainWindow.Screens.All.Any( + screen => screen.Bounds.Contains(mainWindow.Position) + ); - settingsManager.Transaction(s => - { - s.WindowSettings = new WindowSettings( - mainWindow.Width, mainWindow.Height, - validWindowPosition ? mainWindow.Position.X : 0, - validWindowPosition ? mainWindow.Position.Y : 0); - }, ignoreMissingLibraryDir: true); + settingsManager.Transaction( + s => + { + s.WindowSettings = new WindowSettings( + mainWindow.Width, + mainWindow.Height, + validWindowPosition ? mainWindow.Position.X : 0, + validWindowPosition ? mainWindow.Position.Y : 0 + ); + }, + ignoreMissingLibraryDir: true + ); }; mainWindow.Closed += (_, _) => Shutdown(); VisualRoot = mainWindow; StorageProvider = mainWindow.StorageProvider; - + DesktopLifetime.MainWindow = mainWindow; DesktopLifetime.Exit += OnExit; } @@ -195,49 +213,50 @@ public sealed class App : Application { var services = ConfigureServices(); Services = services.BuildServiceProvider(); - + var settingsManager = Services.GetRequiredService(); - + if (settingsManager.TryFindLibrary()) { Cultures.TrySetSupportedCulture(settingsManager.Settings.Language); } - + Services.GetRequiredService().StartEventListener(); } internal static void ConfigurePageViewModels(IServiceCollection services) { - services.AddSingleton() + services + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); - - services.AddSingleton(provider => - new MainWindowViewModel(provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService>(), - provider.GetRequiredService()) - { - Pages = - { - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), - }, - FooterPages = + + services.AddSingleton( + provider => + new MainWindowViewModel( + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService>(), + provider.GetRequiredService() + ) { - provider.GetRequiredService() + Pages = + { + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + }, + FooterPages = { provider.GetRequiredService() } } - }); - + ); + // Register disposable view models for shutdown cleanup - services.AddSingleton(p - => p.GetRequiredService()); + services.AddSingleton(p => p.GetRequiredService()); } internal static void ConfigureDialogViewModels(IServiceCollection services) @@ -251,43 +270,44 @@ public sealed class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); - + // Dialog view models (singleton) services.AddSingleton(); services.AddSingleton(); - + // Other transients (usually sub view models) services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); - + // Global progress services.AddSingleton(); - + // Controls services.AddTransient(); - + // Dialog factory - services.AddSingleton>(provider => - new ServiceManager() - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService) - ); + services.AddSingleton>( + provider => + new ServiceManager() + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + ); } internal static void ConfigureViews(IServiceCollection services) @@ -300,7 +320,7 @@ public sealed class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - + // Dialogs services.AddTransient(); services.AddTransient(); @@ -308,15 +328,15 @@ public sealed class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); - + // Controls services.AddTransient(); - + // Windows services.AddSingleton(); services.AddSingleton(); } - + internal static void ConfigurePackages(IServiceCollection services) { services.AddSingleton(); @@ -336,7 +356,7 @@ public sealed class App : Application ConfigurePageViewModels(services); ConfigureDialogViewModels(services); ConfigurePackages(services); - + // Other services services.AddSingleton(); services.AddSingleton(); @@ -350,23 +370,25 @@ public sealed class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - + services.AddSingleton(); - services.AddSingleton(provider => - (IDisposable) provider.GetRequiredService()); - + services.AddSingleton( + provider => (IDisposable)provider.GetRequiredService() + ); + // Rich presence services.AddSingleton(); - services.AddSingleton(provider => - provider.GetRequiredService()); + services.AddSingleton( + provider => provider.GetRequiredService() + ); Config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .Build(); - + services.Configure(Config.GetSection(nameof(DebugOptions))); - + if (Compat.IsWindows) { services.AddSingleton(); @@ -408,13 +430,13 @@ public sealed class App : Application jsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter()); jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter()); jsonSerializerOptions.Converters.Add( - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + ); jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; var defaultRefitSettings = new RefitSettings { - ContentSerializer = - new SystemTextJsonContentSerializer(jsonSerializerOptions) + ContentSerializer = new SystemTextJsonContentSerializer(jsonSerializerOptions) }; // HTTP Policies @@ -426,9 +448,10 @@ public sealed class App : Application HttpStatusCode.ServiceUnavailable, // 503 HttpStatusCode.GatewayTimeout // 504 }; - var delay = Backoff - .DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(80), - retryCount: 5); + var delay = Backoff.DecorrelatedJitterBackoffV2( + medianFirstRetryDelay: TimeSpan.FromMilliseconds(80), + retryCount: 5 + ); var retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() .Or() @@ -437,25 +460,29 @@ public sealed class App : Application // Shorter timeout for local requests var localTimeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(3)); - var localDelay = Backoff - .DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(50), - retryCount: 3); + var localDelay = Backoff.DecorrelatedJitterBackoffV2( + medianFirstRetryDelay: TimeSpan.FromMilliseconds(50), + retryCount: 3 + ); var localRetryPolicy = HttpPolicyExtensions .HandleTransientHttpError() .Or() .OrResult(r => retryStatusCodes.Contains(r.StatusCode)) - .WaitAndRetryAsync(localDelay, onRetryAsync: (_, _) => - { - Debug.WriteLine("Retrying local request..."); - return Task.CompletedTask; - }); + .WaitAndRetryAsync( + localDelay, + onRetryAsync: (_, _) => + { + Debug.WriteLine("Retrying local request..."); + return Task.CompletedTask; + } + ); // named client for update - services.AddHttpClient("UpdateClient") - .AddPolicyHandler(retryPolicy); + services.AddHttpClient("UpdateClient").AddPolicyHandler(retryPolicy); // Add Refit clients - services.AddRefitClient(defaultRefitSettings) + services + .AddRefitClient(defaultRefitSettings) .ConfigureHttpClient(c => { c.BaseAddress = new Uri("https://civitai.com"); @@ -464,27 +491,31 @@ public sealed class App : Application .AddPolicyHandler(retryPolicy); // Add Refit client managers - services.AddHttpClient("A3Client") + services + .AddHttpClient("A3Client") .AddPolicyHandler(localTimeout.WrapAsync(localRetryPolicy)); ConditionalAddLogViewer(services); - + // Add logging services.AddLogging(builder => { builder.ClearProviders(); - builder.AddFilter("Microsoft.Extensions.Http", LogLevel.Warning) + builder + .AddFilter("Microsoft.Extensions.Http", LogLevel.Warning) .AddFilter("Microsoft.Extensions.Http.DefaultHttpClientFactory", LogLevel.Warning) .AddFilter("Microsoft", LogLevel.Warning) .AddFilter("System", LogLevel.Warning); builder.SetMinimumLevel(LogLevel.Debug); #if DEBUG - builder.AddNLog(ConfigureLogging(), + builder.AddNLog( + ConfigureLogging(), new NLogProviderOptions { IgnoreEmptyEventId = false, CaptureEventId = EventIdCaptureType.Legacy - }); + } + ); #else builder.AddNLog(ConfigureLogging()); #endif @@ -501,8 +532,8 @@ public sealed class App : Application /// If Application.Current is null public static void Shutdown(int exitCode = 0) { - if (Current is null) throw new NullReferenceException( - "Current Application was null when Shutdown called"); + if (Current is null) + throw new NullReferenceException("Current Application was null when Shutdown called"); if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) { lifetime.Shutdown(exitCode); @@ -516,11 +547,9 @@ public sealed class App : Application var settingsManager = Services.GetRequiredService(); // If RemoveFolderLinksOnShutdown is set, delete all package junctions - if (settingsManager is - { - IsLibraryDirSet: true, - Settings.RemoveFolderLinksOnShutdown: true - }) + if ( + settingsManager is { IsLibraryDirSet: true, Settings.RemoveFolderLinksOnShutdown: true } + ) { var sharedFolders = Services.GetRequiredService(); sharedFolders.RemoveLinksForAllPackages(); @@ -542,40 +571,48 @@ public sealed class App : Application var setupBuilder = LogManager.Setup(); ConditionalAddLogViewerNLog(setupBuilder); - - setupBuilder.LoadConfiguration(builder => { - var debugTarget = builder.ForTarget("console").WriteTo(new DebuggerTarget - { - Layout = "${message}" - }).WithAsync(); - - var fileTarget = builder.ForTarget("logfile").WriteTo(new FileTarget - { - Layout = "${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}", - ArchiveOldFileOnStartup = true, - FileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.log", - ArchiveFileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.{#}.log", - ArchiveNumbering = ArchiveNumberingMode.Rolling, - MaxArchiveFiles = 2 - }).WithAsync(); - + + setupBuilder.LoadConfiguration(builder => + { + var debugTarget = builder + .ForTarget("console") + .WriteTo(new DebuggerTarget { Layout = "${message}" }) + .WithAsync(); + + var fileTarget = builder + .ForTarget("logfile") + .WriteTo( + new FileTarget + { + Layout = + "${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}", + ArchiveOldFileOnStartup = true, + FileName = + "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.log", + ArchiveFileName = + "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.{#}.log", + ArchiveNumbering = ArchiveNumberingMode.Rolling, + MaxArchiveFiles = 2 + } + ) + .WithAsync(); + // Filter some sources to be warn levels or above only builder.ForLogger("System.*").WriteToNil(NLog.LogLevel.Warn); builder.ForLogger("Microsoft.*").WriteToNil(NLog.LogLevel.Warn); builder.ForLogger("Microsoft.Extensions.Http.*").WriteToNil(NLog.LogLevel.Warn); - + builder.ForLogger().FilterMinLevel(NLog.LogLevel.Trace).WriteTo(debugTarget); builder.ForLogger().FilterMinLevel(NLog.LogLevel.Debug).WriteTo(fileTarget); #if DEBUG - var logViewerTarget = builder.ForTarget("DataStoreLogger").WriteTo(new DataStoreLoggerTarget() - { - Layout = "${message}" - }); + var logViewerTarget = builder + .ForTarget("DataStoreLogger") + .WriteTo(new DataStoreLoggerTarget() { Layout = "${message}" }); builder.ForLogger().FilterMinLevel(NLog.LogLevel.Trace).WriteTo(logViewerTarget); #endif }); - + // Sentry if (SentrySdk.IsEnabled) { @@ -594,20 +631,26 @@ public sealed class App : Application } LogManager.ReconfigExistingLoggers(); - + return LogManager.Configuration; } - + [Conditional("DEBUG")] private static void ConditionalAddLogViewer(IServiceCollection services) { +#if DEBUG services.AddLogViewer(); +#endif } - + [Conditional("DEBUG")] private static void ConditionalAddLogViewerNLog(ISetupBuilder setupBuilder) { - setupBuilder.SetupExtensions(extensionBuilder => - extensionBuilder.RegisterTarget("DataStoreLogger")); +#if DEBUG + setupBuilder.SetupExtensions( + extensionBuilder => + extensionBuilder.RegisterTarget("DataStoreLogger") + ); +#endif } } diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs index 64b170e5..149bd99a 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs @@ -22,11 +22,13 @@ using FluentAvalonia.UI.Windowing; using Microsoft.Extensions.DependencyInjection; using StabilityMatrix.Avalonia.Animations; using StabilityMatrix.Avalonia.Controls; -using StabilityMatrix.Avalonia.Diagnostics.Views; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Processes; +#if DEBUG +using StabilityMatrix.Avalonia.Diagnostics.Views; +#endif namespace StabilityMatrix.Avalonia.Views; @@ -43,19 +45,21 @@ public partial class MainWindow : AppWindowBase notificationService = null!; navigationService = null!; } - - public MainWindow(INotificationService notificationService, INavigationService navigationService) + + public MainWindow( + INotificationService notificationService, + INavigationService navigationService + ) { this.notificationService = notificationService; this.navigationService = navigationService; - + InitializeComponent(); - + #if DEBUG this.AttachDevTools(); LogWindow.Attach(this, App.Services); #endif - TitleBar.ExtendsContentIntoTitleBar = true; TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; } @@ -64,15 +68,17 @@ public partial class MainWindow : AppWindowBase protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - - navigationService.SetFrame(FrameView ?? throw new NullReferenceException("Frame not found")); - + + navigationService.SetFrame( + FrameView ?? throw new NullReferenceException("Frame not found") + ); + // Navigate to first page if (DataContext is not MainWindowViewModel vm) { throw new NullReferenceException("DataContext is not MainWindowViewModel"); } - + navigationService.NavigateTo(vm.Pages[0], new DrillInNavigationTransitionInfo()); } @@ -81,7 +87,7 @@ public partial class MainWindow : AppWindowBase base.OnOpened(e); Application.Current!.ActualThemeVariantChanged += OnActualThemeVariantChanged; - + var theme = ActualThemeVariant; // Enable mica for Windows 11 if (IsWindows11 && theme != FluentAvaloniaTheme.HighContrastTheme) @@ -93,11 +99,10 @@ public partial class MainWindow : AppWindowBase protected override void OnClosing(WindowClosingEventArgs e) { // Show confirmation if package running - var launchPageViewModel = App.Services - .GetRequiredService(); + var launchPageViewModel = App.Services.GetRequiredService(); launchPageViewModel.OnMainWindowClosing(e); - + base.OnClosing(e); } @@ -106,7 +111,7 @@ public partial class MainWindow : AppWindowBase base.OnLoaded(e); // Initialize notification service using this window as the visual root notificationService.Initialize(this); - + // Attach error notification handler for image loader if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader) { @@ -117,7 +122,7 @@ public partial class MainWindow : AppWindowBase protected override void OnUnloaded(RoutedEventArgs e) { base.OnUnloaded(e); - + // Detach error notification handler for image loader if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader) { @@ -134,20 +139,22 @@ public partial class MainWindow : AppWindowBase { return; } - + if (nvi.Tag is null) { throw new InvalidOperationException("NavigationViewItem Tag is null"); } - + if (nvi.Tag is not ViewModelBase vm) { - throw new InvalidOperationException($"NavigationViewItem Tag must be of type ViewModelBase, not {nvi.Tag?.GetType()}"); + throw new InvalidOperationException( + $"NavigationViewItem Tag must be of type ViewModelBase, not {nvi.Tag?.GetType()}" + ); } navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition()); } } - + private void OnActualThemeVariantChanged(object? sender, EventArgs e) { if (IsWindows11) @@ -163,7 +170,7 @@ public partial class MainWindow : AppWindowBase } } } - + private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e) { Dispatcher.UIThread.Post(() => @@ -173,24 +180,30 @@ public partial class MainWindow : AppWindowBase notificationService.ShowPersistent( "Failed to load image", $"Could not load '{displayName}'\n({e.Exception.Message})", - NotificationType.Warning); + NotificationType.Warning + ); }); } - + private void TryEnableMicaEffect() { TransparencyBackgroundFallback = Brushes.Transparent; TransparencyLevelHint = new[] { - WindowTransparencyLevel.Mica, + WindowTransparencyLevel.Mica, WindowTransparencyLevel.AcrylicBlur, WindowTransparencyLevel.Blur }; - + if (ActualThemeVariant == ThemeVariant.Dark) { - var color = this.TryFindResource("SolidBackgroundFillColorBase", - ThemeVariant.Dark, out var value) ? (Color2)(Color)value! : new Color2(32, 32, 32); + var color = this.TryFindResource( + "SolidBackgroundFillColorBase", + ThemeVariant.Dark, + out var value + ) + ? (Color2)(Color)value! + : new Color2(32, 32, 32); color = color.LightenPercent(-0.8f); @@ -199,8 +212,13 @@ public partial class MainWindow : AppWindowBase else if (ActualThemeVariant == ThemeVariant.Light) { // Similar effect here - var color = this.TryFindResource("SolidBackgroundFillColorBase", - ThemeVariant.Light, out var value) ? (Color2)(Color)value! : new Color2(243, 243, 243); + var color = this.TryFindResource( + "SolidBackgroundFillColorBase", + ThemeVariant.Light, + out var value + ) + ? (Color2)(Color)value! + : new Color2(243, 243, 243); color = color.LightenPercent(0.5f);