A folder two libraries share is listed under only one of them #33

Closed
opened 2026-08-03 08:22:36 +02:00 by Claude · 1 comment
Collaborator

Two libraries can point at one folder. Both then file an ancestor chain for
every item under it, and save writes a chain by deleting what the item had:

crates/jellyfin-db/src/scan.rs
    "DELETE FROM AncestorIds WHERE ItemId = ?"

So the second library's chain replaces the first's, and the first lists
nothing. Reproduced against a real database, both orderings — configured
together, or one added after the other has been scanned:

Films = 0    Also Films = 3

A renamed library leaves a second trace of the same shape: the old
CollectionFolder row stays, because remove_missing scopes by TopParentId
and a collection folder's is NULL. After renaming Films to Movies the
database holds both, Films with nothing under it, and both are offered by
/Users/{id}/Views.

What upstream does with either has not been checked. That is the first step:
point two libraries at one path on the reference instance, rename one, and
diff. The port should do whatever upstream does — including, if that is the
answer, the same thing it does now.

crates/jellyfin-app/tests/scan_incremental.rs asserts the current behaviour
on both, so whichever way it goes the test says so.

Two libraries can point at one folder. Both then file an ancestor chain for every item under it, and `save` writes a chain by deleting what the item had: ``` crates/jellyfin-db/src/scan.rs "DELETE FROM AncestorIds WHERE ItemId = ?" ``` So the second library's chain replaces the first's, and the first lists nothing. Reproduced against a real database, both orderings — configured together, or one added after the other has been scanned: ``` Films = 0 Also Films = 3 ``` A renamed library leaves a second trace of the same shape: the old `CollectionFolder` row stays, because `remove_missing` scopes by `TopParentId` and a collection folder's is `NULL`. After renaming `Films` to `Movies` the database holds both, `Films` with nothing under it, and both are offered by `/Users/{id}/Views`. What upstream does with either has not been checked. That is the first step: point two libraries at one path on the reference instance, rename one, and diff. The port should do whatever upstream does — including, if that is the answer, the same thing it does now. `crates/jellyfin-app/tests/scan_incremental.rs` asserts the current behaviour on both, so whichever way it goes the test says so.
Author
Collaborator

Fixed and merged — the first half. The second half was wrongly premised.

What a shared folder hangs under

Measured on upstream first, on a container built from the pinned image: two
libraries over one path give Films = 3, Also Films = 3, and each film
carries four AncestorIds — the physical folder, both collection folders,
and the aggregate root. The physical folder itself hangs from the root alone.

A pass joins the chains it resolves rather than letting the last library replace
the first, and the port now produces that same four-ancestor set.

Where a row hangs is answered once, after every library has spoken. Filing what
one library resolved deleted the other's ancestor and put it back a few hundred
rows later in another transaction — so a pass that failed in between left the
second library listing nothing until a whole pass succeeded, and a rescan that
changed nothing rewrote every shared row's chain, on every scan, for ever.

The rename half is not a defect

Upstream leaves the old CollectionFolder row behind too. Renaming Films to
Movies on the reference instance leaves /Users/{id}/Views answering with
three views — Films with nothing under it, Movies, and Also Films —
while /Library/VirtualFolders answers with two, and all three survive a
restart. This server already does the same. Nothing was changed for it, and the
claim in the issue text above is wrong.

Fixed and merged — the first half. The second half was wrongly premised. ## What a shared folder hangs under Measured on upstream first, on a container built from the pinned image: two libraries over one path give **Films = 3, Also Films = 3**, and each film carries **four** `AncestorIds` — the physical folder, *both* collection folders, and the aggregate root. The physical folder itself hangs from the root alone. A pass joins the chains it resolves rather than letting the last library replace the first, and the port now produces that same four-ancestor set. Where a row hangs is answered once, after every library has spoken. Filing what one library resolved deleted the other's ancestor and put it back a few hundred rows later in another transaction — so a pass that failed in between left the second library listing nothing until a whole pass succeeded, and a rescan that changed nothing rewrote every shared row's chain, on every scan, for ever. ## The rename half is not a defect Upstream leaves the old `CollectionFolder` row behind too. Renaming `Films` to `Movies` on the reference instance leaves `/Users/{id}/Views` answering with **three** views — `Films` with nothing under it, `Movies`, and `Also Films` — while `/Library/VirtualFolders` answers with two, and all three survive a restart. This server already does the same. Nothing was changed for it, and the claim in the issue text above is wrong.
Sign in to join this conversation.
No description provided.