//! A high-level client for interacting with an Mpd server. //! //! The client provides methods for common operations such as playing, pausing, and //! managing the playlist, and returns the expected response types directly //! from its methods. use crate::{Request, commands::*, types::SongPosition}; #[cfg(feature = "futures")] use futures_util::{ AsyncBufReadExt, io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}, }; #[cfg(feature = "tokio")] use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufStream}; use thiserror::Error; pub struct MpdClient<'a, T> where T: AsyncWrite + AsyncRead + Unpin, { stream: BufStream<&'a mut T>, mpd_version: Option, } #[derive(Error, Debug)] pub enum MpdClientError { #[error("Connection error: {0}")] ConnectionError(#[from] std::io::Error), #[error("Failed to parse MPD response: {0}")] ResponseParseError(#[from] crate::commands::ResponseParserError), #[error("MPD returned an error: {0}")] MpdError(#[from] crate::response::MpdError), } impl<'a, T> MpdClient<'a, T> where T: AsyncWrite + AsyncRead + Unpin, { pub async fn new(connection: &'a mut T) -> Result { let mut client = MpdClient { stream: BufStream::new(connection), mpd_version: None, }; client.read_initial_mpd_version().await?; Ok(client) } pub async fn wrap_existing(connection: &'a mut T, mpd_version: Option) -> Self { MpdClient { stream: BufStream::new(connection), mpd_version, } } pub fn into_connection(self) -> &'a mut T { self.stream.into_inner() } pub fn get_mpd_version(&self) -> Option<&str> { self.mpd_version.as_deref() } async fn read_initial_mpd_version(&mut self) -> Result<(), MpdClientError> { let mut version_line = String::new(); self.stream .read_line(&mut version_line) .await .map_err(MpdClientError::ConnectionError)?; self.mpd_version = Some(version_line.trim().to_string()); Ok(()) } async fn read_response(&mut self) -> Result, MpdClientError> { let mut response = Vec::new(); loop { let mut line = Vec::new(); let bytes_read = self .stream .read_until(b'\n', &mut line) .await .map_err(MpdClientError::ConnectionError)?; if bytes_read == 0 { break; // EOF reached } response.extend_from_slice(&line); if line == b"OK\n" || line.starts_with(b"ACK ") { break; // End of response } } Ok(response) } pub async fn play( &mut self, position: Option, ) -> Result { let message = Request::Play(position); let payload = message.serialize(); self.stream .write_all(payload.as_bytes()) .await .map_err(MpdClientError::ConnectionError)?; self.stream .flush() .await .map_err(MpdClientError::ConnectionError)?; let response_bytes = self.read_response().await?; let response = PlayResponse::parse_raw(&response_bytes)?; Ok(response) } }