use std::{
ffi::CStr,
time::{Duration, SystemTime, UNIX_EPOCH},
};
#[cfg(feature = "protocol_0_24")]
use mpdclient_sys::mpd_song_get_added;
use mpdclient_sys::{
mpd_song, mpd_song_get_audio_format, mpd_song_get_duration, mpd_song_get_duration_ms,
mpd_song_get_end, mpd_song_get_id, mpd_song_get_last_modified, mpd_song_get_pos,
mpd_song_get_prio, mpd_song_get_start, mpd_song_get_tag, mpd_song_get_uri,
};
use crate::{
AudioFormat, Tag,
error::{Error, Result},
};
use super::{Entity, EntityReceiver};
pub type Id = u32;
#[derive(Debug)]
pub struct Song {
inner: *mut mpd_song,
}
impl Song {
pub(super) fn new(mpd_song: *mut mpd_song) -> Self {
Self { inner: mpd_song }
}
pub(crate) fn extract_one(mut entity_recv: EntityReceiver) -> Result<Self> {
let Some(entity) = entity_recv.next() else {
return Err(Error::NoEntity);
};
let Entity::Song(song) = entity? else {
return Err(Error::EntityReturnType);
};
if entity_recv.next().is_some() {
return Err(Error::MultipleEntities);
}
Ok(song)
}
pub(crate) fn extract_all(entity_recv: EntityReceiver) -> Result<Vec<Self>> {
let mut songs: Vec<Song> = vec![];
for entity in entity_recv {
let Entity::Song(song) = entity? else {
return Err(Error::EntityReturnType);
};
songs.push(song);
}
Ok(songs)
}
#[must_use]
pub fn uri(&self) -> String {
unsafe {
CStr::from_ptr(mpd_song_get_uri(self.inner))
.to_string_lossy()
.to_string()
}
}
#[must_use]
pub fn tag(&self, tag: Tag) -> ReturnTag<'_> {
ReturnTag::new(self, tag)
}
#[must_use]
pub fn duration(&self) -> Option<u32> {
let dur = unsafe { mpd_song_get_duration(self.inner) };
if dur == 0 { None } else { Some(dur) }
}
#[must_use]
pub fn duration_ms(&self) -> Option<u32> {
let dur = unsafe { mpd_song_get_duration_ms(self.inner) };
if dur == 0 { None } else { Some(dur) }
}
#[must_use]
pub fn start(&self) -> u32 {
unsafe { mpd_song_get_start(self.inner) }
}
#[must_use]
pub fn end(&self) -> Option<u32> {
let end = unsafe { mpd_song_get_end(self.inner) };
if end == 0 { None } else { Some(end) }
}
#[allow(clippy::cast_sign_loss)]
#[must_use]
pub fn last_modified(&self) -> Option<SystemTime> {
let time = unsafe { mpd_song_get_last_modified(self.inner) };
if time == 0 {
None
} else {
Some(UNIX_EPOCH + Duration::from_secs(time as u64))
}
}
#[cfg(feature = "protocol_0_24")]
#[allow(clippy::cast_sign_loss)]
#[must_use]
pub fn added(&self) -> Option<SystemTime> {
let time = unsafe { mpd_song_get_added(self.inner) };
if time == 0 {
None
} else {
Some(UNIX_EPOCH + Duration::from_secs(time as u64))
}
}
#[must_use]
pub fn pos(&self) -> u32 {
unsafe { mpd_song_get_pos(self.inner) }
}
#[must_use]
pub fn id(&self) -> Id {
unsafe { mpd_song_get_id(self.inner) }
}
#[must_use]
pub fn prio(&self) -> u32 {
unsafe { mpd_song_get_prio(self.inner) }
}
#[must_use]
pub fn audio_format(&self) -> Option<AudioFormat> {
unsafe {
let format = mpd_song_get_audio_format(self.inner);
if format.is_null() {
return None;
}
Some(AudioFormat::from(*format))
}
}
}
unsafe impl Send for Song {}
#[derive(Debug)]
pub struct ReturnTag<'a> {
song: &'a Song,
tag: Tag,
pass: u32,
}
impl<'a> ReturnTag<'a> {
pub(crate) fn new(song: &'a Song, tag: Tag) -> Self {
Self { song, tag, pass: 0 }
}
fn get_tag(&self, idx: u32) -> Option<String> {
unsafe {
let value = mpd_song_get_tag(self.song.inner, self.tag as i32, idx);
if value.is_null() {
None
} else {
Some(CStr::from_ptr(value).to_string_lossy().to_string())
}
}
}
#[must_use]
pub fn receive_all(self) -> Vec<String> {
self.collect()
}
}
impl Iterator for ReturnTag<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
let tag = self.get_tag(self.pass);
self.pass += 1;
tag
}
}