Commit Graph
61 Commits
Author SHA1 Message Date
Robert McRackan 19a867488b Update dependencies 2026-08-30 14:15:35 -04:00
Cursor Agentandrmcrackan 1431fd73a9 test: cover a title made of digits and punctuation
Stephen King's 11/22/63 sits either side of the line the number handling draws.
The analyzer keeps a date like this as one token, so the title is text and must
not be zero-padded, while "title:11*" is a bare number in a text field and must
not be padded either - that one fails against the code from before the numeric
fix, going looking for "title:00000011.00*".

Audible lists it with slashes and a hyphenated spelling turns up elsewhere, so
both are covered, as are books 11, 22 and 63 minutes long so that nothing can
pass by matching a number field instead of the title.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 22:37:43 +00:00
rmcrackan 8402010a2a Merge pull request #1992 from rmcrackan/cursor/parse-a-field-name-used-as-a-value-011e
fix: three search query bugs (field names as values, number padding, grouped negation)
2026-08-24 16:19:36 -04:00
Cursor Agentandrmcrackan 59ab316358 docs: rewrite the Lucene syntax guide around Libation's own fields
It was a generic primer whose examples used title, body and mod_date. Only one
of those exists here, so half the guide could not be typed into the search box.
Every example is now a real field and was run against an index before being
written down, including the exclusive range and the leading-wildcard error the
old text described but never showed.

Covers the two forms that reach the new number handling and had no test: a
wildcard on a numeric title, and a curly-brace range.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 20:14:44 +00:00
Cursor Agentandrmcrackan cad813e28b test: cover a book found through Hours, not just LengthInMinutes
A bare number has to keep every book it already matched and add the one whose
title is that number. The library had a book 14 minutes long but none 14 hours
long, so the Hours field went untested - the same field that made an earlier
draft of these tests pass against the bug by accident.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 19:51:45 +00:00
Cursor Agentandrmcrackan 0e6523e0ae fix: a number is only zero-padded when the field it is compared against is
Lucene 3 has no numeric type, so a number field is indexed zero-padded to make
a range sort: 600 minutes is stored as "00000600.00". Every other field keeps
the number as written, so the novel "1984" is stored as "1984". Padding every
number in a query regardless of the field it was being compared against made a
numeric title unfindable - "title:1984" looked for "00001984.00" among the
titles - and a bare "1984" could only ever mean "some number field is 1984".

A number now takes the spelling its field is indexed in. With no field named,
the default field holds both spellings, so both are searched. A range and a
phrase cannot hold that disjunction: a range stays padded, a phrase does not.

Expanding a bare number to a group exposed the pure-negation workaround, which
judged a query by every clause in its tree, so "-(a OR b)" was read as mixed
and returned nothing. It is now decided per query by that query's own clauses.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 19:26:25 +00:00
Cursor Agentandrmcrackan 0489be0350 fix: searching a field for a word that names another field
Search field names are ordinary words - absent, podcast, plus, series - and
a book's own text is full of them. The sanitizer read any known field name as
a field name wherever it appeared, so a value in that position picked up the
implied ":True" of a bool field: "title:absent" became "title:absent:True",
which Lucene refuses to parse, and "title:\"absent friend\"" quietly matched
nothing. A colon straight after a field name now marks the next term as that
field's value.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 18:41:40 +00:00
Cursor Agentandrmcrackan a0ef5c3d95 fix: repair a search index that is missing books
A book the index does not hold cannot be found by any positive filter, and
every negated filter - which resolves to every document in the index - drops
it from the grid instead. That is why issue #1989 reads as half a working
filter: Absent found nothing while -Absent removed exactly the absent books.

