6.5 KiB
6.5 KiB
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, andmdbook build docsall pass. See "Deviations" at the end.
Store layer (crabidy-server/src/crabidy_store.rs)
- T1 —
OrphanEntry+StoreIndex::remove. Move/defineOrphanEntry(name + display metadata) and addStoreIndex::remove(&mut self, name, sidecar), the inverse ofinsert(dropby_hash[hash]and eachby_provider_id[(provider,id)]that still maps toname). Verify: unit test thatinsertthenremoveleaves the index empty; clippy/fmt. - T2 —
list_orphans(&self, ref_roots) -> Result<Vec<OrphanEntry>, StoreError>. Scanstore_rootfor*.cbd-store.toml(name + parsed sidecar); require the audio file present unless reporting malformed residue (G5). Build the referenced set by walking eachref_rootrecursively for*.cbd-track.toml, parsing each and collectingPlayable::Store(name)(skip unreadable/bad files with a warning). Return all − referenced, metadata fromproviders[0]. Verify: G1–G6 tests. - T3 —
orphan_track/orphan_url.orphan_track(name)reads the sidecar and builds aTrack(metadata fromproviders[0],is_captured = true, path left to the provider to set).orphan_url(name)returnsstore_root/<name>as a string, erroringNoDir/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 ifname_taken(new)(G13); rename audio and sidecar (tokio::fs::renameboth, cross-device fallback to copy+remove likeingest_file); read the sidecar,index.remove(old, &sidecar),index.insert(new, &sidecar). Verify: G11–G13 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 onNotFound),index.remove(name, &sidecar). Verify: G15–G16 tests.
Provider (crabidy-server/src/orphans.rs)
- T6 —
entry_name+ leaf/root nodes. Fillentry_name(decode the one segment; reject bare root, deeper, or separator-bearing paths →None). Implementget_lib_rootandget_lib_node: root lists children fromstore.list_orphans(&self.ref_roots)(flags per G7, path/orphans/<encode(name)>, titlename); leaf returns a childless queueable node with the singleorphan_track(path set to the leaf path). Verify: G7/G8 tests;MalformedPathfor 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, G11–G17 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/fsroot 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 anypub use) tocrabidy-server/src/lib.rs; this un-inerts the stub. Verify: workspace compiles. - T10 — Mount + route
/orphans. Addorphans_client: Option<Arc<OrphansProvider>>toProviderOrchestrator; construct it ininitwhencrabidy_storeisSome, withref_roots=[store.tree_dir()]plusfs_client.disk_root()when present. Addorphans_owns(path); routeis_track_path,get_lib_node,get_urls_for_track,get_metadata_for_track,create/rename/delete_lib_node, andresolve_tracks_intoto it. Add theorphanschild toget_lib_rootwhen mounted. Note: keepannotate_capturedrunning on orphan nodes (already correct — store-backed tracks areis_captured). Verify: G21 by reading; workspace compiles; existing provider tests still pass.
Docs
- T11 — mdbook + README. Add an
/orphanssection todocs/src/store.md(or a shortdocs/src/providers/orphans.mdlinked fromSUMMARY.md) and a line toREADME.md's provider tree describing/orphansas 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-readquality/orphans.mdG1–G21 and check each. Updateplan/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.rsexerciselist_orphans/orphan_track/orphan_url/rename/delete end-to-end through theProviderClientsurface (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 G1–G21 are covered; the split just differs from the per-method layout the plan implied. list_orphansenumerates a union (G5). It walks both*.cbd-store.tomlsidecars 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 inproviders.mdand a line in the README provider tree, rather than a standalonedocs/src/providers/orphans.mdpage —/orphansis a view over the store, so it reads best beside the store doc, and no newSUMMARY.mdentry was needed. - No proto/TUI/web changes (G10 held). The feature reuses the existing
is_editable/is_deletable/is_queablechild handling end-to-end; the only non-server change is the additivefsdy::Client::disk_rootaccessor.