using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Microsoft.AspNetCore.DataProtection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog; using OTSSignsOrchestrator.Core.Configuration; using OTSSignsOrchestrator.Core.Data; using OTSSignsOrchestrator.Core.Services; using OTSSignsOrchestrator.Desktop.Services; using OTSSignsOrchestrator.Desktop.ViewModels; using OTSSignsOrchestrator.Desktop.Views; namespace OTSSignsOrchestrator.Desktop; public class App : Application { public static IServiceProvider Services { get; private set; } = null!; public override void Initialize() { AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { var services = new ServiceCollection(); ConfigureServices(services); Services = services.BuildServiceProvider(); // Apply migrations using (var scope = Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); db.Database.Migrate(); } Log.Information("ApplicationLifetime type: {Type}", ApplicationLifetime?.GetType().FullName ?? "null"); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { Log.Information("Creating MainWindow..."); var vm = Services.GetRequiredService(); Log.Information("MainWindowViewModel resolved"); var window = new MainWindow { DataContext = vm }; desktop.MainWindow = window; Log.Information("MainWindow assigned to lifetime"); window.Show(); window.Activate(); Log.Information("MainWindow Show() + Activate() called"); desktop.ShutdownRequested += (_, _) => { var ssh = Services.GetService(); ssh?.Dispose(); }; } else { Log.Warning("ApplicationLifetime is NOT IClassicDesktopStyleApplicationLifetime — window cannot be shown"); } base.OnFrameworkInitializationCompleted(); } private static void ConfigureServices(IServiceCollection services) { // Configuration var config = new ConfigurationBuilder() .SetBasePath(AppContext.BaseDirectory) .AddJsonFile("appsettings.json", optional: false) .Build(); services.AddSingleton(config); // Options services.Configure(config.GetSection(GitOptions.SectionName)); services.Configure(config.GetSection(DockerOptions.SectionName)); services.Configure(config.GetSection(XiboOptions.SectionName)); services.Configure(config.GetSection(DatabaseOptions.SectionName)); services.Configure(config.GetSection(FileLoggingOptions.SectionName)); // Logging services.AddLogging(builder => { builder.ClearProviders(); builder.AddSerilog(dispose: true); }); // Data Protection var keysDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OTSSignsOrchestrator", "keys"); Directory.CreateDirectory(keysDir); services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(keysDir)) .SetApplicationName("OTSSignsOrchestrator"); // Database var connStr = config.GetConnectionString("Default") ?? "Data Source=otssigns-desktop.db"; services.AddDbContext(options => options.UseSqlite(connStr)); // HTTP services.AddHttpClient(); services.AddHttpClient("XiboApi"); // SSH services (singletons — maintain connections) services.AddSingleton(); // Docker services via SSH (scoped so they get fresh per-operation context) services.AddTransient(); services.AddTransient(); services.AddTransient(sp => sp.GetRequiredService()); services.AddTransient(sp => sp.GetRequiredService()); // Core services services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // ViewModels services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); } }