2020-06-21 01:00:29 +02:00
use chrono ::prelude ::* ;
2020-06-20 22:21:21 +02:00
use helpers ::* ;
2020-07-24 18:30:59 +02:00
use serde ::{ Deserialize , Serialize } ;
2020-06-21 22:52:23 +02:00
use std ::{ fmt , time ::Duration } ;
2020-06-20 22:21:21 +02:00
2020-07-24 17:19:43 +02:00
/// All information about a track. This is returned by the `currentsong`, `queue`, or
/// `playlistinfo` commands.
2020-07-24 18:30:59 +02:00
#[ derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq) ]
2020-06-20 23:29:09 +02:00
#[ serde(default) ]
2020-06-20 22:21:21 +02:00
pub struct Track {
2020-07-23 21:03:54 +02:00
pub file : String ,
2020-06-20 23:27:00 +02:00
#[ serde(rename = " artistsort " ) ]
2020-07-23 21:03:54 +02:00
pub artist_sort : Option < String > ,
2020-06-20 23:27:00 +02:00
#[ serde(rename = " albumartist " ) ]
2020-07-23 21:03:54 +02:00
pub album_artist : Option < String > ,
2020-06-20 23:27:00 +02:00
#[ serde(rename = " albumsort " ) ]
2020-07-23 21:03:54 +02:00
pub album_sort : Option < String > ,
2020-06-20 23:27:00 +02:00
#[ serde(rename = " albumartistsort " ) ]
2020-07-23 21:03:54 +02:00
pub album_artist_sort : Option < String > ,
2020-06-20 23:27:00 +02:00
#[ serde(deserialize_with = " de_string_or_vec " ) ]
#[ serde(rename = " performer " ) ]
2020-07-23 21:03:54 +02:00
pub performers : Vec < String > ,
pub genre : Option < String > ,
pub title : Option < String > ,
2020-06-20 23:27:00 +02:00
#[ serde(deserialize_with = " de_position " ) ]
2020-07-23 21:03:54 +02:00
pub track : Option < Position > ,
pub album : Option < String > ,
pub artist : Option < String > ,
pub pos : u32 ,
pub id : u32 ,
2020-06-20 23:27:00 +02:00
#[ serde(rename = " last-modified " ) ]
2020-07-23 21:03:54 +02:00
pub last_modified : Option < DateTime < FixedOffset > > ,
2020-06-20 23:27:00 +02:00
#[ serde(rename = " originaldate " ) ]
2020-07-23 21:03:54 +02:00
pub original_date : Option < u16 > ,
pub format : Option < String > ,
2020-06-20 23:27:00 +02:00
#[ serde(deserialize_with = " de_time_float " ) ]
2020-07-23 21:03:54 +02:00
pub duration : Option < Duration > ,
pub label : Option < String > ,
pub date : Option < u16 > ,
2020-06-20 23:27:00 +02:00
#[ serde(deserialize_with = " de_position " ) ]
2020-07-23 21:03:54 +02:00
pub disc : Option < Position > ,
pub musicbraiz_trackid : Option < String > ,
pub musicbrainz_albumid : Option < String > ,
pub musicbrainz_albumartistid : Option < String > ,
pub musicbrainz_artistid : Option < String > ,
2020-06-20 23:27:00 +02:00
pub musicbrainz_releasetrackid : Option < String > ,
2020-07-23 21:03:54 +02:00
pub musicbrainz_trackid : Option < String > ,
pub composer : Option < String > ,
2020-06-20 23:27:00 +02:00
}
2020-06-21 23:19:12 +02:00
/// An empty struct that can be used as the response data for commands that only ever return `OK`
/// or an error message, e.g. `consume` or other boolean toggles.
2020-07-24 18:30:59 +02:00
#[ derive(Serialize, Deserialize, PartialEq, Debug) ]
2020-06-21 23:19:12 +02:00
pub struct UnitResponse { }
2020-06-20 23:27:00 +02:00
/// The position of an item in a list with an optional total length.
2020-07-24 17:19:43 +02:00
///
2020-06-20 23:27:00 +02:00
/// Can be used for e.g. track number,
/// where `Position { 3, 12 }` represents track 3 of a CD with 12 tracks,
/// or disc numbers in a multi-disc set.
2020-07-24 18:30:59 +02:00
#[ derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq) ]
2020-06-20 23:27:00 +02:00
pub struct Position {
pub item_position : u16 ,
2020-07-23 21:03:54 +02:00
pub total_items : Option < u16 > ,
2020-06-20 22:21:21 +02:00
}
2020-06-21 22:52:23 +02:00
impl fmt ::Display for Position {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
match self . total_items {
Some ( n ) = > write! ( f , " {}/{} " , self . item_position , n ) ,
None = > write! ( f , " {} " , self . item_position ) ,
}
}
}
2020-06-21 14:50:25 +02:00
/// Current status as returned by `status`.
///
/// Regarding optional `volume`:
/// The volume is None if mpd is running without local audio output,
/// i.e. on a server with no pulse or alsa output configured.
/// Output via httpd might be used instead
/// but will result in empty `volume` and `audio` fields.
2020-07-24 18:30:59 +02:00
#[ derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq) ]
2020-06-21 14:50:25 +02:00
#[ serde(default) ]
pub struct Status {
2020-07-23 21:03:54 +02:00
pub volume : Option < u8 > ,
2020-06-21 14:50:25 +02:00
#[ serde(deserialize_with = " de_bint " ) ]
2020-07-23 21:03:54 +02:00
pub repeat : bool ,
2020-06-21 14:50:25 +02:00
#[ serde(deserialize_with = " de_bint " ) ]
2020-07-23 21:03:54 +02:00
pub random : bool ,
2020-06-21 14:50:25 +02:00
// TODO: make enum
2020-07-23 21:03:54 +02:00
pub single : u8 ,
2020-06-21 14:50:25 +02:00
#[ serde(deserialize_with = " de_bint " ) ]
2020-07-23 21:03:54 +02:00
pub consume : bool ,
2020-06-21 14:50:25 +02:00
// an empty playlist still has an ID
2020-07-23 21:03:54 +02:00
pub playlist : u32 ,
2020-06-21 14:50:25 +02:00
// mpd returns 0 if there is no current playlist
pub playlistlength : u32 ,
2020-06-21 23:03:42 +02:00
#[ serde(deserialize_with = " de_state " ) ]
2020-07-23 21:03:54 +02:00
pub state : State ,
pub song : Option < u32 > ,
pub songid : Option < u32 > ,
pub nextsong : Option < u32 > ,
pub nextsongid : Option < u32 > ,
2020-06-21 14:50:25 +02:00
#[ serde(deserialize_with = " de_time_float " ) ]
2020-07-23 21:03:54 +02:00
pub elapsed : Option < Duration > ,
2020-06-21 14:50:25 +02:00
#[ serde(deserialize_with = " de_time_float " ) ]
2020-07-23 21:03:54 +02:00
pub duration : Option < Duration > ,
pub bitrate : Option < u16 > ,
pub xfade : Option < u8 > ,
2020-06-21 14:50:25 +02:00
// 0 if unset
2020-07-23 21:03:54 +02:00
pub mixrampdb : f32 ,
pub mixrampdelay : Option < u8 > ,
2020-06-21 14:50:25 +02:00
// “audio: The format emitted by the decoder plugin during playback, format: samplerate:bits:channels.
// See Global Audio Format for a detailed explanation.”
// TODO: make struct
2020-07-23 21:03:54 +02:00
pub audio : Option < String > ,
pub updating_db : Option < u32 > ,
pub error : Option < String > ,
2020-06-21 14:50:25 +02:00
}
2020-07-24 17:16:03 +02:00
/// An object in the file system, as returned by the `listfiles` command.
2020-07-24 17:19:43 +02:00
///
2020-07-24 17:16:03 +02:00
/// For directories, the `size` will be `-1`, and [`is_directory`] is provided to check that.
///
/// [`is_directory`]: #method.is_directory
///
/// Properly parsing `listfiles` into a Vector of some enum with variants for file and directory
/// would make the deserialization code a lot more complicated, so I’m not interested in doing that
/// at this point in time.
2020-07-24 18:30:59 +02:00
#[ derive(Serialize, Deserialize, Clone, Debug, PartialEq) ]
2020-07-24 14:46:51 +02:00
pub struct File {
#[ serde(alias = " directory " , rename = " file " ) ]
pub name : String ,
#[ serde(rename = " last-modified " ) ]
pub last_modified : DateTime < FixedOffset > ,
2020-07-24 17:16:03 +02:00
#[ serde(default = " minus_one " ) ]
pub size : i64 ,
}
fn minus_one ( ) -> i64 {
- 1
2020-07-24 14:46:51 +02:00
}
impl File {
2020-07-24 17:16:03 +02:00
/// Returns true if this file is a directory.
/// Internally, this just checks if `size == -1`.
2020-07-24 14:46:51 +02:00
pub fn is_directory ( & self ) -> bool {
2020-07-24 17:16:03 +02:00
self . size = = - 1
2020-07-24 14:46:51 +02:00
}
}
2020-07-24 18:30:59 +02:00
#[ derive(Serialize, Deserialize, Clone, Debug, PartialEq) ]
2020-06-21 23:03:42 +02:00
pub enum State {
Stop ,
Play ,
Pause ,
}
// Default implementation so I can derive default for containing structs.
impl Default for State {
fn default ( ) -> Self {
Self ::Stop
}
}
2020-06-21 19:12:36 +02:00
/// Database statistics as returned by the `stats` command.
2020-07-24 18:30:59 +02:00
#[ derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq) ]
2020-06-21 19:12:36 +02:00
#[ serde(default) ]
pub struct Stats {
2020-07-23 21:03:54 +02:00
pub artists : u32 ,
pub albums : u32 ,
pub songs : u32 ,
2020-06-21 19:12:36 +02:00
#[ serde(deserialize_with = " de_time_int " ) ]
2020-07-23 21:03:54 +02:00
pub uptime : Duration ,
2020-06-21 19:12:36 +02:00
#[ serde(deserialize_with = " de_time_int " ) ]
pub db_playtime : Duration ,
// TODO: this is a unix era. use some datetime for it
2020-07-23 21:03:54 +02:00
pub db_update : u32 ,
2020-06-21 19:12:36 +02:00
#[ serde(deserialize_with = " de_time_int " ) ]
2020-07-23 21:03:54 +02:00
pub playtime : Duration ,
2020-06-21 19:12:36 +02:00
}
2020-06-20 22:21:21 +02:00
/// Deserialization helpers to handle the quirks of mpd’s output.
mod helpers {
2020-06-20 23:27:00 +02:00
use super ::* ;
use crate ::SEPARATOR ;
use serde ::{ de , Deserialize } ;
use std ::{ str ::FromStr , time ::Duration } ;
2020-06-20 22:21:21 +02:00
2020-06-20 23:27:00 +02:00
/// Deserialize time from an integer that represents the seconds.
/// mpd uses int for the database stats (e.g. total time played).
pub fn de_time_int < ' de , D > ( deserializer : D ) -> Result < Duration , D ::Error >
2020-07-23 21:03:54 +02:00
where
D : de ::Deserializer < ' de > ,
{
2020-06-20 23:27:00 +02:00
u64 ::deserialize ( deserializer ) . map ( Duration ::from_secs )
}
2020-06-20 22:21:21 +02:00
2020-06-20 23:27:00 +02:00
/// Deserialize time from a float that represents the seconds.
/// mpd uses floats for the current status (e.g. time elapsed in song).
2020-06-21 14:50:25 +02:00
pub fn de_time_float < ' de , D > ( deserializer : D ) -> Result < Option < Duration > , D ::Error >
2020-07-23 21:03:54 +02:00
where
D : de ::Deserializer < ' de > ,
{
2020-06-21 14:50:25 +02:00
f64 ::deserialize ( deserializer ) . map ( Duration ::from_secs_f64 ) . map ( Some )
2020-06-20 23:27:00 +02:00
}
2020-06-21 23:03:42 +02:00
/// Deserialize the playback state.
pub fn de_state < ' de , D > ( deserializer : D ) -> Result < State , D ::Error >
2020-07-23 21:03:54 +02:00
where
D : de ::Deserializer < ' de > ,
{
2020-06-21 23:03:42 +02:00
match String ::deserialize ( deserializer ) ? . as_ref ( ) {
" play " = > Ok ( State ::Play ) ,
" pause " = > Ok ( State ::Pause ) ,
" stop " = > Ok ( State ::Stop ) ,
s = > Err ( de ::Error ::invalid_value (
de ::Unexpected ::Str ( s ) ,
& " expected one of play, pause, or stop " ,
) ) ,
}
}
2020-06-20 23:27:00 +02:00
pub fn de_string_or_vec < ' de , D > ( deserializer : D ) -> Result < Vec < String > , D ::Error >
2020-07-23 21:03:54 +02:00
where
D : de ::Deserializer < ' de > ,
{
2020-06-20 23:27:00 +02:00
String ::deserialize ( deserializer ) . map ( | s | s . split ( SEPARATOR ) . map ( std ::string ::String ::from ) . collect ( ) )
}
2020-06-20 22:21:21 +02:00
2020-06-20 23:27:00 +02:00
/// mpd uses bints (0 or 1) to represent booleans,
/// so we need a special parser for those.
pub fn de_bint < ' de , D > ( deserializer : D ) -> Result < bool , D ::Error >
2020-07-23 21:03:54 +02:00
where
D : de ::Deserializer < ' de > ,
{
2020-06-20 23:27:00 +02:00
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 " ) ) ,
2020-06-20 22:21:21 +02:00
}
2020-06-20 23:27:00 +02:00
}
2020-06-20 22:21:21 +02:00
2020-06-20 23:27:00 +02:00
/// Deserialize a position with an optional total length.
/// The input string here is either a number or two numbers separated by `SEPARATOR`.
pub fn de_position < ' de , D > ( deserializer : D ) -> Result < Option < Position > , D ::Error >
2020-07-23 21:03:54 +02:00
where
D : de ::Deserializer < ' de > ,
{
2020-06-20 23:53:32 +02:00
let s = String ::deserialize ( deserializer ) ? ;
2020-06-21 00:00:24 +02:00
let mut ints = s . split ( SEPARATOR ) . filter_map ( | s | u16 ::from_str ( s ) . ok ( ) ) ;
if let Some ( n ) = ints . next ( ) {
2020-06-20 23:53:32 +02:00
return Ok ( Some ( Position {
item_position : n ,
2020-07-23 21:03:54 +02:00
total_items : ints . next ( ) ,
2020-06-20 23:53:32 +02:00
} ) ) ;
2020-06-20 22:21:21 +02:00
}
2020-06-20 23:27:00 +02:00
Ok ( None )
}
2020-06-20 22:21:21 +02:00
}