From 9f74b219d34b362f2de756cc0246d673e064deaf Mon Sep 17 00:00:00 2001 From: h7x4 Date: Mon, 8 Dec 2025 05:32:49 +0900 Subject: [PATCH] client: init --- Cargo.toml | 7 ++++ src/client.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 +-- src/request.rs | 2 +- src/server.rs | 2 +- 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/client.rs diff --git a/Cargo.toml b/Cargo.toml index deebe67..58426fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,17 @@ rust-version = "1.85.0" [dependencies] chrono = { version = "0.4.42", features = ["serde"] } +futures-util = { version = "0.3.31", optional = true, features = ["io"] } lalrpop-util = { version = "0.22.2", features = ["lexer"] } paste = "1.0.15" serde = { version = "1.0.228", features = ["derive"] } thiserror = "2.0.17" +tokio = { version = "1.48.0", optional = true, features = ["io-util"] } + +[features] +default = ["tokio"] +futures = ["dep:futures-util"] +tokio = ["dep:tokio"] [dev-dependencies] indoc = "2.0.7" diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..a032772 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,106 @@ +//! Module containing a high-level client for interacting with an MPD server. +//! It 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}, +}; + +use thiserror::Error; +#[cfg(feature = "tokio")] +use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader}; + +pub struct MpdClient<'a, T> +where + T: AsyncWrite + AsyncRead + Unpin, +{ + connection: &'a mut T, +} + +#[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 fn new(connection: &'a mut T) -> Self { + MpdClient { connection } + } + + async fn read_initial_mpd_version(&mut self) -> Result { + let mut reader = BufReader::new(&mut self.connection); + let mut version_line = String::new(); + + reader + .read_line(&mut version_line) + .await + .map_err(MpdClientError::ConnectionError)?; + + return Ok(version_line.trim().to_string()); + } + + async fn read_response(&mut self) -> Result, MpdClientError> { + let mut response = Vec::new(); + let mut reader = BufReader::new(&mut self.connection); + + loop { + let mut line = Vec::new(); + + let bytes_read = reader + .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.connection + .write_all(payload.as_bytes()) + .await + .map_err(MpdClientError::ConnectionError)?; + + self.connection + .flush() + .await + .map_err(MpdClientError::ConnectionError)?; + + let response_bytes = self.read_response().await?; + + let response = PlayResponse::parse_raw(&response_bytes)?; + + Ok(response) + } +} diff --git a/src/lib.rs b/src/lib.rs index 376442d..7387134 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,16 +5,16 @@ mod client; mod commands; -mod filter; mod request; mod request_tokenizer; mod response; mod response_tokenizer; mod server; +pub mod filter; pub mod types; -pub use filter::{CaseSensitivity, ComparisonOperator, Filter}; +pub use client::MpdClient; pub use request::Request; pub use response::Response; -pub use server::MPDServer; +pub use server::MpdServer; diff --git a/src/request.rs b/src/request.rs index dfbb6b3..f63e58a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -224,7 +224,7 @@ macro_rules! parse_req { } impl Request { - fn serialize(self) -> String { + pub fn serialize(self) -> String { match self { /* querying mpd status */ Request::ClearError => serialize_req!(self, ClearErrorRequest), diff --git a/src/server.rs b/src/server.rs index 159a6f8..a57693c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ use crate::{Request, Response, types::SubSystem}; -pub trait MPDServer { +pub trait MpdServer { type Error; fn route_request(&mut self, request: Request) -> Result {