A by-name item's id depends on where the metadata directory sits #67
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?
A by-name item's id depends on where the metadata directory sits.
/Personsagainst a production library: upstream lists 6 people, this serverlists 99, and none of the ids agree — upstream's
2 Chainzis9bca90d85c196e4aff33de16eb67bb88, ours isa7dbc42606c1dd92df8f38fe4e33f766.Both rows exist in the same database, with the same
Path.GetNewItemIdInternal(LibraryManager.cs:782) makes the path relative to theprogram data directory before hashing — but only when it is under it. The
reference container bind-mounts its metadata directory outside its
ProgramDataPath, so upstream hashes the absolute path there and this porthashes the relative form unconditionally.
The database holds both generations: the ids the production install wrote in
2024 (relative, which we reproduce) and the ids the container's own validators
wrote in 2026 (absolute, which upstream now finds).
GetPersonlooks a name upand never creates one, so upstream sees only the 6 it wrote itself.
So this is not a hashing bug — it is a missing condition. Matching upstream means
deciding the form the way it does, from whether the metadata directory lies under
the data directory.
Accounts for 20 of the 23 differences the
bundlescenario reports. It appliesto genres, studios and artists as well as people; people are simply where the
two generations differ.
Fixed. The id now follows where the two directories sit, as upstream's does.
GetNewItemIdInternal(LibraryManager.cs:782) strips the program datadirectory only when the key starts with it, as a raw ordinal prefix — then trims
a leading separator and turns every
/into\. An absolute key keeps itsforward slashes, and that asymmetry is load-bearing. A by-name key is always
lowercased (
EnableNormalizedItemByNameIds, default true), the prefix is the.NET type's full name, and the hash is MD5 over UTF-16LE read as a little-endian
guid.
Both forms reproduce the answer key exactly:
The two databases cannot both be matched, and that is the finding. The
production database holds two generations — rows the 2024 install wrote with
metadata inside its data directory (relative form) and rows the container's own
validators wrote in 2026 with metadata bind-mounted outside it (absolute).
MusicGenresplits almost exactly in half between them. Agreement with thecontainer-laid-out database therefore falls from 97.82 % to 2.18 %, while
agreement with
var/big-upstream— metadata inside/config— holds at99.82 % before and after.
What matters is the running server, and there the harness settles it: the
bundlescenario went from 23 differences to 0, the 20/Personslinesamong them included.
jellyfin-corelearns nothing about configuration; it takes the two paths asstrings, exactly as the existing item-id derivation already did.
One caveat worth recording: a config whose
paths.datanames thedatasubdirectory rather than the program data directory now selects the absolute
form and disagrees with an upstream-laid-out install. The shipped default is
correct; a scratch config may not be.