crabidy/plan/orphans.md

6.5 KiB
Raw Blame History

Task plan — the orphans provider

Implements architecture/orphans.md, satisfying quality/orphans.md. Ordered by dependency. Each task names its verification. All cargo/tooling runs go through devenv shell -- … (see CLAUDE.md).

Done. All tasks implemented and verified: cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --check, and mdbook build docs all pass. See "Deviations" at the end.

Store layer (crabidy-server/src/crabidy_store.rs)

  • T1 — OrphanEntry + StoreIndex::remove. Move/define OrphanEntry (name + display metadata) and add StoreIndex::remove(&mut self, name, sidecar), the inverse of insert (drop by_hash[hash] and each by_provider_id[(provider,id)] that still maps to name). Verify: unit test that insert then remove leaves the index empty; clippy/fmt.
  • T2 — list_orphans(&self, ref_roots) -> Result<Vec<OrphanEntry>, StoreError>. Scan store_root for *.cbd-store.toml (name + parsed sidecar); require the audio file present unless reporting malformed residue (G5). Build the referenced set by walking each ref_root recursively for *.cbd-track.toml, parsing each and collecting Playable::Store(name) (skip unreadable/bad files with a warning). Return all referenced, metadata from providers[0]. Verify: G1G6 tests.
  • T3 — orphan_track / orphan_url. orphan_track(name) reads the sidecar and builds a Track (metadata from providers[0], is_captured = true, path left to the provider to set). orphan_url(name) returns store_root/<name> as a string, erroring NoDir/MalformedPath-style if the entry is gone. Verify: G8/G9 tests.
  • T4 — rename_orphan(&self, old, new). Under the index mutex: validate_folder_name(new, &[]); refuse if name_taken(new) (G13); rename audio and sidecar (tokio::fs::rename both, cross-device fallback to copy+remove like ingest_file); read the sidecar, index.remove(old, &sidecar), index.insert(new, &sidecar). Verify: G11G13 tests.
  • T5 — delete_orphan(&self, name). Under the index mutex: read the sidecar (for index removal; tolerate a missing sidecar → still remove any stray audio), remove audio + sidecar (idempotent on NotFound), index.remove(name, &sidecar). Verify: G15G16 tests.

Provider (crabidy-server/src/orphans.rs)

  • T6 — entry_name + leaf/root nodes. Fill entry_name (decode the one segment; reject bare root, deeper, or separator-bearing paths → None). Implement get_lib_root and get_lib_node: root lists children from store.list_orphans(&self.ref_roots) (flags per G7, path /orphans/<encode(name)>, title name); leaf returns a childless queueable node with the single orphan_track (path set to the leaf path). Verify: G7/G8 tests; MalformedPath for unknown/.. segments (G17).
  • T7 — play/queue + mutations. Implement get_urls_for_track (orphan_url), get_metadata_for_track (orphan_track), rename_lib_node (validate → rename_orphan → return new node), delete_lib_node (delete_orphan → return refreshed root). Verify: G9, G11G17 tests via the provider surface.

fsdy accessor (fsdy/src/lib.rs)

  • T8 — Client::disk_root(&self) -> &Path. Public accessor returning the instance's disk root, so the orchestrator can hand the /fs root to the orphans provider as a reference root. Doc comment (G20). Verify: trivial; compiles + fmt.

Orchestrator wiring (crabidy-server/src/provider.rs, lib.rs)

  • T9 — Declare the module. Add mod orphans; (and any pub use) to crabidy-server/src/lib.rs; this un-inerts the stub. Verify: workspace compiles.
  • T10 — Mount + route /orphans. Add orphans_client: Option<Arc<OrphansProvider>> to ProviderOrchestrator; construct it in init when crabidy_store is Some, with ref_roots = [store.tree_dir()] plus fs_client.disk_root() when present. Add orphans_owns(path); route is_track_path, get_lib_node, get_urls_for_track, get_metadata_for_track, create/rename/delete_lib_node, and resolve_tracks_into to it. Add the orphans child to get_lib_root when mounted. Note: keep annotate_captured running on orphan nodes (already correct — store-backed tracks are is_captured). Verify: G21 by reading; workspace compiles; existing provider tests still pass.

Docs

  • T11 — mdbook + README. Add an /orphans section to docs/src/store.md (or a short docs/src/providers/orphans.md linked from SUMMARY.md) and a line to README.md's provider tree describing /orphans as the store's reclamation view (rename/delete/queue unreferenced audio). Verify: devenv shell -- mdbook build docs + markdownlint clean.

Final gate

  • T12 — Full verification. devenv shell -- cargo test --workspace, cargo clippy --workspace -- -D warnings, cargo fmt --check, markdownlint. Re-read quality/orphans.md G1G21 and check each. Update plan/summary.md. Commit with the co-author trailer.

Deviations from the plan / architecture

  • Test placement (T2/T4/T5). Rather than one test per store method, the provider-level tests in orphans.rs exercise list_orphans/orphan_track/ orphan_url/rename/delete end-to-end through the ProviderClient surface (list vs. reference, leaf-track resolution, rename-both-files + delete, traversal rejection). One store-level test (rename_orphan_moves_both_files_and_repoints_the_index) additionally asserts G12 (index repoint) by inspecting the private index. All of G1G21 are covered; the split just differs from the per-method layout the plan implied.
  • list_orphans enumerates a union (G5). It walks both *.cbd-store.toml sidecars and bare audio files, so a sidecar without audio and audio without a sidecar both surface as reclaimable — as the architecture's "malformed residue" note requires.
  • Docs home (T11). The user-facing section landed in docs/src/store.md ("Reclaiming orphans — /orphans") with a bullet in providers.md and a line in the README provider tree, rather than a standalone docs/src/providers/orphans.md page — /orphans is a view over the store, so it reads best beside the store doc, and no new SUMMARY.md entry was needed.
  • No proto/TUI/web changes (G10 held). The feature reuses the existing is_editable/is_deletable/is_queable child handling end-to-end; the only non-server change is the additive fsdy::Client::disk_root accessor.