use std::ffi::CStr;
use std::{ffi::CString, fmt::Display};
use mpdclient_sys::{
mpd_consume_state, mpd_consume_state_MPD_CONSUME_OFF, mpd_consume_state_MPD_CONSUME_ON,
mpd_consume_state_MPD_CONSUME_ONESHOT, mpd_consume_state_MPD_CONSUME_UNKNOWN,
mpd_lookup_consume_state, mpd_lookup_single_state, mpd_parse_consume_state,
mpd_parse_single_state, mpd_run_status, mpd_single_state, mpd_single_state_MPD_SINGLE_OFF,
mpd_single_state_MPD_SINGLE_ON, mpd_single_state_MPD_SINGLE_ONESHOT,
mpd_single_state_MPD_SINGLE_UNKNOWN, mpd_state, mpd_state_MPD_STATE_PAUSE,
mpd_state_MPD_STATE_PLAY, mpd_state_MPD_STATE_STOP, mpd_status, mpd_status_get_audio_format,
mpd_status_get_consume_state, mpd_status_get_crossfade, mpd_status_get_elapsed_ms,
mpd_status_get_error, mpd_status_get_kbit_rate, mpd_status_get_mixrampdb,
mpd_status_get_mixrampdelay, mpd_status_get_next_song_id, mpd_status_get_next_song_pos,
mpd_status_get_partition, mpd_status_get_queue_length, mpd_status_get_queue_version,
mpd_status_get_random, mpd_status_get_repeat, mpd_status_get_single_state,
mpd_status_get_song_id, mpd_status_get_song_pos, mpd_status_get_state,
mpd_status_get_total_time, mpd_status_get_update_id, mpd_status_get_volume,
};
use super::{Connection, queue::Version};
use crate::{AudioFormat, Error, entity::song::Id, error::Result, i32_to_id};
pub struct Status {
inner: *mut mpd_status,
}
impl Status {
pub(super) fn new(connection: &Connection) -> Result<Self> {
let status = unsafe {
let status = mpd_run_status(connection.connection());
if status.is_null() {
let c_str = CStr::from_ptr(mpd_status_get_error(status));
return Err(Error::Status(c_str.to_string_lossy().to_string()));
}
status
};
Ok(Self { inner: status })
}
#[must_use]
pub fn volume(&self) -> Option<i32> {
let volume = unsafe { mpd_status_get_volume(self.inner) };
if volume == -1 { None } else { Some(volume) }
}
#[must_use]
pub fn repeat(&self) -> bool {
unsafe { mpd_status_get_repeat(self.inner) }
}
#[must_use]
pub fn random(&self) -> bool {
unsafe { mpd_status_get_random(self.inner) }
}
#[must_use]
pub fn single_state(&self) -> SingleState {
SingleState::from(unsafe { mpd_status_get_single_state(self.inner) })
}
#[must_use]
pub fn consume_state(&self) -> ConsumeState {
ConsumeState::from(unsafe { mpd_status_get_consume_state(self.inner) })
}
#[must_use]
pub fn queue_length(&self) -> u32 {
unsafe { mpd_status_get_queue_length(self.inner) }
}
#[must_use]
pub fn queue_version(&self) -> Version {
unsafe { mpd_status_get_queue_version(self.inner) }
}
#[must_use]
pub fn state(&self) -> State {
State::from(unsafe { mpd_status_get_state(self.inner) })
}
#[must_use]
pub fn crossfade(&self) -> Option<u32> {
let crsfd = unsafe { mpd_status_get_crossfade(self.inner) };
if crsfd == 0 { None } else { Some(crsfd) }
}
#[must_use]
pub fn mixrampdb(&self) -> Option<f32> {
let mixdb = unsafe { mpd_status_get_mixrampdb(self.inner) };
if mixdb == 0.0 { None } else { Some(mixdb) }
}
#[must_use]
pub fn mixrampdelay(&self) -> Option<f32> {
let mixdelay = unsafe { mpd_status_get_mixrampdelay(self.inner) };
if mixdelay < 0.0 { None } else { Some(mixdelay) }
}
#[allow(clippy::cast_sign_loss)]
#[must_use]
pub fn current_song_pos(&self) -> Option<u32> {
let pos = unsafe { mpd_status_get_song_pos(self.inner) };
if pos == -1 { None } else { Some(pos as u32) }
}
#[must_use]
pub fn current_song_id(&self) -> Option<Id> {
let id = unsafe { mpd_status_get_song_id(self.inner) };
if id == -1 { None } else { Some(i32_to_id(id)) }
}
#[allow(clippy::cast_sign_loss)]
#[must_use]
pub fn next_song_pos(&self) -> Option<u32> {
let pos = unsafe { mpd_status_get_next_song_pos(self.inner) };
if pos == -1 { None } else { Some(pos as u32) }
}
#[must_use]
pub fn next_song_id(&self) -> Option<Id> {
let id = unsafe { mpd_status_get_next_song_id(self.inner) };
if id == -1 { None } else { Some(i32_to_id(id)) }
}
#[must_use]
pub fn elapsed_ms(&self) -> u32 {
unsafe { mpd_status_get_elapsed_ms(self.inner) }
}
#[must_use]
pub fn total_time(&self) -> u32 {
unsafe { mpd_status_get_total_time(self.inner) }
}
#[must_use]
pub fn kbit_rate(&self) -> Option<u32> {
let kbit = unsafe { mpd_status_get_kbit_rate(self.inner) };
if kbit == 0 { None } else { Some(kbit) }
}
#[must_use]
pub fn audio_format(&self) -> Option<AudioFormat> {
unsafe {
let audio_format = mpd_status_get_audio_format(self.inner);
if audio_format.is_null() {
None
} else {
Some(AudioFormat::from(*audio_format))
}
}
}
#[must_use]
pub fn update(&self) -> bool {
let id = unsafe { mpd_status_get_update_id(self.inner) };
match id {
0 => false,
1 => true,
_ => unreachable!(),
}
}
#[must_use]
pub fn partition(&self) -> Option<String> {
unsafe {
let ptr = mpd_status_get_partition(self.inner);
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr).to_string_lossy().to_string())
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State {
Stop = 1,
Play = 2,
Pause = 3,
}
impl From<mpd_state> for State {
fn from(value: mpd_state) -> Self {
#[allow(non_upper_case_globals)]
match value {
mpd_state_MPD_STATE_STOP => Self::Stop,
mpd_state_MPD_STATE_PLAY => Self::Play,
mpd_state_MPD_STATE_PAUSE => Self::Pause,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(clippy::cast_possible_wrap)]
pub enum SingleState {
Off = mpd_single_state_MPD_SINGLE_OFF as isize,
On = mpd_single_state_MPD_SINGLE_ON as isize,
Oneshot = mpd_single_state_MPD_SINGLE_ONESHOT as isize,
}
impl From<mpd_single_state> for SingleState {
fn from(value: mpd_single_state) -> Self {
#[allow(non_upper_case_globals)]
match value {
mpd_single_state_MPD_SINGLE_OFF => Self::Off,
mpd_single_state_MPD_SINGLE_ON => Self::On,
mpd_single_state_MPD_SINGLE_ONESHOT => Self::Oneshot,
_ => unreachable!(),
}
}
}
impl TryFrom<&str> for SingleState {
type Error = Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
let state = unsafe { mpd_parse_single_state(CString::new(value)?.as_ptr()) };
if state == mpd_single_state_MPD_SINGLE_UNKNOWN {
Err(Error::Unknown("SingleState".to_string()))
} else {
Ok(Self::from(state))
}
}
}
impl Display for SingleState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", unsafe {
CStr::from_ptr(mpd_lookup_single_state(*self as u32))
.to_string_lossy()
.to_string()
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(clippy::cast_possible_wrap)]
pub enum ConsumeState {
Off = mpd_consume_state_MPD_CONSUME_OFF as isize,
On = mpd_consume_state_MPD_CONSUME_ON as isize,
Oneshot = mpd_consume_state_MPD_CONSUME_ONESHOT as isize,
}
impl From<mpd_consume_state> for ConsumeState {
fn from(value: mpd_consume_state) -> Self {
#[allow(non_upper_case_globals)]
match value {
mpd_consume_state_MPD_CONSUME_OFF => Self::Off,
mpd_consume_state_MPD_CONSUME_ON => Self::On,
mpd_consume_state_MPD_CONSUME_ONESHOT => Self::Oneshot,
_ => unreachable!(),
}
}
}
impl TryFrom<&str> for ConsumeState {
type Error = Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
let state = unsafe { mpd_parse_consume_state(CString::new(value)?.as_ptr()) };
if state == mpd_consume_state_MPD_CONSUME_UNKNOWN {
Err(Error::Unknown("ConsumeState".to_string()))
} else {
Ok(Self::from(state))
}
}
}
impl Display for ConsumeState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", unsafe {
CStr::from_ptr(mpd_lookup_consume_state(*self as u32))
.to_string_lossy()
.to_string()
})
}
}
#[cfg(test)]
mod tests {
use crate::{ConsumeState, SingleState};
#[test]
fn single_state_display() {
assert_eq!(SingleState::Off.to_string(), "0");
assert_eq!(SingleState::On.to_string(), "1");
assert_eq!(SingleState::Oneshot.to_string(), "oneshot");
}
#[test]
fn single_state_parse() -> eyre::Result<()> {
assert_eq!(SingleState::try_from("0")?, SingleState::Off);
assert_eq!(SingleState::try_from("1")?, SingleState::On);
assert_eq!(SingleState::try_from("oneshot")?, SingleState::Oneshot);
Ok(())
}
#[test]
fn consume_state_display() {
assert_eq!(ConsumeState::Off.to_string(), "0");
assert_eq!(ConsumeState::On.to_string(), "1");
assert_eq!(ConsumeState::Oneshot.to_string(), "oneshot");
}
#[test]
fn consume_state_parse() -> eyre::Result<()> {
assert_eq!(ConsumeState::try_from("0")?, ConsumeState::Off);
assert_eq!(ConsumeState::try_from("1")?, ConsumeState::On);
assert_eq!(ConsumeState::try_from("oneshot")?, ConsumeState::Oneshot);
Ok(())
}
}