307 lines
9.0 KiB
Rust
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,
|
|
}
|
|
);
|
|
}
|
|
}
|