Parsing HTML belongs before the data reaches the data layer, not in it. Move the
flattening to AudibleUtilities, where the rest of the Item translation lives, so
Book.Description holds plain text and DataLayer no longer references
HtmlAgilityPack.
Import now updates a description as well as creating one, which it never did
before: books imported while descriptions were stored as raw HTML are still
holding markup, and a re-scan is what clears it. A scan that reports no summary
leaves the stored one alone.
This also cleans up the CSV/Excel export, which was emitting raw HTML too.
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
CsvHelper was only used to write headers and flat DTO rows in the csv
exports. The new internal writer reproduces its output byte-for-byte:
the delimiter is the culture's list separator, records end with CRLF,
fields containing the delimiter, quotes, newlines, or leading/trailing
spaces are quoted per RFC 4180, and values are formatted with the
culture. The [Name]/[Ignore] attributes it replaced are now two tiny
local attributes.
Verified byte-identical output against CsvHelper 33.1.0 for both
exporters in en-US, de-DE (';' delimiter), and invariant cultures, and
for a seeded 30-book demo library export before/after.
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
A new DtoImporterService.Tests pinned MSTest 4.2.2, copied from a project file
master has since moved to 4.3.3, so the merge held nine test projects at 4.3.3
and one at 4.2.2. NuGet lifted the shared MSTest.TestFramework to 4.3.3 while the
4.2.2 metapackage kept supplying the adapter, and the adapter refuses to run
against a framework of a different version: 'Zero tests ran' on every platform.
ApplicationServices references DtoImporterService, so its test project already
sees the importers and needs no package reference of its own. One fewer project
file to keep in step is the point.
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
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>
scanAccountsAsync swallows a per-account failure so the remaining accounts can
still scan. That is right for importing, which only adds and updates, but
FindInactiveBooks treats whatever came back as the whole library and reports
every un-liberated book missing from it as no longer in the account. For a
single-account user a login or network failure therefore yields an empty scan,
and 'Remove Books' pre-checks the entire un-liberated library. ImportAccountAsync
already guards itself with 'if (totalCount == 0) return default;'; this path had
no equivalent.
Track which accounts failed and refuse to identify inactive books when any did,
or when a scan returns nothing while books are at stake. Both grids already
catch and report a failed scan with 'Error scanning library. You may still
manually select books to remove', so throwing lands in the right place and
leaves nothing checked.
Also log how many books the scan found absent, so the count behind a removal
prompt is visible after the fact.
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
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>
The classifier's inputs are the actual denials from the log attached to issue
#1947: owned titles on an inactive account, a Plus title no longer in the
catalog, and an unreleased preorder Audible has no audio for.
The backoff tests also caught a real overflow: first * 2^n exceeds a TimeSpan
long before the cap matters.
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
Avalonia and WinForms both get a Daily download limit group on the
Download/Decrypt tab: scope drop-down, and when a limit is chosen a quantity
spinner (minimum 1, no practical maximum) plus a Books/MB/GB unit, with the
MB/GB approximation note shown only for those units.
A license denial that looks like Audible throttling now suggests turning the
limit on, quoting the real number of downloads Libation recorded in the last 24
hours. Audible reports no distinct throttling reason, so the suggestion stays
silent unless that record makes throttling plausible.
Co-authored-by: rmcrackan <rmcrackan@gmail.com>