2020-06-21 14:20:35 +02:00
use serde ::de ;
2020-06-20 22:50:33 +02:00
use std ::collections ::HashMap ;
mod error ;
use error ::{ Error , MpdResult } ;
2020-06-21 15:31:08 +02:00
use itertools ::Itertools ;
2020-06-20 22:50:33 +02:00
mod structs ;
2020-06-21 23:11:41 +02:00
pub use structs ::{ Position , State , Stats , Status , Track } ;
2020-06-20 22:50:33 +02:00
/// some unprintable character to separate repeated keys
const SEPARATOR : char = '\x02' ;
2020-06-21 14:20:35 +02:00
pub fn deserialize_response < ' a , I : Iterator < Item = & ' a str > , T : de ::DeserializeOwned > ( input : I ) -> MpdResult < T > {
2020-06-20 22:50:33 +02:00
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 ( ) ) ) ;
}
2020-06-21 14:20:35 +02:00
2020-06-21 23:11:41 +02:00
match line . splitn ( 2 , " : " ) . next_tuple ( ) {
Some ( ( k , v ) ) = > {
2020-06-20 22:50:33 +02:00
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
2020-06-21 14:20:35 +02:00
Ok ( envy ::from_iter ( map ) . unwrap ( ) )
2020-06-20 22:50:33 +02:00
}
2020-06-21 15:31:08 +02:00
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 ( )
}
2020-06-20 22:50:33 +02:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2020-06-21 01:00:29 +02:00
use chrono ::DateTime ;
2020-06-21 14:50:25 +02:00
use std ::time ::Duration ;
2020-06-20 22:50:33 +02:00
#[ test ]
fn track_de_test ( ) {
let input_str = " file: American Pie.flac
Last - Modified : 2019 - 12 - 16 T21 :38 :54 Z
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 " ;
2020-06-21 00:03:06 +02:00
let t : Track = deserialize_response ( input_str . lines ( ) ) . unwrap ( ) ;
2020-06-20 22:50:33 +02:00
assert_eq! (
t ,
Track {
file : " American Pie.flac " . to_string ( ) ,
2020-06-21 01:00:29 +02:00
last_modified : Some ( DateTime ::parse_from_rfc3339 ( " 2019-12-16T21:38:54Z " ) . unwrap ( ) ) ,
2020-06-20 22:50:33 +02:00
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 ( ) ) ,
2020-06-20 23:27:00 +02:00
track : Some ( Position {
item_position : 1 ,
total_items : None ,
} ) ,
2020-06-21 14:50:25 +02:00
duration : Some ( Duration ::from_secs_f64 ( 512.380 ) ) ,
2020-06-20 22:50:33 +02:00
pos : 1367 ,
id : 1368 ,
.. Track ::default ( )
}
) ;
}
2020-06-20 23:27:00 +02:00
#[ 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 - 11 T09 :01 :54 Z
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 - 474 e - b64a - 803 b0364fa75
MUSICBRAINZ_ALBUMID : 688 d9252 - f897 - 4 ab6 - 879 d - 5 cb83bb71087
MUSICBRAINZ_ARTISTID : b972f589 - fb0e - 474 e - b64a - 803 b0364fa75
MUSICBRAINZ_RELEASETRACKID : 54 a2632f - fa98 - 3713 - bd75 - d7effc03d0b1
MUSICBRAINZ_TRACKID : 2 dd10dd8 - e8de - 4479 - a092 - 9 a04c2760fd6
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 " #;
2020-06-21 00:03:06 +02:00
let t : Track = deserialize_response ( input_str . lines ( ) ) . unwrap ( ) ;
2020-06-20 23:27:00 +02:00
assert_eq! (
t ,
Track {
file : " 06 Symphonie No. 41 en ut majeur, K. 551 _Jupiter_ I. Allegro Vivace.flac " . to_string ( ) ,
2020-06-21 01:00:29 +02:00
last_modified : Some ( DateTime ::parse_from_rfc3339 ( " 2018-11-11T09:01:54Z " ) . unwrap ( ) ) ,
2020-06-20 23:27:00 +02:00
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 ) ,
} ) ,
2020-06-21 14:50:25 +02:00
duration : Some ( Duration ::from_secs_f64 ( 682.840 ) ) ,
2020-06-20 23:27:00 +02:00
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 ( )
}
) ;
}
2020-06-21 14:50:25 +02:00
#[ 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 ,
2020-06-21 23:03:42 +02:00
state : State ::Play ,
2020-06-21 14:50:25 +02:00
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 ( )
}
) ;
}
2020-06-21 15:31:08 +02:00
#[ test ]
fn de_playlist_test ( ) {
let input_str = " file: 137 A New World.mp3
Last - Modified : 2018 - 03 - 07 T13 :11 :43 Z
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 - 07 T13 :11 :43 Z
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 - 07 T13 :11 :43 Z
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! [ ] ) ) ;
}
2020-06-21 19:12:36 +02:00
#[ 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 ,
}
) ;
}
2020-06-20 22:50:33 +02:00
}