Merge pull request #1825 from rmcrackan/rmcrackan/1822-crash-after-upgrade

#1822 - Fix the startup crash after auto-upgrade
This commit is contained in:
rmcrackan
2026-05-18 09:02:42 -04:00
committed by GitHub
4 changed files with 119 additions and 19 deletions

View File

@@ -73,6 +73,7 @@ public class App : Application
{
// setup succeeded or wasn't needed and LibationFiles are valid
RunMigrations(config);
StartupAssemblyBootstrap.PrepareForBackgroundDataAccess();
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
ShowMainWindow(desktop);
}
@@ -142,8 +143,22 @@ public class App : Application
{
if (LibraryTask is not null && MainWindow is not null)
{
List<DataLayer.LibraryBook> library = await LibraryTask;
await Dispatcher.UIThread.InvokeAsync(() => MainWindow.OnLibraryLoadedAsync(library));
try
{
List<DataLayer.LibraryBook> library = await LibraryTask;
await Dispatcher.UIThread.InvokeAsync(() => MainWindow.OnLibraryLoadedAsync(library));
}
catch (Exception ex) when (StartupAssemblyBootstrap.IsMissingDependencyAssembly(ex))
{
Serilog.Log.Logger.Error(ex, "Failed to load library at startup");
await MessageBox.Show(
MainWindow,
StartupAssemblyBootstrap.GetLibraryLoadFailureMessage(),
"Library load failed",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
await Dispatcher.UIThread.InvokeAsync(() => MainWindow.OnLibraryLoadedAsync([]));
}
}
}
}

View File

@@ -56,8 +56,7 @@ static class Program
if (config.LibationFiles.SettingsAreValid)
{
App.RunMigrations(config);
// When running inside Snap, UseWebView getter returns false to avoid portal/sandbox crashes (e.g. github ticket #1664).
//Start loading the library before loading the main form
StartupAssemblyBootstrap.PrepareForBackgroundDataAccess();
App.LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
}
BuildAvaloniaApp()?.StartWithClassicDesktopLifetime([], ShutdownMode.OnExplicitShutdown);

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
namespace LibationFileManager;
/// <summary>
/// Ensures OS interop assembly resolution and required dependency files are ready before background library load.
/// </summary>
public static class StartupAssemblyBootstrap
{
public const string EntityFrameworkCoreSqliteAssemblyFileName = "Microsoft.EntityFrameworkCore.Sqlite.dll";
/// <summary>
/// Registers <see cref="InteropFactory"/> assembly resolution and verifies required install-folder assemblies exist.
/// Call before <c>Task.Run</c> loads the library or opens the database on a thread-pool thread.
/// </summary>
public static void PrepareForBackgroundDataAccess()
{
_ = InteropFactory.InteropFunctionsType;
ValidateEntityFrameworkCoreSqlitePresent();
}
public static string GetLibraryLoadFailureMessage() =>
$"""
Libation could not load its database components (Entity Framework Core for SQLite).
This often happens after an incomplete in-app upgrade. Quit Libation completely, then install a fresh copy of the latest release to a new folder (do not overlay files on top of the old install).
Install folder:
{Configuration.ProcessDirectory}
Expected file:
{Path.Combine(Configuration.ProcessDirectory, EntityFrameworkCoreSqliteAssemblyFileName)}
""";
public static bool IsMissingDependencyAssembly(Exception ex)
{
for (var current = ex; current is not null; current = current.InnerException)
{
if (current is not FileNotFoundException and not FileLoadException)
continue;
var name = (current as FileNotFoundException)?.FileName ?? current.Message;
if (name.Contains("EntityFrameworkCore", StringComparison.OrdinalIgnoreCase)
|| name.Contains("Microsoft.Data.Sqlite", StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
private static void ValidateEntityFrameworkCoreSqlitePresent()
{
var path = Path.Combine(Configuration.ProcessDirectory, EntityFrameworkCoreSqliteAssemblyFileName);
if (File.Exists(path))
return;
throw new FileNotFoundException(
$"Required file '{EntityFrameworkCoreSqliteAssemblyFileName}' was not found in the Libation install folder.{Environment.NewLine}{Environment.NewLine}{GetLibraryLoadFailureMessage()}",
path);
}
}

View File

@@ -4,6 +4,7 @@ using DataLayer;
using LibationFileManager;
using LibationUiBase;
using LibationWinForms.Dialogs;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -58,26 +59,31 @@ static class Program
// migrations which require Forms or are long-running
RunWindowsOnlyMigrations(config);
//*******************************************************************//
// //
// Start loading the library as soon as possible //
// //
// Before calling anything else, including subscribing to events, //
// to ensure database exists. If we wait and let it happen lazily, //
// race conditions and errors are likely during new installs //
// //
//*******************************************************************//
libraryLoadTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
// logging is init'd here
// logging is init'd here (also initializes InteropFactory via logStartupState)
LibationScaffolding.RunPostMigrationScaffolding(Variety.Classic, config);
//*******************************************************************//
// //
// Start loading the library as soon as possible after logging //
// and InteropFactory assembly resolution are ready. //
// //
//*******************************************************************//
StartupAssemblyBootstrap.PrepareForBackgroundDataAccess();
libraryLoadTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
}
catch (Exception ex)
{
var title = "Fatal error, pre-logging";
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
if (Configuration.Instance.SerilogInitialized)
Log.Error(ex, "Fatal error during startup");
var (title, body) = StartupAssemblyBootstrap.IsMissingDependencyAssembly(ex)
? ("Library load failed", StartupAssemblyBootstrap.GetLibraryLoadFailureMessage())
: (
"Fatal error, pre-logging",
"An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.");
try
{
MessageBoxLib.ShowAdminAlert(null, body, title, ex);
@@ -93,7 +99,7 @@ static class Program
postLoggingGlobalExceptionHandling();
form1 = new Form1();
form1.Load += async (_, _) => await form1.InitLibraryAsync(await libraryLoadTask);
form1.Load += async (_, _) => await LoadLibraryIntoFormAsync(form1, libraryLoadTask);
Application.Run(form1);
}
@@ -181,6 +187,24 @@ static class Program
}
}
private static async Task LoadLibraryIntoFormAsync(Form1 form, Task<List<LibraryBook>> libraryLoadTask)
{
try
{
await form.InitLibraryAsync(await libraryLoadTask);
}
catch (Exception ex) when (StartupAssemblyBootstrap.IsMissingDependencyAssembly(ex))
{
Log.Error(ex, "Failed to load library at startup");
MessageBoxLib.ShowAdminAlert(
form,
StartupAssemblyBootstrap.GetLibraryLoadFailureMessage(),
"Library load failed",
ex);
await form.InitLibraryAsync([]);
}
}
private static void postLoggingGlobalExceptionHandling()
{
// this line is all that's needed for strict handling