Rename the track-file extension to .cbd-track.toml

.track.toml was too generic for files that only crabidy understands;
the cbd- prefix makes them unmistakable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Test User 2026-07-21 02:05:54 +02:00
parent 4dd5f01217
commit 955c7ea5b4
3 changed files with 36 additions and 33 deletions

View File

@ -81,9 +81,9 @@ Consequences, accepted deliberately:
resolution. **Link chains are structurally impossible** — see D3's "no
links into `/fs`" rule.
### D3 — On-disk schema: TOML, extension `.track.toml`, exactly one playable
### D3 — On-disk schema: TOML, extension `.cbd-track.toml`, exactly one playable
TOML per project convention. A file named `<anything>.track.toml` inside
TOML per project convention. A file named `<anything>.cbd-track.toml` inside
the configured root is a track node; everything else (other files, hidden
entries) is ignored. Schema:
@ -174,7 +174,7 @@ direction: right
disk: Local disk {
shape: cylinder
tree: "root dir: dirs, *.track.toml"
tree: "root dir: dirs, *.cbd-track.toml"
}
server: crabidy-server {
@ -185,7 +185,7 @@ server: crabidy-server {
}
fsdy: fsdy::Client {
parse: "parse + validate .track.toml"
parse: "parse + validate .cbd-track.toml"
map: "path <-> root-relative file (encoded segments)"
}
@ -218,7 +218,7 @@ pb -> orch: ResolveTracks("/fs/mix", chunk_tx)
orch -> fs: resolve_tracks_into (spawned)
fs -> fs: "list dir, parse 3 track files"
fs -> pb: "chunk of 3 Tracks (paths below)" {style.bold: true}
pb -> orch: "GetTrackUrls(/fs/mix/a.track.toml)"
pb -> orch: "GetTrackUrls(/fs/mix/a.cbd-track.toml)"
orch -> fs: get_urls_for_track
fs -> pb: "[/home/u/Music/a.flac]"
pb -> orch: "GetTrackUrls(/tidal/...) # link track, rewritten path"
@ -241,4 +241,4 @@ path.)
- **Metadata drift** on link tracks (file says X, target now titled Y):
accepted; the file is the user's curated metadata.
- Open (future): tag-reading for bare audio files; multiple roots; a
`%`-style creator that writes a `.track.toml` from inside the TUI.
`%`-style creator that writes a `.cbd-track.toml` from inside the TUI.

View File

