mparsed/src/lib.rs
2020-06-21 23:11:41 +02:00

307 lines
9.0 KiB
Rust

use serde::de;
use std::collections::HashMap;
mod error;
use error::{Error, MpdResult};
use itertools::Itertools;
mod structs;
pub use structs::{Position, State, Stats, Status, Track};
/// some unprintable character to separate repeated keys
const SEPARATOR: char = '\x02';
pub fn deserialize_response<'a, I: Iterator<Item = &'a str>, T: de::DeserializeOwned>(input: I) -> MpdResult<T> {
let mut map: HashMap<String, String> = HashMap::new();
for line in input {
if line.starts_with("OK") {
break;
} else if let Some(message) = line.strip_prefix("ACK") {
return Err(Error::from_str(message.trim()));
}
match line.splitn(2, ": ").next_tuple() {
Some((k, 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),
}
}
// Eventually, I’d like to use my own deserializer instead of envy
Ok(envy::from_iter(map).unwrap())
}
pub fn read_playlist_info<'a, I: Iterator<Item = &'a str>>(input: I) -> MpdResult<Vec<Track>> {
input
.peekable()
.batching(|it| {
let mut v = match it.next() {
// if the playlist is empty or we’ve reached the end,
// the response is only the `OK` line.
Some("OK") => return None,
// otherwise this is the file attribute and thus the first key-value of this track
Some(s) => vec![s],
_ => return None,
};
// Only peek here in case we encounter the `file:` line which we still need for the next track.
while let Some(l) = it.peek() {
if l.starts_with("file:") {
return Some(v);
}
if l.starts_with("OK") {
return Some(v);
}
v.push(it.next().unwrap());
}
None
})
.map(|b| deserialize_response(b.into_iter()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::DateTime;
use std::time::Duration;
#[test]
fn track_de_test() {
let input_str = "file: American Pie.flac
Last-Modified: 2019-12-16T21:38:54Z
Album: American Pie
Artist: Don McLean
Date: 1979
Genre: Rock
Title: American Pie
Track: 1
Time: 512
duration: 512.380
Pos: 1367
Id: 1368
OK";
let t: Track = deserialize_response(input_str.lines()).unwrap();
assert_eq!(
t,
Track {
file: "American Pie.flac".to_string(),
last_modified: Some(DateTime::parse_from_rfc3339("2019-12-16T21:38:54Z").unwrap()),
album: Some("American Pie".to_string()),
artist: Some("Don McLean".to_string()),
date: Some(1979),
genre: Some("Rock".to_string()),
title: Some("American Pie".to_string()),
track: Some(Position {
item_position: 1,
total_items: None,
}),
duration: Some(Duration::from_secs_f64(512.380)),
pos: 1367,
id: 1368,
..Track::default()
}
);
}
#[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: Track = deserialize_response(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(DateTime::parse_from_rfc3339("2018-11-11T09:01:54Z").unwrap()),
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: Some(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()
}
);
}
#[test]
fn de_status_test() {
let input_str = "volume: 74
repeat: 0
random: 1
single: 0
consume: 0
playlist: 6
playlistlength: 5364
mixrampdb: 0.000000
state: play
song: 3833
songid: 3834
time: 70:164
elapsed: 69.642
bitrate: 702
duration: 163.760
audio: 44100:16:2
nextsong: 4036
nextsongid: 4037
OK";
let s: Status = deserialize_response(input_str.lines()).unwrap();
assert_eq!(
s,
Status {
volume: Some(74),
random: true,
playlist: 6,
playlistlength: 5364,
mixrampdb: 0.0,
state: State::Play,
song: Some(3833),
songid: Some(3834),
elapsed: Some(Duration::from_secs_f64(69.642)),
bitrate: Some(702),
duration: Some(Duration::from_secs_f64(163.760)),
audio: Some(String::from("44100:16:2")),
nextsong: Some(4036),
nextsongid: Some(4037),
..Status::default()
}
);
}
#[test]
fn de_playlist_test() {
let input_str = "file: 137 A New World.mp3
Last-Modified: 2018-03-07T13:11:43Z
Artist: Arata Iiyoshi
Title: A New World
Album: Pokemon Mystery Dungeon: Explorers of Sky
duration: 225.802
Pos: 1000
Id: 6365
file: 139 Thoughts For Friends.mp3
Last-Modified: 2018-03-07T13:11:43Z
Artist: Arata Iiyoshi
Title: Thoughts For Friends
Album: Pokemon Mystery Dungeon: Explorers of Sky
duration: 66.560
Pos: 1001
Id: 6366
file: 140 A Message On the Wind.mp3
Last-Modified: 2018-03-07T13:11:43Z
Artist: Arata Iiyoshi
Title: A Message On the Wind
Album: Pokemon Mystery Dungeon: Explorers of Sky
duration: 50.155
Pos: 1002
Id: 6367
OK";
let queue = read_playlist_info(input_str.lines());
let first_track = Track {
file: "137 A New World.mp3".into(),
title: Some("A New World".into()),
artist: Some("Arata Iiyoshi".into()),
album: Some("Pokemon Mystery Dungeon: Explorers of Sky".into()),
last_modified: Some(DateTime::parse_from_rfc3339("2018-03-07T13:11:43Z").unwrap()),
duration: Some(Duration::from_secs_f64(225.802)),
pos: 1000,
id: 6365,
..Track::default()
};
assert_eq!(
queue,
Ok(vec![
first_track.clone(),
Track {
file: "139 Thoughts For Friends.mp3".into(),
title: Some("Thoughts For Friends".into()),
duration: Some(Duration::from_secs_f64(66.56)),
pos: 1001,
id: 6366,
..first_track.clone()
},
Track {
file: "140 A Message On the Wind.mp3".into(),
title: Some("A Message On the Wind".into()),
duration: Some(Duration::from_secs_f64(50.155)),
pos: 1002,
id: 6367,
..first_track
},
])
);
let queue = read_playlist_info("OK".lines());
assert_eq!(queue, Ok(vec![]));
}
#[test]
fn de_stats_test() {
let input_str = "uptime: 23691
playtime: 11288
artists: 2841
albums: 2455
songs: 40322
db_playtime: 11620284
db_update: 1588433046";
let s: Stats = deserialize_response(input_str.lines()).unwrap();
assert_eq!(
s,
Stats {
uptime: Duration::from_secs(23691),
playtime: Duration::from_secs(11288),
artists: 2841,
albums: 2455,
songs: 40322,
db_playtime: Duration::from_secs(11620284),
db_update: 1588433046,
}
);
}
}