//! Music domain root — entity types decided in `13-MUSIC-MODEL.md` (D-13, //! one-way). See that document for the full rationale and the rejected //! alternatives; this module implements the decision, it does not re-derive //! it. //! //! Track identity is hybrid: `(source, canonical path)` is the row key //! (cheap, stat-only, incremental via mtime), with a lazily-backfilled //! content-hash dedupe column (`Track::content_hash`) for the move/dedupe //! case that identity alone can't handle. Albums and artists are derived at //! read time by grouping tracks on their tags, not stored as first-class //! rows. The on-disk index is a single JSON file at //! `data_dir/music/index.json`, matching `content_server.rs::load_catalog`'s //! precedent. pub mod tags; use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// Schema version of the on-disk music index (`data_dir/music/index.json`). /// Bump when `Track`'s shape changes in a way that needs a reindex. See /// `13-MUSIC-MODEL.md`'s "Schema version and the reindex path" section for /// the newer-version-on-older-binary handling contract: an older binary /// encountering a newer-versioned index treats it as absent rather than /// reinterpreting or overwriting it. pub const MUSIC_SCHEMA_VERSION: u32 = 1; /// Where a track was discovered. Both sources are indexed /// (`13-MUSIC-MODEL.md`'s "Sources indexed: both"). #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum MusicSource { /// The node's own FileBrowser `Music` folder. OwnLibrary, /// A peer's shared audio, reachable via the content/peer-proxy /// subsystem. Peer { onion: String }, } /// Stable identity for a track (hybrid-identity, `13-MUSIC-MODEL.md`): /// `(source, canonical path)` is the row key — cheap, stat-only, survives a /// rescan via mtime. A file move or rename orphans this identity; /// `Track::content_hash` is the lazily-backfilled dedupe column that exists /// for exactly that case, and for cross-peer dedupe once it's populated. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct TrackId { pub source: MusicSource, pub path: PathBuf, } /// Derived album grouping key (derived-albums, `13-MUSIC-MODEL.md`). Albums /// are not stored rows — this is the key produced by grouping `Track`s at /// read time on `(album_artist, album)`, not a persisted identity. A retag /// simply changes what the grouping produces on the next read. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct AlbumId { pub album_artist: Option, pub album: String, } /// Derived artist grouping key. Same read-time-only status as `AlbumId`. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ArtistId(pub String); /// A single indexed track — a row in `data_dir/music/index.json`. This is /// the migration surface `13-MUSIC-MODEL.md`'s one-way decision is about: /// changing this shape after nodes have indexed libraries needs a reindex /// path (`MUSIC_SCHEMA_VERSION`), not just a code change. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Track { pub id: TrackId, pub title: String, pub artist: Option, pub album: Option, pub album_artist: Option, pub track_number: Option, pub disc_number: Option, pub year: Option, pub duration_secs: u64, /// `false` when `title` was derived from the filename stem because the /// file carried no readable tags — the track still appears in the /// library rather than being dropped (see `tags::extract_tags`). pub has_tags: bool, /// Lazily-backfilled dedupe column (hybrid-identity, /// `13-MUSIC-MODEL.md`). `None` until a background backfill pass /// computes it; absence is not an error state. #[serde(default)] pub content_hash: Option, } /// An album, computed at read time by grouping `Track`s on /// `(album_artist, album)` — never persisted directly (derived-albums, /// `13-MUSIC-MODEL.md`). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Album { pub id: AlbumId, pub track_ids: Vec, } /// An artist, computed at read time by grouping `Track`s on `artist`. Same /// read-time-only status as `Album`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Artist { pub id: ArtistId, pub track_ids: Vec, }