@ -28,7 +28,7 @@ pub const PROVIDER_ROOT: &str = "/fs";
/// Files with this suffix are serialized track nodes; everything else in
/// the tree is ignored.
pub const TRACK_FILE_SUFFIX: &str = ".track.toml";
pub const TRACK_FILE_SUFFIX: &str = ".cbd-track.toml";
/// Provider settings, persisted as `fsdy.toml` next to the other crabidy
/// config files.
@ -60,7 +60,7 @@ pub enum TrackFileError {
LinkIntoFs(String),
}
/// The on-disk schema of a `*.track.toml` file. See
/// The on-disk schema of a `*.cbd-track.toml` file. See
/// `architecture/fs-provider.md` (D3) for the format documentation.
#[derive(Debug, Deserialize)]
pub struct TrackFile {
@ -214,7 +214,7 @@ impl Client {
}
/// Lists a directory as a library node: subdirectories become queueable
/// child nodes, `*.track.toml` files become tracks, each list sorted
/// child nodes, `*.cbd-track.toml` files become tracks, each list sorted
/// case-insensitively by file name.
///
/// Symlinks, hidden entries (dot-prefixed), non-UTF-8 names, and track
@ -552,7 +552,7 @@ mod tests {
"relative link",
),
(
"title = \"x\"\n[playable]\nlink = \"/fs/other.track.toml\"\n",
"title = \"x\"\n[playable]\nlink = \"/fs/other.cbd-track.toml\"\n",
"link into /fs",
),
("title = \"x\"\nnot toml at all [", "invalid toml"),
@ -569,7 +569,7 @@ mod tests {
#[test]
fn to_track_rewrites_the_path_only_for_links() {
let lib_path = "/fs/mix/song.track.toml";
let lib_path = "/fs/mix/song.cbd-track.toml";
let linked = TrackFile::parse(
"title = \"t\"\nartist = \"a\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
)
@ -590,9 +590,9 @@ mod tests {
#[tokio::test]
async fn track_paths_need_the_suffix_and_the_provider_prefix() {
let (client, _dir) = client_with_root().await;
assert!(client.is_track_path("/fs/mix/song.track.toml"));
assert!(client.is_track_path("/fs/mix/song.cbd-track.toml"));
assert!(!client.is_track_path("/fs/mix"));
assert!(!client.is_track_path("/tidal/song.track.toml"));
assert!(!client.is_track_path("/tidal/song.cbd-track.toml"));
assert!(!client.is_track_path("/fs"));
}
@ -622,10 +622,10 @@ mod tests {
fs::create_dir(dir.path().join("b-dir")).expect("mkdir");
fs::create_dir(dir.path().join("A-dir")).expect("mkdir");
fs::create_dir(dir.path().join(".hidden-dir")).expect("mkdir");
write_track(dir.path(), "b song.track.toml", &url_track("b"));
write_track(dir.path(), "A song.track.toml", &url_track("a"));
write_track(dir.path(), "broken.track.toml", "not [ valid");
write_track(dir.path(), ".hidden.track.toml", &url_track("h"));
write_track(dir.path(), "b song.cbd-track.toml", &url_track("b"));
write_track(dir.path(), "A song.cbd-track.toml", &url_track("a"));
write_track(dir.path(), "broken.cbd-track.toml", "not [ valid");
write_track(dir.path(), ".hidden.cbd-track.toml", &url_track("h"));
fs::write(dir.path().join("cover.jpg"), b"jpg").expect("write");
#[cfg(unix)]
std::os::unix::fs::symlink(dir.path().join("b-dir"), dir.path().join("z-link"))
@ -641,7 +641,7 @@ mod tests {
let tracks: Vec<&str> = node.tracks.iter().map(|t| t.title.as_str()).collect();
assert_eq!(tracks, vec!["a", "b"]);
// Track paths are encoded segments under the node's path.
assert_eq!(node.tracks[0].path, "/fs/A%20song.track.toml");
assert_eq!(node.tracks[0].path, "/fs/A%20song.cbd-track.toml");
}
#[tokio::test]
@ -658,7 +658,7 @@ mod tests {
async fn nodes_link_back_to_their_parent() {
let (client, dir) = client_with_root().await;
fs::create_dir_all(dir.path().join("a/b")).expect("mkdir");
write_track(&dir.path().join("a/b"), "t.track.toml", &url_track("t"));
write_track(&dir.path().join("a/b"), "t.cbd-track.toml", &url_track("t"));
let node = client.get_lib_node("/fs/a/b").await.expect("node");
assert_eq!(node.parent.as_deref(), Some("/fs/a"));
assert_eq!(node.title, "b");
@ -675,28 +675,28 @@ mod tests {
fs::create_dir(&sub).expect("mkdir");
write_track(
&sub,
"rel.track.toml",
"rel.cbd-track.toml",
"title = \"r\"\n[playable]\nfile = \"a.flac\"\n",
);
let abs_target = dir.path().join("elsewhere.mp3");
write_track(
&sub,
"abs.track.toml",
"abs.cbd-track.toml",
&format!(
"title = \"a\"\n[playable]\nfile = {:?}\n",
abs_target.to_str().expect("utf8")
),
);
write_track(&sub, "web.track.toml", &url_track("w"));
write_track(&sub, "web.cbd-track.toml", &url_track("w"));
write_track(
&sub,
"linked.track.toml",
"linked.cbd-track.toml",
"title = \"l\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
);
// Relative files resolve against the track file's directory.
let urls = client
.get_urls_for_track("/fs/mix/rel.track.toml")
.get_urls_for_track("/fs/mix/rel.cbd-track.toml")
.await
.expect("relative file");
assert_eq!(
@ -705,20 +705,20 @@ mod tests {
);
// Absolute files pass through.
let urls = client
.get_urls_for_track("/fs/mix/abs.track.toml")
.get_urls_for_track("/fs/mix/abs.cbd-track.toml")
.await
.expect("absolute file");
assert_eq!(urls, vec![abs_target.to_str().expect("utf8").to_string()]);
// URLs pass through.
let urls = client
.get_urls_for_track("/fs/mix/web.track.toml")
.get_urls_for_track("/fs/mix/web.cbd-track.toml")
.await
.expect("url");
assert_eq!(urls, vec!["https://example.org/s.mp3".to_string()]);
// Link playables never resolve here: their tracks route to the
// target provider, so landing here means a malformed request.
let err = client
.get_urls_for_track("/fs/mix/linked.track.toml")
.get_urls_for_track("/fs/mix/linked.cbd-track.toml")
.await
.expect_err("link");
assert_eq!(err, ProviderError::MalformedPath);
@ -729,11 +729,11 @@ mod tests {
let (client, dir) = client_with_root().await;
write_track(
dir.path(),
"linked.track.toml",
"linked.cbd-track.toml",
"title = \"t\"\nartist = \"a\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
);
let track = client
.get_metadata_for_track("/fs/linked.track.toml")
.get_metadata_for_track("/fs/linked.cbd-track.toml")
.await
.expect("metadata");
assert_eq!(track.path, "/tidal/artists/1/2");
@ -750,9 +750,9 @@ mod tests {
let al2 = dir.path().join("artist/album2");
fs::create_dir_all(&al1).expect("mkdir");
fs::create_dir_all(&al2).expect("mkdir");
write_track(&al1, "01.track.toml", &url_track("one"));
write_track(&al1, "02.track.toml", &url_track("two"));
write_track(&al2, "01.track.toml", &url_track("three"));
write_track(&al1, "01.cbd-track.toml", &url_track("one"));
write_track(&al1, "02.cbd-track.toml", &url_track("two"));
write_track(&al2, "01.cbd-track.toml", &url_track("three"));
let (chunk_tx, chunk_rx) = flume::bounded(8);
client

View File

@ -4,7 +4,7 @@
Built per `plan/fs-provider.md`: a second media provider (crate `fsdy`,
`/fs`) that walks one configured root directory and treats
`*.track.toml` files as serialized track nodes — metadata plus exactly
`*.cbd-track.toml` files as serialized track nodes — metadata plus exactly
one playable reference: a local audio file (absolute or relative to the
track file), an http(s) URL, or a crabidy-internal link. The wire types
are unchanged (architecture D1): the only new datastructure is the
@ -30,6 +30,9 @@ are recorded in `architecture/fs-provider.md` (options + rationale).
### Deviations from plan / architecture (fs-provider)
- **Extension renamed to `.cbd-track.toml`** (user request, follow-up
commit): the original `.track.toml` was too generic; the `cbd-` prefix
makes the files unmistakably crabidy's.
- **`TrackFileError::UrlScheme` carries only the scheme**, not the URL:
the parse error ends up in skip-warnings, and a private stream URL may
embed a token (quality gate "no file contents in logs"). The