115 lines
6.5 KiB
Markdown
115 lines
6.5 KiB
Markdown
# 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`)
|
||
|
||
- [x] **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.
|
||
- [x] **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:* G1–G6 tests.
|
||
- [x] **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.
|
||
- [x] **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:* G11–G13 tests.
|
||
- [x] **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:* G15–G16 tests.
|
||
|
||
## Provider (`crabidy-server/src/orphans.rs`)
|
||
|
||
- [x] **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).
|
||
- [x] **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`)
|
||
|
||
- [x] **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`)
|
||
|
||
- [x] **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.
|
||
- [x] **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
|
||
|
||
- [x] **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
|
||
|
||
- [x] **T12 — Full verification.** `devenv shell -- cargo test --workspace`,
|
||
`cargo clippy --workspace -- -D warnings`, `cargo fmt --check`, markdownlint.
|
||
Re-read `quality/orphans.md` G1–G21 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 G1–G21 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.
|