diff --git a/crates/music-dht/src/catalog.rs b/crates/music-dht/src/catalog.rs new file mode 100644 index 0000000..76401d2 --- /dev/null +++ b/crates/music-dht/src/catalog.rs @@ -0,0 +1,148 @@ +//! Shared wire shapes for Furumi catalog streams. +//! +//! The DHT itself only indexes compact search records. Applications can expose +//! richer, peer-to-peer catalog slices over this protocol when another peer +//! needs a browseable library view. + +use serde::{Deserialize, Serialize}; + +/// ALPN of the Furumi catalog stream protocol. +pub const CATALOG_ALPN: &[u8] = b"furumi-fd/catalog/1"; + +/// Request sent by a catalog client. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogRequest { + /// Artist name for full artist-card and image requests. + #[serde(default)] + pub artist: String, + /// Requested resource kind. + #[serde(default)] + pub want: Option, + /// Release title for release-cover requests. + #[serde(default)] + pub release: Option, + /// Opaque pagination cursor for browse slices. + #[serde(default)] + pub cursor: Option, + /// Desired number of entries for browse slices. + #[serde(default)] + pub limit: Option, +} + +/// Response returned by a catalog peer. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogResponse { + /// Whether the request succeeded. + pub ok: bool, + /// Human-readable error message when `ok` is false. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Full artist card for `catalog` requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub artist: Option, + /// Compact artist previews for `artists` slice requests. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub artists: Vec, + /// Optional release card for future direct release requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub release: Option, + /// Next pagination cursor for browse slices. + #[serde(skip_serializing_if = "Option::is_none")] + pub next_cursor: Option, +} + +/// Compact artist entry used by paginated library browsing. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogArtistPreview { + /// Normalized artist key. + pub artist_key: String, + /// Display artist name. + pub name: String, + /// Optional image path or peer-local image handle. + #[serde(skip_serializing_if = "Option::is_none")] + pub image_path: Option, + /// Number of releases where the artist is a primary release artist. + pub release_count: i64, + /// Number of tracks where the artist appears. + pub track_count: i64, +} + +/// Full artist card returned by the catalog protocol. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogArtist { + /// Display artist name. + pub name: String, + /// Releases owned by this artist. + #[serde(default)] + pub releases: Vec, + /// Release appearances where this artist is not the primary artist. + #[serde(default)] + pub appears_on: Vec, +} + +/// Release appearance used by full artist cards. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogAppearance { + /// Release title. + pub release_title: String, + /// Release type. + #[serde(default)] + pub release_type: String, + /// Release year. + pub year: Option, + /// Track where the artist appears. + pub track: CatalogTrack, +} + +/// Release card returned by the catalog protocol. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogRelease { + /// Release title. + pub title: String, + /// Release type. + #[serde(default)] + pub release_type: String, + /// Release year. + pub year: Option, + /// Tracks on this release. + #[serde(default)] + pub tracks: Vec, +} + +/// Track entry returned inside catalog cards. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogTrack { + /// Track title. + pub title: String, + /// Main artists. + #[serde(default)] + pub artists: Vec, + /// Featured artists. + #[serde(default)] + pub featured_artists: Vec, + /// Track number. + pub track_number: Option, + /// Disc number. + pub disc_number: Option, + /// Track duration in seconds. + pub duration_seconds: Option, + /// Stable audio content id when known. + #[serde(skip_serializing_if = "Option::is_none")] + pub content_id: Option, + /// Peer-local item id used by legacy fetch paths. + pub item_id: String, +} + +/// Header returned by catalog image requests before the image bytes. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CatalogImageHeader { + /// Whether the image request succeeded. + pub ok: bool, + /// Human-readable error message when `ok` is false. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// MIME type of the returned image. + pub mime_type: String, + /// Size of the following image payload in bytes. + pub size: u64, +} diff --git a/crates/music-dht/src/lib.rs b/crates/music-dht/src/lib.rs index 1d2dd40..0dd5f75 100644 --- a/crates/music-dht/src/lib.rs +++ b/crates/music-dht/src/lib.rs @@ -77,6 +77,7 @@ #![warn(missing_docs)] #![forbid(unsafe_code)] +pub mod catalog; mod config; mod database; pub mod device_sync;