use helpers::*; use serde::Deserialize; use std::time::Duration; #[derive(Deserialize, Clone, Debug, Default, PartialEq)] #[serde(default)] pub struct Track { pub file: String, #[serde(rename = "artistsort")] pub artist_sort: Option, #[serde(rename = "albumartist")] pub album_artist: Option, #[serde(rename = "albumsort")] pub album_sort: Option, #[serde(rename = "albumartistsort")] pub album_artist_sort: Option, #[serde(deserialize_with = "de_string_or_vec")] #[serde(rename = "performer")] pub performers: Vec, pub genre: Option, pub title: Option, #[serde(deserialize_with = "de_position")] pub track: Option, pub album: Option, pub artist: Option, pub pos: u32, pub id: u32, // TODO: use proper time here #[serde(rename = "last-modified")] pub last_modified: Option, #[serde(rename = "originaldate")] pub original_date: Option, // probably not needed. it’s just the duration but as an int // pub time: u16, pub format: Option, #[serde(deserialize_with = "de_time_float")] pub duration: Duration, pub label: Option, pub date: Option, #[serde(deserialize_with = "de_position")] pub disc: Option, pub musicbraiz_trackid: Option, pub musicbrainz_albumid: Option, pub musicbrainz_albumartistid: Option, pub musicbrainz_artistid: Option, pub musicbrainz_releasetrackid: Option, pub musicbrainz_trackid: Option, pub composer: Option, } /// The position of an item in a list with an optional total length. /// Can be used for e.g. track number, /// where `Position { 3, 12 }` represents track 3 of a CD with 12 tracks, /// or disc numbers in a multi-disc set. #[derive(Deserialize, Clone, Debug, Default, PartialEq)] pub struct Position { pub item_position: u16, pub total_items: Option, } /// Deserialization helpers to handle the quirks of mpd’s output. mod helpers { use super::*; use crate::SEPARATOR; use serde::{de, Deserialize}; use std::{str::FromStr, time::Duration}; /// Deserialize time from an integer that represents the seconds. /// mpd uses int for the database stats (e.g. total time played). pub fn de_time_int<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de> { u64::deserialize(deserializer).map(Duration::from_secs) } /// Deserialize time from a float that represents the seconds. /// mpd uses floats for the current status (e.g. time elapsed in song). pub fn de_time_float<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de> { f64::deserialize(deserializer).map(Duration::from_secs_f64) } pub fn de_string_or_vec<'de, D>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de> { String::deserialize(deserializer).map(|s| s.split(SEPARATOR).map(std::string::String::from).collect()) } /// mpd uses bints (0 or 1) to represent booleans, /// so we need a special parser for those. pub fn de_bint<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de> { match u8::deserialize(deserializer)? { 0 => Ok(false), 1 => Ok(true), n => Err(de::Error::invalid_value(de::Unexpected::Unsigned(n as u64), &"zero or one")), } } /// Deserialize a position with an optional total length. /// The input string here is either a number or two numbers separated by `SEPARATOR`. pub fn de_position<'de, D>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de> { let s = String::deserialize(deserializer)?; let mut ints = s.split(SEPARATOR).filter_map(|s| u16::from_str(s).ok()); if let Some(n) = ints.next() { return Ok(Some(Position { item_position: n, total_items: ints.next(), })); } Ok(None) } }