empidee/src/commands/querying_mpd_status/status.rs

242 lines
7.5 KiB
Rust

use std::collections::HashMap;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::common::{Audio, BoolOrOneshot, SongId, SongPosition};
use crate::commands::{
get_and_parse_optional_property, get_and_parse_property, get_optional_property, get_property,
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StatusResponseState {
Play,
Stop,
Pause,
}
impl FromStr for StatusResponseState {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"play" => Ok(StatusResponseState::Play),
"stop" => Ok(StatusResponseState::Stop),
"pause" => Ok(StatusResponseState::Pause),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatusResponse {
pub partition: String,
// Note: the Option<>::None here is serialized as -1
pub volume: Option<u8>,
pub repeat: bool,
pub random: bool,
pub single: BoolOrOneshot,
pub consume: BoolOrOneshot,
pub playlist: u32,
pub playlist_length: u64,
pub state: StatusResponseState,
pub song: Option<SongPosition>,
pub song_id: Option<SongId>,
pub next_song: Option<SongPosition>,
pub next_song_id: Option<SongId>,
pub time: Option<(u64, u64)>,
pub elapsed: Option<f64>,
pub duration: Option<f64>,
pub bitrate: Option<u32>,
pub xfade: Option<u32>,
pub mixrampdb: Option<f64>,
pub mixrampdelay: Option<f64>,
pub audio: Option<Audio>,
pub updating_db: Option<u64>,
pub error: Option<String>,
pub last_loaded_playlist: Option<String>,
}
#[inline]
fn parse_status_response(
parts: ResponseAttributes<'_>,
) -> Result<StatusResponse, ResponseParserError> {
let parts: HashMap<&str, GenericResponseValue> = parts.into();
let partition = get_property!(parts, "partition", Text).to_string();
let volume = match get_property!(parts, "volume", Text) {
"-1" => None,
volume => Some(
volume
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("volume", volume))?,
),
};
let repeat = match get_property!(parts, "repeat", Text) {
"0" => Ok(false),
"1" => Ok(true),
repeat => Err(ResponseParserError::InvalidProperty("repeat", repeat)),
}?;
let random = match get_property!(parts, "random", Text) {
"0" => Ok(false),
"1" => Ok(true),
random => Err(ResponseParserError::InvalidProperty("random", random)),
}?;
let single = get_and_parse_property!(parts, "single", Text);
let consume = get_and_parse_property!(parts, "consume", Text);
let playlist: u32 = get_and_parse_property!(parts, "playlist", Text);
let playlist_length: u64 = get_and_parse_property!(parts, "playlistlength", Text);
let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
let next_song: Option<SongPosition> = get_and_parse_optional_property!(parts, "nextsong", Text);
let next_song_id: Option<SongId> = get_and_parse_optional_property!(parts, "nextsongid", Text);
let time = match get_optional_property!(parts, "time", Text) {
Some(time) => {
let mut parts = time.split(':');
let elapsed = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
let duration = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, time))?
.parse()
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
Some((elapsed, duration))
}
None => None,
};
let elapsed = get_and_parse_optional_property!(parts, "elapsed", Text);
let duration = get_and_parse_optional_property!(parts, "duration", Text);
let bitrate = get_and_parse_optional_property!(parts, "bitrate", Text);
let xfade = get_and_parse_optional_property!(parts, "xfade", Text);
let mixrampdb = get_and_parse_optional_property!(parts, "mixrampdb", Text);
let mixrampdelay = get_and_parse_optional_property!(parts, "mixrampdelay", Text);
let audio = get_and_parse_optional_property!(parts, "audio", Text);
let updating_db = get_and_parse_optional_property!(parts, "updating_db", Text);
let error = get_and_parse_optional_property!(parts, "error", Text);
let last_loaded_playlist =
get_and_parse_optional_property!(parts, "last_loaded_playlist", Text);
Ok(StatusResponse {
partition,
volume,
repeat,
random,
single,
consume,
playlist,
playlist_length,
state,
song,
song_id,
next_song,
next_song_id,
time,
elapsed,
duration,
bitrate,
xfade,
mixrampdb,
mixrampdelay,
audio,
updating_db,
error,
last_loaded_playlist,
})
}
pub struct Status;
impl Command for Status {
type Response = StatusResponse;
const COMMAND: &'static str = "status";
fn parse_request(_parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
Ok((Request::Status, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
parse_status_response(parts)
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_status_response() {
let contents = indoc! { r#"
volume: 66
repeat: 1
random: 1
single: 0
consume: 0
partition: default
playlist: 2
playlistlength: 78
mixrampdb: 0
state: play
song: 0
songid: 1
time: 225:263
elapsed: 225.376
bitrate: 127
duration: 262.525
audio: 44100:f:2
nextsong: 44
nextsongid: 45
OK
"# };
assert_eq!(
Status::parse_raw_response(contents),
Ok(StatusResponse {
partition: "default".into(),
volume: Some(66),
repeat: true,
random: true,
single: BoolOrOneshot::False,
consume: BoolOrOneshot::False,
playlist: 2,
playlist_length: 78,
state: StatusResponseState::Play,
song: Some(0),
song_id: Some(1),
next_song: Some(44),
next_song_id: Some(45),
time: Some((225, 263)),
elapsed: Some(225.376),
duration: Some(262.525),
bitrate: Some(127),
xfade: None,
mixrampdb: Some(0.0),
mixrampdelay: None,
audio: Some(Audio {
sample_rate: 44100,
bits: 16,
channels: 2,
}),
updating_db: None,
error: None,
last_loaded_playlist: None,
}),
);
}
}