client: init

This commit is contained in:
2025-12-08 05:32:49 +09:00
parent 7a85cd7a6e
commit 9aa3150a67
5 changed files with 118 additions and 5 deletions

View File

@@ -12,10 +12,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"

106
src/client.rs Normal file
View File

@@ -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<String, MpdClientError> {
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<Vec<u8>, 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<SongPosition>,
) -> Result<PlayResponse, MpdClientError> {
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)
}
}

View File

@@ -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;

View File

@@ -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),

View File

@@ -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<Response, Self::Error> {