An owned row outlives the file it was filed from #216

Closed
opened 2026-09-12 19:07:20 +02:00 by Claude · 1 comment
Collaborator

Delete a stack part or an alternate-version file while its owner stays, and the row lingers. The removal sweep reaches an owned row only through its owner: crates/ephyra-db/src/scan/removal.rs:122-127 finds owned rows by owner, so a rescan that still sees the owner never asks whether the owned file is still there.

Upstream cleans them in RefreshMetadataForOwnedVideo — the owned rows are rebuilt from what is on disk each refresh, so one whose file is gone is not rebuilt.

Shape

  • /movies/Concert - cd1.mp4 + cd2.mp4, then delete cd2: the part row stays, and the owner keeps naming it in additional_parts.
  • Since #207, the same is true of a deleted alternate version.

Pre-existing for stack parts; #207 gives it a second shape, which is how it was found.

What it costs

A row pointing at a file that is not there. It is reachable by id, so a client that has the id gets a playable-looking item that 404s on stream.

Verifying

crates/ephyra-app/tests/scan_write.rs gained a filed() helper for #207 that resolves a listing through skeleton + library into an in-memory database with no snapshot needed — a two-scan test (file the stack, drop a file, scan again, assert the row is gone) fits there and runs everywhere.

Found reviewing #207.

Delete a stack part or an alternate-version file while its owner stays, and the row lingers. The removal sweep reaches an owned row only through its owner: `crates/ephyra-db/src/scan/removal.rs:122-127` finds owned rows by owner, so a rescan that still sees the owner never asks whether the owned file is still there. Upstream cleans them in `RefreshMetadataForOwnedVideo` — the owned rows are rebuilt from what is on disk each refresh, so one whose file is gone is not rebuilt. ## Shape - `/movies/Concert - cd1.mp4` + `cd2.mp4`, then delete `cd2`: the part row stays, and the owner keeps naming it in `additional_parts`. - Since #207, the same is true of a deleted alternate version. Pre-existing for stack parts; #207 gives it a second shape, which is how it was found. ## What it costs A row pointing at a file that is not there. It is reachable by id, so a client that has the id gets a playable-looking item that 404s on stream. ## Verifying `crates/ephyra-app/tests/scan_write.rs` gained a `filed()` helper for #207 that resolves a listing through `skeleton` + `library` into an in-memory database with no snapshot needed — a two-scan test (file the stack, drop a file, scan again, assert the row is gone) fits there and runs everywhere. Found reviewing #207.
Author
Collaborator

Fixed in be3827e4, landed on main at 53ac9c6c.

The removal sweep reaches owned rows directly: rows with an owner and no extra type, whose owner belongs to the library. That is how a part carrying no TopParentId is found at all. The planner change beside it stops an owner naming a part that is gone, so additional_parts no longer points at a missing file.

What the sweep does not take, checked while reviewing: a version merged by hand (it carries a primary and no owner), a row owned by an item in another library, and an extra — the same line upstream's refresh draws.

Verified by a native two-pass scan of the whole fixture tree and by scan tests that drop a file between passes.

One case the sweep still gets wrong is older than this fix and now reaches owned rows too: a library whose media cannot be listed at all — an unmounted share — sweeps its rows away. Filed as #231.

Fixed in `be3827e4`, landed on `main` at `53ac9c6c`. The removal sweep reaches owned rows directly: rows with an owner and no extra type, whose owner belongs to the library. That is how a part carrying no `TopParentId` is found at all. The planner change beside it stops an owner naming a part that is gone, so `additional_parts` no longer points at a missing file. What the sweep does not take, checked while reviewing: a version merged by hand (it carries a primary and no owner), a row owned by an item in another library, and an extra — the same line upstream's refresh draws. Verified by a native two-pass scan of the whole fixture tree and by scan tests that drop a file between passes. One case the sweep still gets wrong is older than this fix and now reaches owned rows too: a library whose media cannot be listed at all — an unmounted share — sweeps its rows away. Filed as #231.
Sign in to join this conversation.
No description provided.