fix(voice): cascade delete, rename, and move folder operations to descendants #16

Merged
salmaelsoly merged 1 commit from development_delete_folder_recursive into development 2026-04-23 09:19:55 +00:00
Member

Summary

Fixes three related bugs in VoiceService folder operations where mutations to a folder were not propagated to its descendants.

  • delete_folder only removed the folder's own DB row. Child topics, sub-folders, and their on-disk audio/transform files stayed behind as orphans.
  • rename_folder updated only the target folder's name. Descendants' parent_path kept pointing at the old path, making them unreachable from the tree.
  • move_folder updated only the target folder's parent_path. Same descendants-go-stale problem, and nothing stopped a move into the folder's own subtree (which would create a cycle).

Closes #12

Changes

All in crates/hero_voice/src/voice/server/rpc.rs. No UI, SDK, schema, or generated-code touches.

  • Added folder_full_path(&Folder) -> String — one place for the parent_path + "/" + name convention used by the UI's tree builder.
  • Added rewrite_path(path, old_prefix, new_prefix) -> Option<String> — returns a rewritten path iff the input starts with old_prefix as a whole path segment (exact match or followed by /). Avoids the starts_with footgun where "work" would wrongly match "work_backup".
  • Added module-level const MAX_FOLDER_DEPTH: usize = 64 — depth budget for cycle protection.
  • Rewrote delete_folder to cascade:
    1. Load the target (preserving the original NotFound for unknown sids).
    2. Delete every direct-child topic via the existing delete_topic so audio-dir and transforms-dir cleanup stays in one place.
    3. Recurse into sub-folders via a new private helper delete_folder_children (snapshot-then-iterate, depth-budgeted).
    4. Finally delete the target folder's own record.
  • Rewrote rename_folder to capture old/new full path, persist the renamed folder, then cascade the path change to every descendant.
  • Rewrote move_folder to capture old full path, reject self/descendant destinations with InvalidInput (tree-cycle guard), persist, then cascade.
  • Added a new private method VoiceServiceHandler::propagate_path_change(old_prefix, new_prefix) used by both rename_folder and move_folder.

Test Results

Command: cargo test -p hero_voice --features voice

  • Total: 15
  • Passed: 15
  • Failed: 0

Two new integration tests:

  • delete_folder_cascades — covers empty folder, folder with one topic (DB + fs verification), nested sub-tree, and sibling isolation.
  • rename_and_move_folder_cascades — covers rename propagation through two nesting levels, sibling isolation against prefix-only matches, move propagation for a mid-tree folder, and rejection of moves into self or descendants.

Both tests share a module-level Mutex to serialise std::env::set_current_dir (needed because rpc.rs still uses the relative DATA_DIR = "data" path).

cargo clippy -p hero_voice --features voice --all-targets -- -D warnings passes.

## Summary Fixes three related bugs in `VoiceService` folder operations where mutations to a folder were not propagated to its descendants. - `delete_folder` only removed the folder's own DB row. Child topics, sub-folders, and their on-disk audio/transform files stayed behind as orphans. - `rename_folder` updated only the target folder's `name`. Descendants' `parent_path` kept pointing at the old path, making them unreachable from the tree. - `move_folder` updated only the target folder's `parent_path`. Same descendants-go-stale problem, and nothing stopped a move into the folder's own subtree (which would create a cycle). ## Related Issue Closes https://forge.ourworld.tf/lhumina_code/hero_voice/issues/12 ## Changes All in `crates/hero_voice/src/voice/server/rpc.rs`. No UI, SDK, schema, or generated-code touches. - Added `folder_full_path(&Folder) -> String` — one place for the `parent_path + "/" + name` convention used by the UI's tree builder. - Added `rewrite_path(path, old_prefix, new_prefix) -> Option<String>` — returns a rewritten path iff the input starts with `old_prefix` as a whole path segment (exact match or followed by `/`). Avoids the `starts_with` footgun where `"work"` would wrongly match `"work_backup"`. - Added module-level `const MAX_FOLDER_DEPTH: usize = 64` — depth budget for cycle protection. - Rewrote `delete_folder` to cascade: 1. Load the target (preserving the original `NotFound` for unknown sids). 2. Delete every direct-child topic via the existing `delete_topic` so audio-dir and transforms-dir cleanup stays in one place. 3. Recurse into sub-folders via a new private helper `delete_folder_children` (snapshot-then-iterate, depth-budgeted). 4. Finally delete the target folder's own record. - Rewrote `rename_folder` to capture old/new full path, persist the renamed folder, then cascade the path change to every descendant. - Rewrote `move_folder` to capture old full path, reject self/descendant destinations with `InvalidInput` (tree-cycle guard), persist, then cascade. - Added a new private method `VoiceServiceHandler::propagate_path_change(old_prefix, new_prefix)` used by both `rename_folder` and `move_folder`. ## Test Results Command: `cargo test -p hero_voice --features voice` - Total: 15 - Passed: 15 - Failed: 0 Two new integration tests: - `delete_folder_cascades` — covers empty folder, folder with one topic (DB + fs verification), nested sub-tree, and sibling isolation. - `rename_and_move_folder_cascades` — covers rename propagation through two nesting levels, sibling isolation against prefix-only matches, move propagation for a mid-tree folder, and rejection of moves into self or descendants. Both tests share a module-level `Mutex` to serialise `std::env::set_current_dir` (needed because `rpc.rs` still uses the relative `DATA_DIR = "data"` path). `cargo clippy -p hero_voice --features voice --all-targets -- -D warnings` passes.
fix(voice): cascade delete, rename, and move folder operations to descendants
All checks were successful
Build / build (pull_request) Successful in 7m39s
c480a70897
salmaelsoly force-pushed development_delete_folder_recursive from c480a70897
All checks were successful
Build / build (pull_request) Successful in 7m39s
to 107f6103a7
All checks were successful
Build / build (pull_request) Successful in 4m36s
2026-04-23 08:04:09 +00:00
Compare
thabeta approved these changes 2026-04-23 09:16:54 +00:00
salmaelsoly merged commit a20051ae91 into development 2026-04-23 09:19:55 +00:00
salmaelsoly deleted branch development_delete_folder_recursive 2026-04-23 09:20:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_voice!16
No description provided.