diff --git a/Cargo.toml b/Cargo.toml index 717b996..7e4d2a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -pest = "2.1" -pest_derive = "2.1" serde = { version = "1.0", features = ["derive"] } +#itertools = "0.9" +envy = "0.4" diff --git a/src/error.rs b/src/error.rs index 8b14429..ba73f9f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,6 @@ -use pest; use serde::de; use std::fmt::{self, Display}; -use crate::Rule; - pub type MpdResult = std::result::Result; #[derive(Clone, Debug, PartialEq)] @@ -11,14 +8,6 @@ pub struct Error { pub message: String, } -impl From> for Error { - fn from(err: pest::error::Error) -> Self { - Error { - message: err.to_string(), - } - } -} - impl de::Error for Error { fn custom(msg: T) -> Self { Error { diff --git a/src/main.rs b/src/main.rs index e4de75d..309c7b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,91 @@ -#[macro_use] -extern crate pest_derive; -use pest::Parser; use serde::de; use serde::de::value::MapDeserializer; use serde::forward_to_deserialize_any; use serde::Deserialize; +use std::fmt; +use std::time::Duration; mod error; use error::MpdResult; -#[derive(Parser)] -#[grammar = "response.pest"] -struct MpdParser; - struct MPDeserializer<'de, Iter: Iterator> { inner: MapDeserializer<'de, Iter, error::Error>, } +/// Deserialize time from an integer that represents the seconds. +/// mpd uses int for the database stats (e.g. total time played). +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). +fn de_time_float<'de, D>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + f64::deserialize(deserializer) + .map(Duration::from_secs_f64) + .map(Some) +} + +/// mpd uses bints (0 or 1) to represent booleans, +/// so we need a special parser for those. +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", + )), + } +} + +#[derive(Deserialize, Clone, Debug)] +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: Option, + pub id: Option, + // TODO: use proper time here + pub last_modified: Option, + pub original_date: Option, + pub time: Option, + pub format: Option, + #[serde(deserialize_with = "de_time_float")] + pub duration: Option, + 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, +} + +// some unprintable character +const SEPARATOR: char = '\x02'; + impl<'de, Iter: Iterator> de::Deserializer<'de> for MPDeserializer<'de, Iter> { @@ -42,41 +112,73 @@ impl<'de, Iter: Iterator> de::Deserializer<'de> } } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "PascalCase")] -struct Song { - title: String, - artist: String, +fn string_or_vec<'de, D>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + struct StringOrVec(); + + impl<'de> de::Visitor<'de> for StringOrVec { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string or list of strings") + } + + fn visit_str(self, value: &str) -> Result { + Ok(value + .split(SEPARATOR) + .map(std::string::String::from) + .collect()) + } + } + + deserializer.deserialize_any(StringOrVec()) +} + +fn deserialize_response<'a, I: Iterator>(input: I) -> Result { + let mut map: std::collections::HashMap = std::collections::HashMap::new(); + for line in input { + if let Some(_) = line.strip_prefix("OK") { + break; + } else if let Some(message) = line.strip_prefix("ACK") { + return Err(error::Error { + message: message.trim().to_string(), + }); + } + let mut fields = line.splitn(2, ": "); + match (fields.next(), fields.next()) { + (Some(k), Some(v)) => { + if let Some(existing) = map.get_mut(k) { + existing.push(SEPARATOR); + existing.push_str(v); + } else { + map.insert(k.to_string(), v.to_string()); + } + } + _ => panic!("invalid response line: {:?}", line), + } + } + return Ok(envy::from_iter(map).unwrap()); + // let deser = MPDeserializer { + // inner: MapDeserializer::new(map.into_iter()), + // }; + // Song::deserialize(deser) } fn main() { - let input_var: &str = "Title: A song 星 -Artist: A name -OK mpd 0.21.23"; - - let mut map = std::collections::HashMap::new(); - let parser = MpdParser::parse(Rule::response, input_var) - .unwrap() - .next() - .unwrap(); - for line in parser.into_inner() { - match line.as_rule() { - Rule::kv => { - let mut fields = line.into_inner(); - map.insert( - fields.next().unwrap().as_str(), - fields.next().unwrap().as_str(), - ); - } - Rule::ok => break, - Rule::err => panic!("received error response"), - _ => unreachable!(), - } - } - dbg!(&map); - let deser = MPDeserializer { - inner: MapDeserializer::new(map.into_iter()), - }; - let s = Song::deserialize(deser); - println!("{:?}", s); +let input_str = "file: Youtube Rip/VVVVVV Medley.flac +Last-Modified: 2018-03-07T13:18:01Z +Album: VVVVVV OST / PPPPPP +Artist: FamilyJules7x +Composer: Magnus Pålsson +Date: 2014 +Genre: Video Game Music +Title: VVVVVV Medley +Time: 433 +duration: 432.727 +Pos: 1548 +Id: 1549"; + let s = deserialize_response(input_str.lines()); + dbg!(s); }