A music library re-asks four databases and re-downloads its covers on every scan #155
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Every scan re-asks all four databases about every album and every artist, and
re-downloads the artwork it already has.
Two independent causes, and either alone is enough.
The folder is never short-circuited.
crates/jellyfin-app/src/scan/summary.rs:437calls
folders(&on_record, asking.is_some()), which bypasses thedescribes()short-circuit whenever any library has
fetch_metadataon. Soplan.rs::describerunsasked_aboutfor everyMusicAlbumandMusicArtiston every pass — MusicBrainz for the release, the release group and the artist,
then AudioDb for each — with MusicBrainz paced at one request a second by
jellyfin_fetch::Live.The picture is not seen as already there.
saved()(
crates/jellyfin-app/src/scan/run/remote.rs:276) builds itsalreadyset frombeside, which comes fromread()'s pass over the library listing. A coverfetched on the last scan lives at
metadata/library/<shard>/folder.jpg, whichis not in that listing, so the kind is never marked taken and the file is
fetched and rewritten every pass.
A 500-album library therefore spends on the order of 2000 requests and ~35
minutes of pacing per scan, indefinitely, and rewrites every cover it already
had. A film does not have this problem: it goes through
read(), whosealreadyis the pictures found beside the file.Where to start
The two halves are separable and the second is the cheaper one. For the
pictures, the question is what
alreadyshould be — the listing plus what themetadata directory holds for that item, which is what
ImageStore::modifiedalready answers for a portrait in
stood_up.For the folder, the question is what
describes()should mean for a row whosedatabases have already answered. Note the constraint that makes it non-obvious:
an album is identified by ids its tracks carry, so the row has to be
summarised before anything can be asked — which is why
asked_aboutruns whereit does.
How to know it is done
A second scan of an unchanged music library makes no request and writes no
picture, and a third one after a track's tags change still asks. The pair in
crates/jellyfin-app/tests/scan_enriched.rsthat rescans (rescanning/again)is the shape this is measured in.
House rules
CLAUDE.mdis the short version and these are the parts that bite: tests rununder
cargo nextest; upstream's source is the checkout inupstream/at thepin (
v12.0-rc5,6d501ba4) and not the clone beside the repository;generated files are regenerated rather than edited; a comment says what the code
does, not how it got there; and
Dnreferences stay out of code and comments.Landed in
b45ce06, with corrections inb5de663.The folder was never short-circuited — and the reason it was not turned out
to be deeper than the
askingflag. A folder the pass did not read carries abare row, so
describes()was comparing provider-enriched stored data againsta summary of the tracks and could never hold again once anything had answered.
The flag was not the cause, it was the workaround.
So the comparison had to change, not just its guard. A row a database has
answered about —
DateLastRefreshed, now carried onTracked— is asked tocarry what the tracks say rather than to equal it, where the pass read nothing
under the folder. Where it read something the lists are compared exactly,
because a track can lose a value as easily as gain one. The running time is
exact either way, which is what a track leaving is noticed by.
The cover was not seen as already there.
savedbuilt what is taken fromthe library listing, and an album's cover lives under the metadata directory,
which no listing carries. It asks the store now, the way
stood_upalready didfor a portrait. A kind with nowhere to be written is also settled before the
fetch rather than after, so the bytes are no longer downloaded and dropped.
Two regressions the review caught, both mine and both in the first version
of the gate:
that the pass had disturbed the folder and that the row had drifted — and
nothing reads a file that is gone. The row would have kept the deleted track's
running time forever.
what the short-circuit reads — so one unreachable pass meant that folder was
never asked about again.
fetched_musicreports whether it reached them andan unreached folder is left unstamped, which is the distinction #154 already
made for a person.
On measuring it. The first version of the rescan test asserted "asks nothing
and rewrites nothing" on one pass and passed — then passed again with the
picture fix reverted, because once the folder is short-circuited that pass never
reaches the artwork at all. The artwork is asserted on the rescan that does
ask instead. Six tests, each checked against the mutation it exists to catch.
One test was deleted rather than made to pass: it asserted an album drops a
genre when a track is re-tagged, which contradicts a rule this suite already
documents — the genres are the row's, not the tag's.
What is left is filed as #158: a rescan of an unchanged library is now
silent except for three requests, all of them a series. A film is quiet.