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:
parent
4dd5f01217
commit
955c7ea5b4
|
|
@ -81,9 +81,9 @@ Consequences, accepted deliberately:
|
||||||
resolution. **Link chains are structurally impossible** — see D3's "no
|
resolution. **Link chains are structurally impossible** — see D3's "no
|
||||||
links into `/fs`" rule.
|
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
|
the configured root is a track node; everything else (other files, hidden
|
||||||
entries) is ignored. Schema:
|
entries) is ignored. Schema:
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@ direction: right
|
||||||
|
|
||||||
disk: Local disk {
|
disk: Local disk {
|
||||||
shape: cylinder
|
shape: cylinder
|
||||||
tree: "root dir: dirs, *.track.toml"
|
tree: "root dir: dirs, *.cbd-track.toml"
|
||||||
}
|
}
|
||||||
|
|
||||||
server: crabidy-server {
|
server: crabidy-server {
|
||||||
|
|
@ -185,7 +185,7 @@ server: crabidy-server {
|
||||||
}
|
}
|
||||||
|
|
||||||
fsdy: fsdy::Client {
|
fsdy: fsdy::Client {
|
||||||
parse: "parse + validate .track.toml"
|
parse: "parse + validate .cbd-track.toml"
|
||||||
map: "path <-> root-relative file (encoded segments)"
|
map: "path <-> root-relative file (encoded segments)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,7 +218,7 @@ pb -> orch: ResolveTracks("/fs/mix", chunk_tx)
|
||||||
orch -> fs: resolve_tracks_into (spawned)
|
orch -> fs: resolve_tracks_into (spawned)
|
||||||
fs -> fs: "list dir, parse 3 track files"
|
fs -> fs: "list dir, parse 3 track files"
|
||||||
fs -> pb: "chunk of 3 Tracks (paths below)" {style.bold: true}
|
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
|
orch -> fs: get_urls_for_track
|
||||||
fs -> pb: "[/home/u/Music/a.flac]"
|
fs -> pb: "[/home/u/Music/a.flac]"
|
||||||
pb -> orch: "GetTrackUrls(/tidal/...) # link track, rewritten path"
|
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):
|
- **Metadata drift** on link tracks (file says X, target now titled Y):
|
||||||
accepted; the file is the user's curated metadata.
|
accepted; the file is the user's curated metadata.
|
||||||
- Open (future): tag-reading for bare audio files; multiple roots; a
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub const PROVIDER_ROOT: &str = "/fs";
|
||||||
|
|
||||||
/// Files with this suffix are serialized track nodes; everything else in
|
/// Files with this suffix are serialized track nodes; everything else in
|
||||||
/// the tree is ignored.
|
/// 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
|
/// Provider settings, persisted as `fsdy.toml` next to the other crabidy
|
||||||
/// config files.
|
/// config files.
|
||||||
|
|
@ -60,7 +60,7 @@ pub enum TrackFileError {
|
||||||
LinkIntoFs(String),
|
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.
|
/// `architecture/fs-provider.md` (D3) for the format documentation.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct TrackFile {
|
pub struct TrackFile {
|
||||||
|
|
@ -214,7 +214,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lists a directory as a library node: subdirectories become queueable
|
/// 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.
|
/// case-insensitively by file name.
|
||||||
///
|
///
|
||||||
/// Symlinks, hidden entries (dot-prefixed), non-UTF-8 names, and track
|
/// Symlinks, hidden entries (dot-prefixed), non-UTF-8 names, and track
|
||||||
|
|
@ -552,7 +552,7 @@ mod tests {
|
||||||
"relative link",
|
"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",
|
"link into /fs",
|
||||||
),
|
),
|
||||||
("title = \"x\"\nnot toml at all [", "invalid toml"),
|
("title = \"x\"\nnot toml at all [", "invalid toml"),
|
||||||
|
|
@ -569,7 +569,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_track_rewrites_the_path_only_for_links() {
|
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(
|
let linked = TrackFile::parse(
|
||||||
"title = \"t\"\nartist = \"a\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
|
"title = \"t\"\nartist = \"a\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
|
||||||
)
|
)
|
||||||
|
|
@ -590,9 +590,9 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn track_paths_need_the_suffix_and_the_provider_prefix() {
|
async fn track_paths_need_the_suffix_and_the_provider_prefix() {
|
||||||
let (client, _dir) = client_with_root().await;
|
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("/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"));
|
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("b-dir")).expect("mkdir");
|
||||||
fs::create_dir(dir.path().join("A-dir")).expect("mkdir");
|
fs::create_dir(dir.path().join("A-dir")).expect("mkdir");
|
||||||
fs::create_dir(dir.path().join(".hidden-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(), "b song.cbd-track.toml", &url_track("b"));
|
||||||
write_track(dir.path(), "A song.track.toml", &url_track("a"));
|
write_track(dir.path(), "A song.cbd-track.toml", &url_track("a"));
|
||||||
write_track(dir.path(), "broken.track.toml", "not [ valid");
|
write_track(dir.path(), "broken.cbd-track.toml", "not [ valid");
|
||||||
write_track(dir.path(), ".hidden.track.toml", &url_track("h"));
|
write_track(dir.path(), ".hidden.cbd-track.toml", &url_track("h"));
|
||||||
fs::write(dir.path().join("cover.jpg"), b"jpg").expect("write");
|
fs::write(dir.path().join("cover.jpg"), b"jpg").expect("write");
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
std::os::unix::fs::symlink(dir.path().join("b-dir"), dir.path().join("z-link"))
|
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();
|
let tracks: Vec<&str> = node.tracks.iter().map(|t| t.title.as_str()).collect();
|
||||||
assert_eq!(tracks, vec!["a", "b"]);
|
assert_eq!(tracks, vec!["a", "b"]);
|
||||||
// Track paths are encoded segments under the node's path.
|
// 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]
|
#[tokio::test]
|
||||||
|
|
@ -658,7 +658,7 @@ mod tests {
|
||||||
async fn nodes_link_back_to_their_parent() {
|
async fn nodes_link_back_to_their_parent() {
|
||||||
let (client, dir) = client_with_root().await;
|
let (client, dir) = client_with_root().await;
|
||||||
fs::create_dir_all(dir.path().join("a/b")).expect("mkdir");
|
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");
|
let node = client.get_lib_node("/fs/a/b").await.expect("node");
|
||||||
assert_eq!(node.parent.as_deref(), Some("/fs/a"));
|
assert_eq!(node.parent.as_deref(), Some("/fs/a"));
|
||||||
assert_eq!(node.title, "b");
|
assert_eq!(node.title, "b");
|
||||||
|
|
@ -675,28 +675,28 @@ mod tests {
|
||||||
fs::create_dir(&sub).expect("mkdir");
|
fs::create_dir(&sub).expect("mkdir");
|
||||||
write_track(
|
write_track(
|
||||||
&sub,
|
&sub,
|
||||||
"rel.track.toml",
|
"rel.cbd-track.toml",
|
||||||
"title = \"r\"\n[playable]\nfile = \"a.flac\"\n",
|
"title = \"r\"\n[playable]\nfile = \"a.flac\"\n",
|
||||||
);
|
);
|
||||||
let abs_target = dir.path().join("elsewhere.mp3");
|
let abs_target = dir.path().join("elsewhere.mp3");
|
||||||
write_track(
|
write_track(
|
||||||
&sub,
|
&sub,
|
||||||
"abs.track.toml",
|
"abs.cbd-track.toml",
|
||||||
&format!(
|
&format!(
|
||||||
"title = \"a\"\n[playable]\nfile = {:?}\n",
|
"title = \"a\"\n[playable]\nfile = {:?}\n",
|
||||||
abs_target.to_str().expect("utf8")
|
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(
|
write_track(
|
||||||
&sub,
|
&sub,
|
||||||
"linked.track.toml",
|
"linked.cbd-track.toml",
|
||||||
"title = \"l\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
|
"title = \"l\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Relative files resolve against the track file's directory.
|
// Relative files resolve against the track file's directory.
|
||||||
let urls = client
|
let urls = client
|
||||||
.get_urls_for_track("/fs/mix/rel.track.toml")
|
.get_urls_for_track("/fs/mix/rel.cbd-track.toml")
|
||||||
.await
|
.await
|
||||||
.expect("relative file");
|
.expect("relative file");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -705,20 +705,20 @@ mod tests {
|
||||||
);
|
);
|
||||||
// Absolute files pass through.
|
// Absolute files pass through.
|
||||||
let urls = client
|
let urls = client
|
||||||
.get_urls_for_track("/fs/mix/abs.track.toml")
|
.get_urls_for_track("/fs/mix/abs.cbd-track.toml")
|
||||||
.await
|
.await
|
||||||
.expect("absolute file");
|
.expect("absolute file");
|
||||||
assert_eq!(urls, vec![abs_target.to_str().expect("utf8").to_string()]);
|
assert_eq!(urls, vec![abs_target.to_str().expect("utf8").to_string()]);
|
||||||
// URLs pass through.
|
// URLs pass through.
|
||||||
let urls = client
|
let urls = client
|
||||||
.get_urls_for_track("/fs/mix/web.track.toml")
|
.get_urls_for_track("/fs/mix/web.cbd-track.toml")
|
||||||
.await
|
.await
|
||||||
.expect("url");
|
.expect("url");
|
||||||
assert_eq!(urls, vec!["https://example.org/s.mp3".to_string()]);
|
assert_eq!(urls, vec!["https://example.org/s.mp3".to_string()]);
|
||||||
// Link playables never resolve here: their tracks route to the
|
// Link playables never resolve here: their tracks route to the
|
||||||
// target provider, so landing here means a malformed request.
|
// target provider, so landing here means a malformed request.
|
||||||
let err = client
|
let err = client
|
||||||
.get_urls_for_track("/fs/mix/linked.track.toml")
|
.get_urls_for_track("/fs/mix/linked.cbd-track.toml")
|
||||||
.await
|
.await
|
||||||
.expect_err("link");
|
.expect_err("link");
|
||||||
assert_eq!(err, ProviderError::MalformedPath);
|
assert_eq!(err, ProviderError::MalformedPath);
|
||||||
|
|
@ -729,11 +729,11 @@ mod tests {
|
||||||
let (client, dir) = client_with_root().await;
|
let (client, dir) = client_with_root().await;
|
||||||
write_track(
|
write_track(
|
||||||
dir.path(),
|
dir.path(),
|
||||||
"linked.track.toml",
|
"linked.cbd-track.toml",
|
||||||
"title = \"t\"\nartist = \"a\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
|
"title = \"t\"\nartist = \"a\"\n[playable]\nlink = \"/tidal/artists/1/2\"\n",
|
||||||
);
|
);
|
||||||
let track = client
|
let track = client
|
||||||
.get_metadata_for_track("/fs/linked.track.toml")
|
.get_metadata_for_track("/fs/linked.cbd-track.toml")
|
||||||
.await
|
.await
|
||||||
.expect("metadata");
|
.expect("metadata");
|
||||||
assert_eq!(track.path, "/tidal/artists/1/2");
|
assert_eq!(track.path, "/tidal/artists/1/2");
|
||||||
|
|
@ -750,9 +750,9 @@ mod tests {
|
||||||
let al2 = dir.path().join("artist/album2");
|
let al2 = dir.path().join("artist/album2");
|
||||||
fs::create_dir_all(&al1).expect("mkdir");
|
fs::create_dir_all(&al1).expect("mkdir");
|
||||||
fs::create_dir_all(&al2).expect("mkdir");
|
fs::create_dir_all(&al2).expect("mkdir");
|
||||||
write_track(&al1, "01.track.toml", &url_track("one"));
|
write_track(&al1, "01.cbd-track.toml", &url_track("one"));
|
||||||
write_track(&al1, "02.track.toml", &url_track("two"));
|
write_track(&al1, "02.cbd-track.toml", &url_track("two"));
|
||||||
write_track(&al2, "01.track.toml", &url_track("three"));
|
write_track(&al2, "01.cbd-track.toml", &url_track("three"));
|
||||||
|
|
||||||
let (chunk_tx, chunk_rx) = flume::bounded(8);
|
let (chunk_tx, chunk_rx) = flume::bounded(8);
|
||||||
client
|
client
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
Built per `plan/fs-provider.md`: a second media provider (crate `fsdy`,
|
Built per `plan/fs-provider.md`: a second media provider (crate `fsdy`,
|
||||||
`/fs`) that walks one configured root directory and treats
|
`/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
|
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
|
track file), an http(s) URL, or a crabidy-internal link. The wire types
|
||||||
are unchanged (architecture D1): the only new datastructure is the
|
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)
|
### 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:
|
- **`TrackFileError::UrlScheme` carries only the scheme**, not the URL:
|
||||||
the parse error ends up in skip-warnings, and a private stream URL may
|
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
|
embed a token (quality gate "no file contents in logs"). The
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue