kageru
c374a04417
This is also the first occurence of multiple structs in one response. The Itertools.batching approach seems to work quite well.
284 lines
8.5 KiB
Rust
284 lines
8.5 KiB
Rust
use serde::de;
|
|
use std::collections::HashMap;
|
|
mod error;
|
|
use error::{Error, MpdResult};
|
|
use itertools::Itertools;
|
|
mod structs;
|
|
pub use structs::{Position, 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()));
|
|
}
|
|
|
|
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),
|
|
}
|
|
}
|
|
// 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: String::from("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![]));
|
|
}
|
|
}
|