Commit Graph
385 Commits
Author SHA1 Message Date
Robert McRackan 19a867488b Update dependencies 2026-08-30 14:15:35 -04:00
Cursor Agentandrmcrackan eca013e780 Bump version to 14.0.0
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 18:14:08 +00:00
rmcrackan b02aedb579 Merge pull request #2007 from rmcrackan/cursor/serilog-startup-resilience-3b83
Survive an install folder that cannot load Serilog (#2001)
2026-08-26 10:51:46 -04:00
Cursor Agentandrmcrackan 1729f84dbb Log startup recovery without Serilog so a broken install cannot silence it
Libation's releases are ReadyToRun, so a Serilog reference resolves lazily
when the line holding it runs. An install folder whose Serilog.dll is missing
or older than the build therefore threw inside the very catch blocks that were
meant to report the problem, which destroyed the original exception and aborted
the in-app upgrade recovery before it could roll anything back.

Add StartupLog: a Serilog-free, never-throwing buffer for the window before
ConfigureLogging runs, replayed into Serilog once logging exists. Move the
pre-logging call sites onto it, including the crash-message and crash-dialog
paths that run precisely when the install is broken.

Those messages were silent before this change: Serilog.Log.Logger is still
Serilog's silent logger until App.RunMigrations. They now reach the log file.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:44:36 +00:00
Cursor Agentandrmcrackan c2f9392fa8 Let users turn off the startup update check
Libation asked GitHub for a newer release every time it started, with no way
to stop it. That is noise for anyone whose install is updated by something
else - a package manager, or an AppImage updater - because the prompt it
raises is one they can do nothing useful with.

Add CheckForUpgradesAtStartup, on by default so nothing changes for people
who rely on the prompt. Only the automatic check is optional: the About
window's "Check for Upgrade" button and the CLI's `version --check` ask for a
check outright, so they run either way. That is why the setting is read in a
new CheckForUpgradeAtStartupAsync rather than inside CheckForUpgradeAsync,
which the startup path and the About button share.

The new setting takes the slot of BetaOptIn, which is deleted here. It was
declared, described and logged, but no axaml or designer ever bound it and
nothing read the value: GetLatestRelease only ever asks for the stable
release, so there was no beta channel for it to select. A stale BetaOptIn key
in an existing Settings.json needs no migration, since PersistentDictionary
ignores keys with no matching property.

Closes #1999

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-25 21:09:23 +00:00
Cursor Agentandrmcrackan 04a7103ae8 Bump version to 13.7.11
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 22:07:52 +00:00
Cursor Agentandrmcrackan 5ef3f3e11e fix: a directory that stops being readable no longer throws at whoever lists it
SaferEnumerateFiles returned a lazy sequence, so an I/O error was raised where
the sequence was walked rather than where it was created - past the try/catch
callers had wrapped around it. IgnoreInaccessible did not help either: it only
forgives permissions, not a volume that has stopped answering.

Walk the enumerator defensively instead, keeping what was read and reporting
the reason, and let a caller ask whether a directory can be read at all.

See issue #1984.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 14:21:58 +00:00
Cursor Agentandrmcrackan 099b01af14 Ask Windows which folders are synced instead of guessing from OneDrive variables
The previous check read the OneDrive environment variables and matched path
prefixes, so it missed Dropbox, Google Drive and iCloud, and had its own
guesswork to get wrong.

Every sync engine that serves files on demand registers a sync root with the
Cloud Files API, and CfGetSyncRootInfoByPath answers directly whether a path
sits under one. That covers every provider without naming any of them, and it
replaces the environment lookups, path canonicalisation and prefix matching
rather than adding to them. Asking for the provider info class also gets the
provider's own name, so the message can say Dropbox rather than a generic phrase.

The buffers are plain byte arrays read by hand: nothing here can be verified
away from Windows, so there is no marshalling to get subtly wrong.

Detection cannot produce a false positive now, since the answer is Windows'
rather than an inference from a folder name. The warning still appears only in
the incomplete-upgrade message, where something has already failed, so a folder
someone deliberately keeps fully downloaded never prompts anything.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-18 17:55:06 +00:00
Cursor Agentandrmcrackan a0ca574974 Detect Application Control enforcement and stop upgrading into a broken install
An in-app upgrade overlays files Windows has never seen. Smart App Control
blocks unsigned files it does not recognise, so upgrading in place under
enforcement is precisely how a working install becomes one that cannot start,
which is what #1873, #1876 and #1967 all describe.

Read VerifiedAndReputablePolicyState under HKLM\SYSTEM\CurrentControlSet\Control\CI\Policy
to find out. The read needs no elevation and cannot raise a UAC prompt: UAC
prompts only on an explicit elevation request, and HKLM is readable by standard
users. Only the value 1 counts as enforcing. A missing key, a missing value, or
anything unrecognised counts as not enforcing, because the cost of guessing
wrong is telling someone to disable Smart App Control, which cannot be undone,
on a PC that was never blocking anything.

When enforcing, the upgrade notification becomes a notice with the download
link instead of an update prompt, and the flow stops before downloading even if
a UI ignores that. Classic honoured no such flag at all, so its dialog now takes
one; its two prompt labels had to be promoted from designer locals to fields to
carry the explanation.

The blocked-file dialog now states the setting it found rather than asking the
user to go and look, and startup logs the state, the install folder, and any
cloud sync root containing it, so a report answers these without a round trip.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-18 16:26:56 +00:00
Cursor Agentandrmcrackan 294608d95c Bump version to 13.7.10
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 19:13:48 +00:00
Cursor Agentandrmcrackan 209fd3aa4c Bump version to 13.7.9
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 18:48:26 +00:00
Cursor Agentandrmcrackan 79bdbe6d76 Tell the user how to delete the search index when repair fails
Ported from #1949, which surfaces the manual recovery steps the maintainer had
been giving out by hand instead of leaving the user with a raw Lucene error.

Adapted to the failure now being contained: with the exception no longer escaping
into the library change, the scan-failure catch blocks #1949 hooked would never
see it, and hooking only those would still miss every other trigger -- removing
books is what crashed the GUI. So the guard moves from AppScaffolding into
SearchEngineCommands next to the update commands it protects, and raises
UpdateFailed from there. Both GUIs subscribe, so any trigger is covered, and the
event carries the exception rather than needing #1949's StackTrace string sniffing
to find it. The dialog is shown once per session: a damaged index fails on every
library change and these steps only need following once.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:44:58 +00:00
Cursor Agentandrmcrackan 0aa9cb0019 Heal a search index Lucene cannot open, instead of retrying it as a lock conflict
A truncated or zero-length segments file is reported by Lucene 3 as a plain
IOException ("read past EOF") rather than a CorruptIndexException, so it was
misclassified as a write.lock conflict: CreateNewIndex burned its whole backoff
budget and rethrew, and the delete-and-rebuild recovery never ran. Passing
create/overwrite to IndexWriter does not repair it either, because
IndexFileDeleter reads every segments_* file in the directory and tolerates only
missing ones, so a single unreadable segments file -- even a stale one from an
older commit -- leaves the index permanently unopenable. The user's only cure
was deleting the SearchEngine folder by hand.

Retries are now reserved for genuine lock conflicts (LockObtainFailedException,
which derives from IOException, and UnauthorizedAccessException), and any other
open failure gets one delete-and-rebuild pass before giving up with a message
that says which folder to remove. The query path recovers too, since
IsRecoverableCorruptIndexException now recognizes the truncated-segments
signature.

Search index updates are also no longer allowed to fail the library change that
triggered them. Both events fire after the database is committed, so an escaping
exception reported a successful scan as "Error importing library" and, being the
first subscriber, stopped the handlers that refresh the grid and backup counts.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:05:45 +00:00
Cursor Agentandrmcrackan cf5903e5be Bump version to 13.7.8
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 21:29:53 +00:00
Cursor Agentandrmcrackan a5b1df0d29 Centralize best-effort file deletion in FileUtility
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-12 18:55:42 +00:00
Cursor Agentandrmcrackan 90856b29a6 Add single-instance guard and make SQLite WAL/SHM cleanup data-safe
Concurrent Libation instances against one LibationFiles folder raced on the
SQLite database, Lucene search index, and log file, and the startup routine
unconditionally deleted LibationContext.db-wal/-shm - discarding committed
transactions when a prior run died abruptly (issue #1931).

- Add SingleInstance (named mutex keyed on the LibationFiles folder). Wire it
  into the Avalonia and WinForms startup so a second launch shows a message and
  exits before any database access, and holds the lock for the process lifetime.
- DeleteOpenSqliteFiles now skips cleanup when the DB is held by another process
  and preserves a non-empty (unrecovered) WAL so SQLite can recover it on open.

Also compare AccountId case-insensitively in AccountsSettings.GetAccount so a
capitalization difference no longer causes spurious 'No account found' errors.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-12 18:13:58 +00:00
Cursor Agentandrmcrackan a01fece235 bugfix #1925: stop library scans from silently dropping titles
Audible's catalog endpoint can answer 200 while omitting products from the
response. getProductsAsync returned whatever came back, so any podcast episode
Audible skipped simply vanished from the scan. The reporter's log shows this:
across 1117 consecutive scans of an unchanged 440-item library, the post-scan
item total drifted between 2147 and 2151.

Re-request the omitted asins before accepting the loss, and warn with the asins
that are still unaccounted for afterwards.

The rest of the scan's exclusions were equally invisible at the default log
level, which is why the reporter found nothing in the log about the missing
book:
- episodes dropped for having no series parent were logged at Debug, without
  identifying them. Warn instead, and name them.
- titles excluded by ImportEpisodes / ImportPlusTitles were not logged at all.
  Tally them, and record both settings in the startup state block.

Read the two import filters once per scan so a settings change mid-scan can't
produce a half-filtered library.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-10 02:58:42 +00:00
Cursor Agentandrmcrackan a40e9e7cf7 release: bump version to 13.7.7
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-09 18:48:30 +00:00
Robert McRackan 065118cf6c Fail fast on invalid Settings.json enums and broken Serilog structure: reject unknown enum values at startup with clear errors, accept case-insensitive names, migrate ZipFile sinks, and validate Serilog shape without blocking hand-edited custom sinks 2026-08-07 11:10:43 -04:00
Robert McRackan e0d49b57e1 Encrypt-fail plaintext fallback 2026-08-05 10:02:53 -04:00
Robert McRackan 3872c10c81 incr ver 2026-08-02 11:56:17 -04:00
Robert McRackan 8a61e4d7f4 incr ver 2026-08-02 11:08:19 -04:00
Robert McRackan 680e48638a incr ver 2026-08-01 12:21:51 -04:00
Robert McRackan f12f5d42a1 incr ver 2026-08-01 09:40:20 -04:00
Robert McRackan a079752c71 incr ver. add contributor @Demoniskk 2026-07-28 14:22:35 -04:00
Robert McRackan 2c52513ea9 incr ver 2026-07-25 22:08:49 -04:00
Robert McRackan bb18e9adc4 Add TokenStorageMethod preference and AudibleApi token-storage wiring 2026-07-25 21:17:11 -04:00
Robert McRackan e372bae144 Add donate link to 'About' form 2026-07-25 11:22:45 -04:00
Robert McRackan 07c2f2b2a1 incr ver 2026-07-17 14:53:27 -04:00
rmcrackan 9ff15a07aa #1881 - Fix Linux in-app upgrades on RHEL/Fedora by selecting .rpm packages instead of .deb when dnf is the native package manager. 2026-06-17 15:36:28 -04:00
rmcrackan 82d4001f0a #1878 : Detect incomplete in-app upgrades, roll back install files, and show clear recovery messages 2026-06-17 10:18:52 -04:00
rmcrackan d955cb7605 #1873 , #1876 - Handle Windows Application Control blocking after in-app upgrades 2026-06-16 17:25:18 -04:00
rmcrackan a85179f50a incr ver 2026-06-12 11:54:14 -04:00
Robert 1547e6bec3 incr ver 2026-06-08 15:41:20 -04:00
rmcrackan b7ce96e91d Finish windows installers, phase 1 2026-06-08 14:03:59 -04:00
rmcrackan d03955a2ca Add Windows installers to release build 2026-06-08 10:43:32 -04:00
rmcrackan 6623300883 Update AppScaffolding.csproj 2026-06-05 10:42:21 -04:00
rmcrackan 0c22b821bf incr ver 2026-05-25 11:16:38 -04:00
Robert 9ee59738eb incr ver 2026-05-21 10:36:21 -04:00
rmcrackan ca52ed4b7a incr ver 2026-05-19 08:55:57 -04:00
Robert f330d8bed6 incr ver 2026-05-05 13:45:27 -04:00
Robert 78371e3522 incr ver 2026-05-05 09:41:26 -04:00
MBucari 505c614210 Code Cleanup
Remove unused parameters
Remove unnecessary casts
Make fields readonly
Order modifiers
Format document
Sort usings
Remove unnecessary nullable directive
Apply namespace preferences (file-level)
2026-05-04 21:10:28 -06:00
rmcrackan 7b1dd64936 incr ver 2026-05-04 13:43:05 -04:00
rmcrackan d934511c32 incr ver 2026-05-01 10:11:51 -04:00
rmcrackan dd059a0ce9 #1776 - Make Linux Snap SQLite startup failures easier to understand and fix (stale LibationFiles in appsettings.json after a revision change), improve crash and log detail (inner exceptions + paths), and document that in the docs and FAQ. Tests lock in exception formatting behavior. 2026-05-01 10:01:03 -04:00
rmcrackan 849f5f508e trying again to fix the intermittent cover image issue 2026-05-01 09:38:29 -04:00
Robert 9d6f0f7700 incr ver 2026-04-20 22:01:31 -04:00
rmcrackan 6efc64b927 incr ver 2026-04-16 12:27:11 -04:00
rmcrackan 26946e8a87 Log initial db state, esp to capture books in trash 2026-04-09 10:57:41 -04:00