The index is only ever written as a whole, and a rebuild that fails is
deliberately swallowed so a bad index cannot fail a good scan, so a short
index stayed short until something else changed the library. Count the
index against the library once per run and rebuild when it is short.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-24 18:00:52 +00:00
Cursor Agentandrmcrackan afd970c672 Update MSTest to 4.3.3
Libation already runs its tests on Microsoft.Testing.Platform via the MSTest
metapackage; this only moves the version off 4.2.2 so all three repos name the
same MSTest release.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-19 18:17:57 +00:00
Cursor Agentandrmcrackan 4187712c8d Add HasSubtitle and TitleHasColon search fields
<title short> stops at the first colon, so it shortens Audible titles that
contain one just as readily as it drops Audible's subtitle, and distinct books
then collapse onto the same name. A colon cannot be searched for: the analyzer
discards punctuation and Lucene reads a colon in a query as a field separator.
Two bool index fields find the affected books instead.

Document how the two title tags differ, since <audible title> already drops
Audible's subtitle without ever cutting a title, and how to audit for names
that actually collide in a spreadsheet export.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 23:43:53 +00:00
Cursor Agentandrmcrackan bb55fb40a3 Stop the filter box looping on dialogs when the search index is at fault
Both grids restored the last good filter by recursing into the filter handler, which
never terminated once the search index rather than the query was the problem: the
restore fails the same way, and the retry uses the same filter. The user got an
endless run of dialogs, each of them blaming a filter string that was fine. Only an
empty last-good filter broke the loop, because that short-circuits before reaching
the search engine.

The fallback is now a bounded sequence -- last good filter, then no filter -- and
the message distinguishes an index Libation cannot reach from a query it cannot
parse. Only the first failure is reported, so restoring is quiet. A malformed query
never surfaces as an IO-family exception, which QueryFailureShapeTests pins against
the real engine, so a typo is never mistaken for index trouble or made to trigger a
rebuild.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 18:06:40 +00:00
Cursor Agentandrmcrackan 77bbc1b0a0 Treat a Windows sharing violation on write.lock as a lock conflict
Windows CI caught real over-reach. When another holder has write.lock, Windows
raises the sharing violation before Lucene can turn it into a
LockObtainFailedException, so it arrives as a plain IOException. Repairing anything
that is not a recognised lock conflict then meant deleting the index the other
holder was using -- exactly the second-instance case the retry exists for.

An IOException naming Lucene's write lock now counts as a lock conflict. Matching
the file name rather than the message wording keeps it working on non-English
Windows. The end-to-end test asserts the property instead of the exception type,
since the type legitimately differs by platform.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 17:25:01 +00:00
Cursor Agentandrmcrackan 4409fb6801 Make the lock conflict test deterministic on Windows
Releasing the write.lock part way through the retry budget raced with Lucene 3's
own lock bookkeeping: on Windows a competing Obtain left a handle on the file, so
Release and the temp directory cleanup both failed with a sharing violation. Hold
the lock for the whole budget instead and assert what actually matters, that a lock
conflict is retried and leaves the index files alone.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 17:14:57 +00:00
Cursor Agentandrmcrackan cacff3c71b Cover a garbled segments.gen in the search index recovery tests
Lucene 3's base-36 filename formatter overruns its buffer when segments.gen names
an absurd generation, so the rebuild path has to survive an IndexOutOfRangeException
as well as the IOException shapes. Damage does not always announce itself as IO.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:30:46 +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
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
Michael Bucari-Tovo df531de255 Update Avalonia to v12
Avalonia 12 uses compiled bindings by default. Converted all remaining reflection bindings into compiled bindings,

Fixed binding errors
2026-05-04 21:08:26 -06:00
Jo-Be-Co 7dd55a2545 Refactorings 2026-03-22 13:55:24 +01:00
Michael Bucari-Tovo 3ab1edc076 Code Cleanup
Make fields readonly
Remove unnecessary casts
Format document
Remove unnecessary usings
Sort usings
Use file-level namespaces
Order modifiers
2026-02-05 12:48:44 -07:00
Michael Bucari-Tovo d67692355f Enable project-wide nullable reference types
Added DB migration for nullable types.
2026-02-05 12:43:05 -07:00
MBucari e850465ec1 Add more null safety
Enable project-wide nullable on LibationUiBase and LibationAvalonia

