From e8c8be6725aa88abc1a505c9d77667e5e1a3d068 Mon Sep 17 00:00:00 2001 From: kageru Date: Sat, 20 Jun 2020 23:27:00 +0200 Subject: [PATCH] Properly handle repeated fields --- src/lib.rs | 75 ++++++++++++++++++++- src/structs.rs | 174 ++++++++++++++++++++++++++++--------------------- 2 files changed, 171 insertions(+), 78 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3b5c470..dffec96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; mod error; use error::{Error, MpdResult}; mod structs; -pub use structs::Track; +pub use structs::{Position, Track}; /// some unprintable character to separate repeated keys const SEPARATOR: char = '\x02'; @@ -53,7 +53,7 @@ pub fn deserialize_response<'a, I: Iterator, T: de::Deserialize< _ => panic!("invalid response line: {:?}", line), } } - return Ok(envy::from_iter(map).unwrap()); + Ok(envy::from_iter(map).unwrap()) // Eventually, I’d like to use my own deserializer instead of envy // let deser = MPDeserializer { // inner: MapDeserializer::new(map.into_iter()), @@ -91,7 +91,10 @@ OK"; date: Some(1979), genre: Some("Rock".to_string()), title: Some("American Pie".to_string()), - track: Some(1), + track: Some(Position { + item_position: 1, + total_items: None, + }), duration: std::time::Duration::from_secs_f64(512.380), pos: 1367, id: 1368, @@ -99,4 +102,70 @@ OK"; } ); } + + #[test] + fn repeated_field_test() { + let input_str = r#"file: 06 Symphonie No. 41 en ut majeur, K. 551 _Jupiter_ I. Allegro Vivace.flac +Last-Modified: 2018-11-11T09:01:54Z +Album: Symphonies n°40 & n°41 +AlbumArtist: Wolfgang Amadeus Mozart; Royal Philharmonic Orchestra, Jane Glover +AlbumArtistSort: Mozart, Wolfgang Amadeus; Royal Philharmonic Orchestra, Glover, Jane +Artist: Wolfgang Amadeus Mozart +ArtistSort: Mozart, Wolfgang Amadeus +Composer: Wolfgang Amadeus Mozart +Date: 2005 +Disc: 1 +Disc: 1 +MUSICBRAINZ_ALBUMARTISTID: b972f589-fb0e-474e-b64a-803b0364fa75 +MUSICBRAINZ_ALBUMID: 688d9252-f897-4ab6-879d-5cb83bb71087 +MUSICBRAINZ_ARTISTID: b972f589-fb0e-474e-b64a-803b0364fa75 +MUSICBRAINZ_RELEASETRACKID: 54a2632f-fa98-3713-bd75-d7effc03d0b1 +MUSICBRAINZ_TRACKID: 2dd10dd8-e8de-4479-a092-9a04c2760fd6 +OriginalDate: 1993 +Title: Symphonie No. 41 en ut majeur, K. 551 "Jupiter": I. Allegro Vivace +Track: 6 +Track: 6 +Time: 683 +Performer: Royal Philharmonic Orchestra +duration: 682.840 +Performer: Jane Glover +Pos: 3439 +Id: 3440 +OK"#; + let t = deserialize_response::<'_, _, Track>(input_str.lines()).unwrap(); + assert_eq!( + t, + Track { + file: "06 Symphonie No. 41 en ut majeur, K. 551 _Jupiter_ I. Allegro Vivace.flac".to_string(), + last_modified: Some("2018-11-11T09:01:54Z".to_string()), + album: Some("Symphonies n°40 & n°41".to_string()), + artist: Some("Wolfgang Amadeus Mozart".to_string()), + artist_sort: Some("Mozart, Wolfgang Amadeus".to_string()), + album_artist: Some("Wolfgang Amadeus Mozart; Royal Philharmonic Orchestra, Jane Glover".to_string()), + album_artist_sort: Some("Mozart, Wolfgang Amadeus; Royal Philharmonic Orchestra, Glover, Jane".to_string()), + composer: Some("Wolfgang Amadeus Mozart".to_string()), + date: Some(2005), + original_date: Some(1993), + title: Some("Symphonie No. 41 en ut majeur, K. 551 \"Jupiter\": I. Allegro Vivace".to_string()), + disc: Some(Position { + item_position: 1, + total_items: Some(1), + }), + track: Some(Position { + item_position: 6, + total_items: Some(6), + }), + duration: std::time::Duration::from_secs_f64(682.840), + pos: 3439, + id: 3440, + performers: vec!["Royal Philharmonic Orchestra".to_string(), "Jane Glover".to_string()], + musicbrainz_albumartistid: Some("b972f589-fb0e-474e-b64a-803b0364fa75".to_string()), + musicbrainz_albumid: Some("688d9252-f897-4ab6-879d-5cb83bb71087".to_string()), + musicbrainz_artistid: Some("b972f589-fb0e-474e-b64a-803b0364fa75".to_string()), + musicbrainz_releasetrackid: Some("54a2632f-fa98-3713-bd75-d7effc03d0b1".to_string()), + musicbrainz_trackid: Some("2dd10dd8-e8de-4479-a092-9a04c2760fd6".to_string()), + ..Track::default() + } + ); + } } diff --git a/src/structs.rs b/src/structs.rs index 50a0983..efbe265 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -4,88 +4,112 @@ use std::time::Duration; #[derive(Deserialize, Clone, Debug, Default, PartialEq)] pub struct Track { - pub file: String, - pub artist_sort: Option, - pub album_artist: Option, - pub album_sort: Option, - pub album_artist_sort: Option, - #[serde(deserialize_with = "string_or_vec")] - #[serde(default)] - #[serde(rename = "performer")] - pub performers: Vec, - pub genre: Option, - pub title: Option, - 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, - 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, - pub disc: Option, - pub musicbraiz_trackid: Option, - pub musicbrainz_albumid: Option, - pub musicbrainz_albumartistid: Option, - pub musicbrainz_artistid: Option, - pub musicbraiz_releasetrackid: Option, - pub composer: Option, + 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(default)] + #[serde(rename = "performer")] + pub performers: Vec, + pub genre: Option, + pub title: Option, + #[serde(default)] + #[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(default)] + #[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 crate::SEPARATOR; - use serde::de; - use serde::Deserialize; - use std::time::Duration; + 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 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) - } + /// 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 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()) - } + 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", - )), - } + /// 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> { + if let Ok(s) = String::deserialize(deserializer) { + let mut ints = s.split(SEPARATOR).map(u16::from_str); + if let Some(Ok(n)) = ints.next() { + return Ok(Some(Position { + item_position: n, + total_items: ints.next().and_then(Result::ok), + })); + } + } + Ok(None) + } }