#if DEBUG using StabilityMatrix.Avalonia.Diagnostics.LogViewer; using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions; #endif using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input.Platform; using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Styling; using Avalonia.Threading; using FluentAvalonia.UI.Controls; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NLog; using NLog.Config; using NLog.Extensions.Logging; using NLog.Targets; using Octokit; using Polly; using Polly.Contrib.WaitAndRetry; using Polly.Extensions.Http; using Polly.Timeout; using Refit; using Sentry; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.DesignData; using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Models.TagCompletion; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Avalonia.ViewModels.Inference; using StabilityMatrix.Avalonia.ViewModels.PackageManager; using StabilityMatrix.Avalonia.ViewModels.Progress; using StabilityMatrix.Avalonia.ViewModels.Settings; using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Avalonia.Views.Dialogs; using StabilityMatrix.Avalonia.Views.Inference; using StabilityMatrix.Avalonia.Views.Settings; using StabilityMatrix.Core.Api; using StabilityMatrix.Core.Converters.Json; using StabilityMatrix.Core.Database; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.Factory; using StabilityMatrix.Core.Models.Api; using StabilityMatrix.Core.Models.Configs; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.Models.Settings; using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Updater; using Application = Avalonia.Application; using DrawingColor = System.Drawing.Color; using LogLevel = Microsoft.Extensions.Logging.LogLevel; 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 IClipboard? Clipboard { get; private set; } // ReSharper disable once MemberCanBePrivate.Global [NotNull] public static IConfiguration? Config { get; private set; } // ReSharper disable once MemberCanBePrivate.Global public IClassicDesktopStyleApplicationLifetime? DesktopLifetime => ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; public override void Initialize() { AvaloniaXamlLoader.Load(this); // Set design theme if (Design.IsDesignMode) { RequestedThemeVariant = ThemeVariant.Dark; } } public override void OnFrameworkInitializationCompleted() { base.OnFrameworkInitializationCompleted(); if (Design.IsDesignMode) { DesignData.DesignData.Initialize(); Services = DesignData.DesignData.Services; } else { ConfigureServiceProvider(); } if (DesktopLifetime is not null) { DesktopLifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown; // First time setup if needed var settingsManager = Services.GetRequiredService(); if (!settingsManager.IsEulaAccepted()) { var setupWindow = Services.GetRequiredService(); var setupViewModel = Services.GetRequiredService(); setupWindow.DataContext = setupViewModel; setupWindow.ShowAsDialog = true; setupWindow.ShowActivated = true; setupWindow.ShowAsyncCts = new CancellationTokenSource(); setupWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects ? ExtendClientAreaChromeHints.NoChrome : ExtendClientAreaChromeHints.PreferSystemChrome; DesktopLifetime.MainWindow = setupWindow; setupWindow.ShowAsyncCts.Token.Register(() => { if (setupWindow.Result == ContentDialogResult.Primary) { settingsManager.SetEulaAccepted(); ShowMainWindow(); DesktopLifetime.MainWindow.Show(); } else { Shutdown(); } }); } else { ShowMainWindow(); } } } private void ShowMainWindow() { 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; 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; } else { mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen; } mainWindow.Closing += (_, _) => { 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 ); }; mainWindow.Closed += (_, _) => Shutdown(); mainWindow.SetDefaultFonts(); VisualRoot = mainWindow; StorageProvider = mainWindow.StorageProvider; Clipboard = mainWindow.Clipboard ?? throw new NullReferenceException("Clipboard is null"); DesktopLifetime.MainWindow = mainWindow; DesktopLifetime.Exit += OnExit; } private static void ConfigureServiceProvider() { var services = ConfigureServices(); Services = services.BuildServiceProvider(); var settingsManager = Services.GetRequiredService(); if (settingsManager.TryFindLibrary()) { Cultures.SetSupportedCultureOrDefault(settingsManager.Settings.Language); } else { Cultures.TrySetSupportedCulture(Settings.GetDefaultCulture()); } Services.GetRequiredService().StartEventListener(); } internal static void ConfigurePageViewModels(IServiceCollection services) { services .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); services.AddSingleton( provider => new MainWindowViewModel( provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService>(), provider.GetRequiredService(), provider.GetRequiredService() ) { Pages = { provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService() }, FooterPages = { provider.GetRequiredService() } } ); // Register disposable view models for shutdown cleanup services.AddSingleton(p => p.GetRequiredService()); } internal static void ConfigureDialogViewModels(IServiceCollection services) { // Dialog view models (transient) services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); 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(); services.AddTransient(); services.AddTransient(); // Global progress services.AddSingleton(); // Controls services.AddTransient(); // Inference controls services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); 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) .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) .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) ); } internal static void ConfigureViews(IServiceCollection services) { // Pages services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Inference tabs services.AddTransient(); services.AddTransient(); // Inference controls services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // Dialogs services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // Controls services.AddTransient(); // Windows services.AddSingleton(); services.AddSingleton(); } internal static void ConfigurePackages(IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); } private static IServiceCollection ConfigureServices() { var services = new ServiceCollection(); services.AddMemoryCache(); ConfigurePageViewModels(services); ConfigureDialogViewModels(services); ConfigurePackages(services); // Other services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddTransient(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton( provider => (IDisposable)provider.GetRequiredService() ); // Rich presence services.AddSingleton(); 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(); } else if (Compat.IsLinux || Compat.IsMacOS) { services.AddSingleton(); } ConfigureViews(services); if (Design.IsDesignMode) { services.AddSingleton(); } else { services.AddSingleton(); services.AddSingleton(p => p.GetRequiredService()); } services.AddTransient(_ => { var client = new GitHubClient(new ProductHeaderValue("StabilityMatrix")); // var githubApiKey = Config["GithubApiKey"]; // if (string.IsNullOrWhiteSpace(githubApiKey)) // return client; // // client.Credentials = // new Credentials(""); return client; }); // Configure Refit and Polly var jsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; jsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter()); jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter()); jsonSerializerOptions.Converters.Add( new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) ); jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; var defaultRefitSettings = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(jsonSerializerOptions) }; // Refit settings for IApiFactory var defaultSystemTextJsonSettings = SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions(); defaultSystemTextJsonSettings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; var apiFactoryRefitSettings = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(defaultSystemTextJsonSettings), }; // HTTP Policies var retryStatusCodes = new[] { HttpStatusCode.RequestTimeout, // 408 HttpStatusCode.InternalServerError, // 500 HttpStatusCode.BadGateway, // 502 HttpStatusCode.ServiceUnavailable, // 503 HttpStatusCode.GatewayTimeout // 504 }; var delay = Backoff.DecorrelatedJitterBackoffV2( medianFirstRetryDelay: TimeSpan.FromMilliseconds(80), retryCount: 5 ); var retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() .Or() .OrResult(r => retryStatusCodes.Contains(r.StatusCode)) .WaitAndRetryAsync(delay); // Shorter timeout for local requests var localTimeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(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; } ); // named client for update services.AddHttpClient("UpdateClient").AddPolicyHandler(retryPolicy); // Add Refit clients services .AddRefitClient(defaultRefitSettings) .ConfigureHttpClient(c => { c.BaseAddress = new Uri("https://civitai.com"); c.Timeout = TimeSpan.FromSeconds(15); }) .AddPolicyHandler(retryPolicy); // Add Refit client managers services .AddHttpClient("A3Client") .AddPolicyHandler(localTimeout.WrapAsync(localRetryPolicy)); /*services.AddHttpClient("IComfyApi") .AddPolicyHandler(localTimeout.WrapAsync(localRetryPolicy));*/ // Add Refit client factory services.AddSingleton( provider => new ApiFactory(provider.GetRequiredService()) { RefitSettings = apiFactoryRefitSettings, } ); ConditionalAddLogViewer(services); // Add logging services.AddLogging(builder => { builder.ClearProviders(); 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(), new NLogProviderOptions { IgnoreEmptyEventId = false, CaptureEventId = EventIdCaptureType.Legacy } ); #else builder.AddNLog(ConfigureLogging()); #endif }); return services; } /// /// Requests shutdown of the Current Application. /// /// This returns asynchronously *without waiting* for Shutdown /// Exit code for the 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.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) { lifetime.Shutdown(exitCode); } } private static void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs args) { Debug.WriteLine("Start OnExit"); // Services.GetRequiredService().OnShutdown(); var settingsManager = Services.GetRequiredService(); // If RemoveFolderLinksOnShutdown is set, delete all package junctions if ( settingsManager is { IsLibraryDirSet: true, Settings.RemoveFolderLinksOnShutdown: true } ) { var sharedFolders = Services.GetRequiredService(); sharedFolders.RemoveLinksForAllPackages(); } Debug.WriteLine("Start OnExit: Disposing services"); // Dispose all services foreach (var disposable in Services.GetServices()) { Debug.WriteLine($"Disposing {disposable.GetType().Name}"); disposable.Dispose(); } Debug.WriteLine("End OnExit"); } private static LoggingConfiguration ConfigureLogging() { var setupBuilder = LogManager.Setup(); ConditionalAddLogViewerNLog(setupBuilder); setupBuilder.LoadConfiguration(builder => { var debugTarget = builder .ForTarget("console") .WriteTo( new DebuggerTarget { Layout = "[${level:format=TriLetter}] " + "${callsite:includeNamespace=false:captureStackTrace=false}: ${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); // Disable console trace logging by default builder .ForLogger("StabilityMatrix.Avalonia.ViewModels.ConsoleViewModel") .WriteToNil(NLog.LogLevel.Debug); 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}" }); builder.ForLogger().FilterMinLevel(NLog.LogLevel.Trace).WriteTo(logViewerTarget); #endif }); // Sentry if (SentrySdk.IsEnabled) { LogManager.Configuration.AddSentry(o => { o.InitializeSdk = false; o.Layout = "${message}"; o.ShutdownTimeoutSeconds = 5; o.IncludeEventDataOnBreadcrumbs = true; o.BreadcrumbLayout = "${logger}: ${message}"; // Debug and higher are stored as breadcrumbs (default is Info) o.MinimumBreadcrumbLevel = NLog.LogLevel.Debug; // Error and higher is sent as event (default is Error) o.MinimumEventLevel = NLog.LogLevel.Error; }); } LogManager.ReconfigExistingLoggers(); return LogManager.Configuration; } /// /// Opens a dialog to save the current view as a screenshot. /// /// Only available in debug builds. [Conditional("DEBUG")] internal static void DebugSaveScreenshot(int dpi = 96) { const int scale = 2; dpi *= scale; var results = new List(); var targets = new List { VisualRoot }; foreach (var visual in targets.Where(x => x != null)) { var rect = new Rect(visual!.Bounds.Size); var pixelSize = new PixelSize((int)rect.Width * scale, (int)rect.Height * scale); var dpiVector = new Vector(dpi, dpi); var ms = new MemoryStream(); using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) { bitmap.Render(visual); bitmap.Save(ms); } results.Add(ms); } Dispatcher.UIThread.InvokeAsync(async () => { var dest = await StorageProvider.SaveFilePickerAsync( new FilePickerSaveOptions() { SuggestedFileName = "screenshot.png", ShowOverwritePrompt = true } ); if (dest?.TryGetLocalPath() is { } localPath) { var localFile = new FilePath(localPath); foreach (var (i, stream) in results.Enumerate()) { var name = localFile.NameWithoutExtension; if (results.Count > 1) { name += $"_{i + 1}"; } localFile = localFile.Directory!.JoinFile(name + ".png"); localFile.Create(); await using var fileStream = localFile.Info.OpenWrite(); stream.Seek(0, SeekOrigin.Begin); await stream.CopyToAsync(fileStream); } } }); } [Conditional("DEBUG")] private static void ConditionalAddLogViewer(IServiceCollection services) { #if DEBUG services.AddLogViewer(); #endif } [Conditional("DEBUG")] private static void ConditionalAddLogViewerNLog(ISetupBuilder setupBuilder) { #if DEBUG setupBuilder.SetupExtensions( extensionBuilder => extensionBuilder.RegisterTarget("DataStoreLogger") ); #endif } }