mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-19 05:55:47 -04:00
#1822 - Fix the startup crash after auto-upgrade by loading the library only after InteropFactory assembly resolution is ready, checking that Microsoft.EntityFrameworkCore.Sqlite.dll is present in the install folder, and showing a clear reinstall message instead of a generic "Unexpected error" when that still fails.
This commit is contained in:
@@ -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([]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
62
Source/LibationFileManager/StartupAssemblyBootstrap.cs
Normal file
62
Source/LibationFileManager/StartupAssemblyBootstrap.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user