Explicitly parallelize unit tests
2025-12-30 13:17:11 -07:00
Youssef1313 03ed8e6b57 Migrate from VSTest to MTP 2025-12-21 15:03:52 +01:00
MBucari fde78f4167 Update to .NET 10
- Update all project runtime targets
- Update all dependencies
  - NOTE: Using Npgsql.EntityFrameworkCore.PostgreSQL RTM build from MyGet
- Delete unused pubxml files (they were made redundant by recent workflow changes)
- Replace Libation.sln with Libation.slnx
2025-11-20 15:56:47 -07:00
Michael Bucari-Tovo f98adef9e9 Update Dependencies
- Remove package references that are already included transitively
- Change Avalonia.ReactiveUI to ReactiveUI.Avalonia
2025-11-03 09:34:23 -07:00
Michael Bucari-Tovo eda100b7ac Remove FluentAssertions 2025-08-15 10:29:23 -06:00
Michael Bucari-Tovo 05fad01624 Update dependencies 2025-08-14 15:57:35 -06:00
rmcrackan 0045202334 update dependencies 2025-08-13 07:52:13 -04:00
rmcrackan 72f92ec6c0 update dependencies 2025-08-10 11:21:34 -04:00
MBucari 5b9bf2fbb0 Remove duplicate tests 2025-07-14 12:53:47 -06:00
MBucari 9b1ce8c1d7 Update dependencies 2025-07-14 12:43:53 -06:00
MBucari 1777dc5a7e Update AAXClean.Codecs and dependencies 2025-04-25 19:52:51 -06:00
Robert McRackan 71afb5c9f4 incr ver 2025-03-18 21:09:27 -04:00
Robert McRackan 614965e1ab incr ver 2025-03-10 19:09:44 -04:00
Robert McRackan 5cb22cfd24 Update AAXClean.Codecs 2025-02-17 20:07:50 -05:00
Robert McRackan c7279574a9 Upgrade avalonia 2025-01-07 08:00:35 -05:00
Robert McRackan 6134becc70 Upgrade to .net9 2024-11-22 16:47:59 -05:00
Robert McRackan 92ee0b2e6d update dependencies 2024-09-11 07:20:54 -04:00
Robert McRackan f9340db90a update references. audible api bugfix 2024-05-15 06:55:25 -04:00
Robert McRackan 93ccc206ef Update dependencies 2024-03-04 14:02:42 -05:00
Robert McRackan b6d1a7e3ba Upgrade to .net8 2023-11-15 19:53:26 -05:00
Michael Bucari-Tovo 59689cb647 Update Dependencies 2023-08-30 11:59:40 -06:00
Robert McRackan f733079a49 remove Moq 2023-08-10 15:24:43 -04:00
Robert McRackan 15ad753fa1 update dependencies 2023-07-14 20:58:26 -04:00
Mbucari ec9d11cf52 Fix query parsing tags with underscores (#655) 2023-07-05 15:47:37 -06:00
Mbucari ea3d96329b Add query sanitization unit tests 2023-06-11 09:44:21 -06:00
Mbucari 541cf79b6f Redesign query sanitizer (#618) 2023-06-10 15:08:50 -06:00
Robert McRackan 265794bae0 update dependencies 2023-05-19 11:24:47 -04:00
Robert McRackan 378cf7057e updated to AudibleApi v8 2023-02-24 13:12:18 -05:00
Robert McRackan 73533c58a8 update dependencies 2023-02-13 21:14:56 -05:00
Robert McRackan b668cff0ac Update dependenciesd. Build is broken until the ambiguous ref.s moved into Dinah.Core are resolved 2023-01-24 22:40:08 -05:00