diff --git a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAppHost.cs b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAppHost.cs index 6b4a5c8af6..345d3bd32f 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAppHost.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAppHost.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using Avalonia; +using Avalonia.Threading; using UniGetUI.Core.Data; using UniGetUI.Core.Logging; using UniGetUI.Core.Tools; @@ -68,6 +69,11 @@ Welcome to UniGetUI Version {CoreData.VersionName} Logger.ImportantInfo($"Packaged (MSIX): {CoreTools.IsPackagedApp()}"); Logger.ImportantInfo($"Args: {(args.Length > 0 ? string.Join(" ", args) : "(none)")}"); + // Bind Avalonia's UI-thread dispatcher to this (main/STA) thread before the single-instance + // listener starts: if a second instance connects mid-startup, the listener's Dispatcher.UIThread.Post + // would otherwise bind it to the worker thread and make Win32Platform.Initialize throw. + _ = Dispatcher.UIThread; + if (!TryRegisterSingleInstance(args)) { return; diff --git a/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs b/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs index b114fc305b..1d1ebd4771 100644 --- a/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs +++ b/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs @@ -33,57 +33,85 @@ public static class PEInterface { private const int ManagerLoadTimeout = 60; // 60 seconds timeout for Package Manager initialization (in seconds) #if WINDOWS - public static readonly WinGet WinGet = new(); - public static readonly Scoop Scoop = new(); - public static readonly Chocolatey Chocolatey = new(); + public static readonly WinGet? WinGet = Create(() => new WinGet()); + public static readonly Scoop? Scoop = Create(() => new Scoop()); + public static readonly Chocolatey? Chocolatey = Create(() => new Chocolatey()); #endif - public static readonly Npm Npm = new(); - public static readonly Bun Bun = new(); - public static readonly Pip Pip = new(); - public static readonly DotNet DotNet = new(); - public static readonly PowerShell7 PowerShell7 = new(); + public static readonly Npm? Npm = Create(() => new Npm()); + public static readonly Bun? Bun = Create(() => new Bun()); + public static readonly Pip? Pip = Create(() => new Pip()); + public static readonly DotNet? DotNet = Create(() => new DotNet()); + public static readonly PowerShell7? PowerShell7 = Create(() => new PowerShell7()); #if WINDOWS - public static readonly PowerShell PowerShell = new(); + public static readonly PowerShell? PowerShell = Create(() => new PowerShell()); #endif - public static readonly Cargo Cargo = new(); - public static readonly Vcpkg Vcpkg = new(); + public static readonly Cargo? Cargo = Create(() => new Cargo()); + public static readonly Vcpkg? Vcpkg = Create(() => new Vcpkg()); #if !WINDOWS - public static readonly Apt Apt = new(); - public static readonly Dnf Dnf = new(); - public static readonly Pacman Pacman = new(); - public static readonly Homebrew Homebrew = new(); - public static readonly Snap Snap = new(); - public static readonly Flatpak Flatpak = new(); + public static readonly Apt? Apt = Create(() => new Apt()); + public static readonly Dnf? Dnf = Create(() => new Dnf()); + public static readonly Pacman? Pacman = Create(() => new Pacman()); + public static readonly Homebrew? Homebrew = Create(() => new Homebrew()); + public static readonly Snap? Snap = Create(() => new Snap()); + public static readonly Flatpak? Flatpak = Create(() => new Flatpak()); #endif public static readonly IPackageManager[] Managers = CreateManagers(); + // A single manager that fails to construct must not take down the whole engine (and with it + // the app, via TypeInitializationException). Log it and leave the field null; CreateManagers + // drops nulls so the rest of the managers stay usable. + private static T? Create(Func factory) where T : class, IPackageManager + { + try + { + return factory(); + } + catch (Exception ex) + { + // Logging runs inside its own guard: this method must never throw, or it would + // re-trigger the TypeInitializationException it exists to prevent (it runs from + // a static field initializer). + try + { + Logger.Error($"Failed to construct package manager {typeof(T).Name}; it will be unavailable this session."); + Logger.Error(ex); + } + catch { /* swallow: never let static initialization fail */ } + return null; + } + } + private static IPackageManager[] CreateManagers() { - List managers = [Npm, Bun, Pip, Cargo, Vcpkg, DotNet, PowerShell7]; + List candidates = [Npm, Bun, Pip, Cargo, Vcpkg, DotNet, PowerShell7]; #if WINDOWS - managers.InsertRange(0, [WinGet, Scoop, Chocolatey]); - managers.Add(PowerShell); + candidates.InsertRange(0, [WinGet, Scoop, Chocolatey]); + candidates.Add(PowerShell); #else - managers.Insert(0, Homebrew); + candidates.Insert(0, Homebrew); if (OperatingSystem.IsLinux()) { var families = ReadLinuxDistroFamilies(); // If /etc/os-release is unreadable, include both as a safe fallback. bool unknown = families.Count == 0; if (unknown || families.Contains("debian") || families.Contains("ubuntu")) - managers.Add(Apt); + candidates.Add(Apt); if (unknown || families.Contains("fedora") || families.Contains("rhel") || families.Contains("centos")) - managers.Add(Dnf); + candidates.Add(Dnf); if (unknown || families.Contains("arch")) - managers.Add(Pacman); + candidates.Add(Pacman); if (unknown || families.Contains("ubuntu") || families.Contains("debian") || families.Contains("fedora") || families.Contains("arch")) { - managers.Add(Snap); - managers.Add(Flatpak); + candidates.Add(Snap); + candidates.Add(Flatpak); } } #endif + List managers = []; + foreach (IPackageManager? manager in candidates) + if (manager is not null) + managers.Add(manager); return [.. managers]; } diff --git a/src/UniGetUI/CrashHandler.cs b/src/UniGetUI/CrashHandler.cs index 095b5f12cb..b62a304428 100644 --- a/src/UniGetUI/CrashHandler.cs +++ b/src/UniGetUI/CrashHandler.cs @@ -182,6 +182,8 @@ static string GetExceptionData(Exception e) {{e.StackTrace?.Replace("\n", "\n ")}} """; + Exception originalException = e; + try { int i = 0; @@ -216,6 +218,19 @@ Inner exception details (depth level: {{i}}) // ignore } + // Authoritative fallback: ToString() recurses through every inner exception (and all of an + // AggregateException's inners) with their stack traces. The walk above only follows the single + // .InnerException chain, so it can drop the real cause of e.g. a TypeInitializationException. + try + { + Error_String += "\n\n\n———————————————————————————————————————————————————————————\n" + + "Full exception detail (ToString):\n" + originalException; + } + catch + { + // ignore + } + Error_String = Logger.Redact(Error_String); Console.WriteLine(Error_String); diff --git a/src/UniGetUI/Pages/DialogPages/IgnoredUpdates.xaml.cs b/src/UniGetUI/Pages/DialogPages/IgnoredUpdates.xaml.cs index f069dd5532..2b36a9f82e 100644 --- a/src/UniGetUI/Pages/DialogPages/IgnoredUpdates.xaml.cs +++ b/src/UniGetUI/Pages/DialogPages/IgnoredUpdates.xaml.cs @@ -44,12 +44,15 @@ private void UpdateData() foreach (var (ignoredId, version) in rawIgnoredPackages) { - IPackageManager manager = PEInterface.WinGet; // Manager by default + IPackageManager? manager = PEInterface.WinGet; // Manager by default if (ManagerNameReference.ContainsKey(ignoredId.Split("\\")[0])) { manager = ManagerNameReference[ignoredId.Split("\\")[0]]; } + if (manager is null) + continue; + ignoredPackages.Add( new IgnoredPackageEntry( ignoredId.Split("\\")[^1], diff --git a/src/UniGetUI/Pages/MainView.xaml.cs b/src/UniGetUI/Pages/MainView.xaml.cs index da09efe28f..b5e12d23d8 100644 --- a/src/UniGetUI/Pages/MainView.xaml.cs +++ b/src/UniGetUI/Pages/MainView.xaml.cs @@ -175,7 +175,7 @@ public MainView(AutoSuggestBox mainTextBlock) } if ( - !PEInterface.Chocolatey.Status.Found + PEInterface.Chocolatey is { Status.Found: false } && Chocolatey.HasLegacyBundledInstallation() && !Settings.Get(Settings.K.AlreadyWarnedAboutChocolateyMigration) ) diff --git a/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs b/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs index d215fbf39e..7c854741a5 100644 --- a/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs +++ b/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs @@ -85,6 +85,10 @@ protected override void OnNavigatedTo(NavigationEventArgs e) else throw new InvalidCastException("The specified type was not a package manager!"); + // The manager failed to construct at startup (see PEInterface): there is nothing to configure. + if (Manager is null) + return; + ReapplyProperties?.Invoke(this, new()); ApplyManagerState();