#[macro_use]
mod util_macros;
mod list;
mod playlist;
mod song;
use chrono::ParseError;
use std::error::Error;
use std::fmt;
use std::num::{ParseFloatError, ParseIntError};
use std::sync::Arc;
use std::time::Duration;
use crate::commands::{SingleMode, SongId, SongPosition};
use crate::raw::Frame;
use crate::sealed;
pub use list::List;
pub use playlist::Playlist;
pub use song::{Song, SongInQueue, SongRange};
type KeyValuePair = (Arc<str>, String);
pub trait Response: Sized + sealed::Sealed {
#[doc(hidden)]
fn from_frame(frame: Frame) -> Result<Self, TypedResponseError>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TypedResponseError {
field: &'static str,
kind: ErrorKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum ErrorKind {
Missing,
UnexpectedField(String),
InvalidValue(String),
MalformedInteger(ParseIntError),
MalformedFloat(ParseFloatError),
MalformedTimestamp(ParseError),
}
impl fmt::Display for TypedResponseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error converting response: ")?;
match &self.kind {
ErrorKind::Missing => write!(f, "field {:?} is but missing", self.field),
ErrorKind::InvalidValue(val) => {
write!(f, "value {:?} is invalid for field {:?}", val, self.field)
}
ErrorKind::UnexpectedField(found) => {
write!(f, "Expected field {:?} but found {:?}", self.field, found)
}
ErrorKind::MalformedInteger(_) => write!(f, "field {:?} is not an integer", self.field),
ErrorKind::MalformedFloat(_) => write!(f, "field {:?} is not a float", self.field),
ErrorKind::MalformedTimestamp(_) => {
write!(f, "field {:?} is not a timestamp", self.field)
}
}
}
}
impl Error for TypedResponseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.kind {
ErrorKind::MalformedFloat(e) => Some(e),
ErrorKind::MalformedInteger(e) => Some(e),
ErrorKind::MalformedTimestamp(e) => Some(e),
_ => None,
}
}
}
pub type Empty = ();
impl sealed::Sealed for Empty {}
impl Response for Empty {
fn from_frame(_: Frame) -> Result<Self, TypedResponseError> {
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum PlayState {
Stopped,
Playing,
Paused,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub struct Status {
pub volume: u8,
pub state: PlayState,
pub repeat: bool,
pub random: bool,
pub consume: bool,
pub single: SingleMode,
pub playlist_version: u32,
pub playlist_length: usize,
pub current_song: Option<(SongPosition, SongId)>,
pub next_song: Option<(SongPosition, SongId)>,
pub elapsed: Option<Duration>,
pub duration: Option<Duration>,
pub bitrate: Option<u64>,
pub crossfade: Duration,
pub update_job: Option<u64>,
pub error: Option<String>,
pub partition: Option<String>,
}
impl sealed::Sealed for Status {}
impl Response for Status {
fn from_frame(mut raw: Frame) -> Result<Self, TypedResponseError> {
let single = match raw.get("single") {
None => SingleMode::Disabled,
Some(val) => match val.as_str() {
"0" => SingleMode::Disabled,
"1" => SingleMode::Enabled,
"oneshot" => SingleMode::Oneshot,
_ => {
return Err(TypedResponseError {
field: "single",
kind: ErrorKind::InvalidValue(val),
})
}
},
};
let mut partition = raw.get("partition");
if partition.as_deref() == Some("default") {
partition = None;
}
Ok(Self {
volume: field!(raw, "volume" integer default 0),
state: field!(raw, "state" PlayState),
repeat: field!(raw, "repeat" boolean),
random: field!(raw, "random" boolean),
consume: field!(raw, "consume" boolean),
single,
playlist_length: field!(raw, "playlistlength" integer default 0),
playlist_version: field!(raw, "playlist" integer default 0),
current_song: song_identifier!(raw, "song", "songid"),
next_song: song_identifier!(raw, "nextsong", "nextsongid"),
elapsed: field!(raw, "elapsed" duration optional),
duration: field!(raw, "duration" duration optional),
bitrate: field!(raw, "bitrate" integer optional),
crossfade: field!(raw, "xfade" duration default Duration::from_secs(0)),
update_job: field!(raw, "update_job" integer optional),
error: raw.get("error"),
partition,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub struct Stats {
pub artists: u64,
pub albums: u64,
pub songs: u64,
pub uptime: Duration,
pub playtime: Duration,
pub db_playtime: Duration,
pub db_last_update: u64,
}
impl sealed::Sealed for Stats {}
impl Response for Stats {
fn from_frame(mut raw: Frame) -> Result<Self, TypedResponseError> {
Ok(Self {
artists: field!(raw, "artists" integer),
albums: field!(raw, "albums" integer),
songs: field!(raw, "songs" integer),
uptime: field!(raw, "uptime" duration),
playtime: field!(raw, "playtime" duration),
db_playtime: field!(raw, "db_playtime" duration),
db_last_update: field!(raw, "db_update" integer),
})
}
}
impl sealed::Sealed for Option<SongInQueue> {}
impl Response for Option<SongInQueue> {
fn from_frame(raw: Frame) -> Result<Self, TypedResponseError> {
let mut vec = SongInQueue::parse_frame(raw, Some(1))?;
Ok(vec.pop())
}
}
impl sealed::Sealed for Vec<SongInQueue> {}
impl Response for Vec<SongInQueue> {
fn from_frame(raw: Frame) -> Result<Self, TypedResponseError> {
Ok(SongInQueue::parse_frame(raw, None)?)
}
}
impl sealed::Sealed for Vec<Song> {}
impl Response for Vec<Song> {
fn from_frame(raw: Frame) -> Result<Self, TypedResponseError> {
Ok(Song::parse_frame(raw, None)?)
}
}
impl sealed::Sealed for SongId {}
impl Response for SongId {
fn from_frame(mut raw: Frame) -> Result<Self, TypedResponseError> {
Ok(SongId(field!(raw, "Id" integer)))
}
}
impl sealed::Sealed for Vec<Playlist> {}
impl Response for Vec<Playlist> {
fn from_frame(raw: Frame) -> Result<Self, TypedResponseError> {
let fields_count = raw.fields_len();
Ok(Playlist::parse_frame(raw, fields_count)?)
}
}
impl sealed::Sealed for List {}
impl Response for List {
fn from_frame(frame: Frame) -> Result<Self, TypedResponseError> {
Ok(List::from_frame(frame))
}
}