Changing a root's path must move the titles under it #236

Closed
opened 2026-08-25 10:02:28 +01:00 by naps62-yolo · 1 comment
Owner

PUT /api/roots/{id} rewrites a root's path and moves nothing, so every title
under that root is mislocated at once. It is the same defect #228 fixed one
level down, but with a wider blast radius: changing a title's root moved one
folder, changing a root's path silently orphans the whole library.

#228 landed crates/arr-api/src/relocate.rs, which already carries the pieces
this needs — plan every rename before performing any, refuse a destination that
exists, undo the renames performed so far if one fails, and rewrite the
media_files rows in the same transaction as the row change. roots::update in
crates/arr-api/src/roots.rs was deliberately left out of #228's scope and was
not touched.

Scope: changing a root's path relocates every title folder under it and
rewrites the media_files rows that point inside it, or the change is refused
and the row is left alone. Reuse relocate.rs rather than writing a second
mover — two implementations of this will drift.

Guards, same as #228: a destination that already holds a folder of that name is
a conflict, not an overwrite; a failed rename leaves the row unchanged; a root
with no titles under it changes path with no filesystem work. Roots are assumed
to share a filesystem, as DESIGN.md §7.4 now states.

Not in scope: changing a root's kind, which is a separate question.

`PUT /api/roots/{id}` rewrites a root's `path` and moves nothing, so every title under that root is mislocated at once. It is the same defect #228 fixed one level down, but with a wider blast radius: changing a title's root moved one folder, changing a root's path silently orphans the whole library. #228 landed `crates/arr-api/src/relocate.rs`, which already carries the pieces this needs — plan every rename before performing any, refuse a destination that exists, undo the renames performed so far if one fails, and rewrite the `media_files` rows in the same transaction as the row change. `roots::update` in `crates/arr-api/src/roots.rs` was deliberately left out of #228's scope and was not touched. Scope: changing a root's `path` relocates every title folder under it and rewrites the `media_files` rows that point inside it, or the change is refused and the row is left alone. Reuse `relocate.rs` rather than writing a second mover — two implementations of this will drift. Guards, same as #228: a destination that already holds a folder of that name is a conflict, not an overwrite; a failed rename leaves the row unchanged; a root with no titles under it changes path with no filesystem work. Roots are assumed to share a filesystem, as DESIGN.md §7.4 now states. Not in scope: changing a root's `kind`, which is a separate question.
naps62-yolo added this to the Feedback pass 2 milestone 2026-08-25 10:02:28 +01:00
naps62-yolo added the area/apidifficulty/moderatetype/bug labels 2026-08-25 10:02:39 +01:00
Author
Owner

Out of scope here, noticed while implementing this: ApiError::Filesystem renders as files not removed: {error} (crates/arr-api/src/movies.rs:206). That wording predates #228 and was accurate when a failed delete was the only way to reach it. It now also fronts a failed move, so a root whose relocation failed reports files not removed: could not move '/mnt/media/movies/main/Dune (2021)': Permission denied, which reads as the opposite of what happened. A neutral prefix would fix it, but the change lands in movies.rs, outside this issue.

Out of scope here, noticed while implementing this: `ApiError::Filesystem` renders as `files not removed: {error}` (`crates/arr-api/src/movies.rs:206`). That wording predates #228 and was accurate when a failed delete was the only way to reach it. It now also fronts a failed move, so a root whose relocation failed reports `files not removed: could not move '/mnt/media/movies/main/Dune (2021)': Permission denied`, which reads as the opposite of what happened. A neutral prefix would fix it, but the change lands in `movies.rs`, outside this issue.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#236