Merge remote-tracking branch 'upstream/master' into language_and_region

This commit is contained in:
Jo-Be-Co
2026-04-14 00:55:42 +02:00
11 changed files with 93 additions and 4 deletions

View File

@@ -15,7 +15,7 @@ jobs:
backfill:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@v9
with:
script: |
const exemptLabel = 'exempt';

View File

@@ -25,7 +25,7 @@ jobs:
contains(fromJSON('["rmcrackan","Mbucari"]'), github.event.issue.user.login)) ||
(github.event_name == 'pull_request' &&
contains(fromJSON('["rmcrackan","Mbucari"]'), github.event.pull_request.user.login))
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const number = context.issue?.number || context.payload.pull_request?.number;

View File

@@ -49,7 +49,7 @@ jobs:
- name: Release
id: release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
name: Libation ${{ needs.prerelease.outputs.version }}
body: "<Put a body here>"

View File

@@ -108,7 +108,7 @@ setup_db() {
error "database not found and creation is disabled"
exit 1
fi
ln -s "${FILE}" "${LIBATION_CONFIG_INTERNAL}/LibationContext.db"
ln -sf "${FILE}" "${LIBATION_CONFIG_INTERNAL}/LibationContext.db"
}
run() {

View File

@@ -225,6 +225,7 @@ public static class LibationScaffolding
private static void configureLogging(Configuration config)
{
config.ConfigureLogging();
DbContexts.TryEmitPendingInitialDatabaseStatistics();
// capture most Console.WriteLine() and write to serilog. See below tests for details.
// Some dependencies print helpful info via Console.WriteLine. We'd like to log it.

View File

@@ -1,6 +1,9 @@
using DataLayer;
using LibationFileManager;
using Serilog;
using System;
using System.Collections.Generic;
using System.Threading;
namespace ApplicationServices;
@@ -8,6 +11,24 @@ public static class DbContexts
{
private static bool _sqliteDbValidated;
private static readonly object _initialDatabaseStatisticsCaptureLock = new();
/// <summary>
/// True after initial DB statistics were read and either written to Serilog or stored for <see cref="TryEmitPendingInitialDatabaseStatistics"/>.
/// False if capture has not run yet or the last attempt threw (a later <see cref="GetContext"/> may retry).
/// </summary>
private static bool _initialDatabaseStatisticsCaptured;
/// <summary>Shape of the initial DB statistics log event; edit here only when changing what is logged.</summary>
private sealed class InitialDatabaseStatistics
{
public required int LibraryBooksNotInTrash { get; init; }
public required int LibraryBooksInTrash { get; init; }
public required int BookRecords { get; init; }
}
private static InitialDatabaseStatistics? _pendingInitialDbStats;
/// <summary>Use for fully functional context, incl. SaveChanges(). For query-only, use the other method</summary>
public static LibationContext GetContext()
{
@@ -25,9 +46,59 @@ public static class DbContexts
_sqliteDbValidated = true;
}
TryCaptureInitialDatabaseStatistics(context);
return context;
}
private static void TryCaptureInitialDatabaseStatistics(LibationContext context)
{
lock (_initialDatabaseStatisticsCaptureLock)
{
if (_initialDatabaseStatisticsCaptured)
return;
try
{
var (notInTrash, inTrash) = context.GetLibraryBookCountsByTrashFlag();
var bookRecords = context.GetBookCount();
var stats = new InitialDatabaseStatistics
{
LibraryBooksNotInTrash = notInTrash,
LibraryBooksInTrash = inTrash,
BookRecords = bookRecords,
};
if (Configuration.Instance.SerilogInitialized)
LogInitialDatabaseStatistics(stats);
else
_pendingInitialDbStats = stats;
_initialDatabaseStatisticsCaptured = true;
}
catch (Exception ex)
{
if (Configuration.Instance.SerilogInitialized)
Log.Warning(ex, "Could not capture initial database statistics");
}
}
}
/// <summary>
/// Writes initial DB statistics that were captured before Serilog was configured (e.g. WinForms early library load).
/// Call once after <see cref="Configuration.ConfigureLogging"/>.
/// </summary>
public static void TryEmitPendingInitialDatabaseStatistics()
{
var pending = Interlocked.Exchange(ref _pendingInitialDbStats, null);
if (pending is not null)
LogInitialDatabaseStatistics(pending);
}
private static void LogInitialDatabaseStatistics(InitialDatabaseStatistics stats) =>
Log.Logger.Information("Initial database statistics. {@DbStats}", stats);
/// <summary>Use for full library querying. No lazy loading</summary>
public static List<LibraryBook> GetLibrary_Flat_NoTracking(bool includeParents = false)
{

View File

@@ -16,6 +16,10 @@ public static class BookQueries
.AsNoTrackingWithIdentityResolution()
.GetBook(productId);
/// <summary>Total rows in <see cref="LibationContext.Books"/> (no related entities loaded).</summary>
public static int GetBookCount(this LibationContext context)
=> context.Books.AsNoTracking().Count();
public static Book? GetBook(this IQueryable<Book> books, string productId)
=> books
.GetBooks()

View File

@@ -62,6 +62,13 @@ public static class LibraryBookQueries
.Where(lb => lb.IsDeleted || lb.Book.ContentType == ContentType.Parent)
.getLibrary()
.ToList();
/// <summary>Counts <see cref="LibraryBook"/> rows by <see cref="LibraryBook.IsDeleted"/> (no related entities loaded).</summary>
public (int NotInTrash, int InTrash) GetLibraryBookCountsByTrashFlag()
{
var q = context.LibraryBooks.AsNoTracking();
return (q.Count(lb => !lb.IsDeleted), q.Count(lb => lb.IsDeleted));
}
}
extension(IQueryable<LibraryBook> library)

View File

@@ -75,6 +75,7 @@
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.11" />
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.21.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />

View File

@@ -80,6 +80,7 @@
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
<PackageReference Include="WebViewControlAvaloniaFree" Version="11.3.16" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.21.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -71,6 +71,10 @@ sudo docker run -d \
If the user it's running as is correct, and it still cannot write, be sure to check whether the files and/or folders might be owned by the wrong user. You can use the `chown` command to change the owner of the file to the correct user and group number, for example: `chown -R 1001:1001 /mnt/audiobooks /mnt/libation-config`
## Troubleshooting
- **Library scan appears to hang in Docker:** Try setting `ImportEpisodes` to `false` in `Settings.json` on your config volume and run again. That turns off the extra episode/podcast catalog requests during import and helps narrow down whether the stall is in that path versus auth or the network.
## Advanced Database Options
The docker image supports an optional database mount location defined by `LIBATION_DB_DIR`. This allows the database to be mounted as read/write, while allowing the rest of the configuration files to be mounted as read only. This is specifically useful if running in Kubernetes where you can use Configmaps and Secrets to define the configuration. If the `LIBATION_DB_DIR` is mounted, it will be used, otherwise it will look for the database in `LIBATION_CONFIG_DIR`. If it does not find the database in the expected location, it will attempt to make an empty database there.