Krolyxon
Commit 3c223ad

rmptuisrcmpdconnection.rs

173 lines · 5.5 KB · Rust

1//! An asynchronous MPD connection over TCP or a Unix socket.
2
3use std::{fmt, path::PathBuf};
4
5use tokio::{
6 io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufStream},
7 net::TcpStream,
8};
9
10use super::{
11 error::{MpdError, MpdResult},
12 protocol::{self, Command, Line, Pair},
13};
14
15/// Where the server listens.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum Address {
18 /// `host:port`, with IPv6 hosts in brackets.
19 Tcp(String),
20 Unix(PathBuf),
21}
22
23impl Address {
24 /// Whether the server runs on this machine.
25 pub fn is_local(&self) -> bool {
26 match self {
27 Self::Unix(_) => true,
28 Self::Tcp(addr) => {
29 let host = addr
30 .rsplit_once(':')
31 .map_or(addr.as_str(), |(host, _)| host)
32 .trim_start_matches('[')
33 .trim_end_matches(']');
34 matches!(host, "localhost" | "127.0.0.1" | "::1") || host.starts_with("127.")
35 }
36 }
37 }
38}
39
40impl fmt::Display for Address {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::Tcp(addr) => f.write_str(addr),
44 Self::Unix(path) => write!(f, "{}", path.display()),
45 }
46 }
47}
48
49trait Socket: AsyncRead + AsyncWrite + Unpin + Send {}
50impl<T: AsyncRead + AsyncWrite + Unpin + Send> Socket for T {}
51
52pub struct Connection {
53 stream: BufStream<Box<dyn Socket>>,
54 version: String,
55 line: Vec<u8>,
56}
57
58impl fmt::Debug for Connection {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 f.debug_struct("Connection")
61 .field("version", &self.version)
62 .finish_non_exhaustive()
63 }
64}
65
66impl Connection {
67 /// Connects, reads the greeting and authenticates if `password` is set.
68 pub async fn connect(address: &Address, password: Option<&str>) -> MpdResult<Self> {
69 let socket: Box<dyn Socket> = match address {
70 Address::Tcp(addr) => {
71 let stream = TcpStream::connect(addr).await?;
72 stream.set_nodelay(true)?;
73 Box::new(stream)
74 }
75 #[cfg(unix)]
76 Address::Unix(path) => Box::new(tokio::net::UnixStream::connect(path).await?),
77 #[cfg(not(unix))]
78 Address::Unix(_) => {
79 return Err(MpdError::Protocol(
80 "Unix sockets are not supported on this platform".into(),
81 ));
82 }
83 };
84 let mut conn = Self {
85 stream: BufStream::new(socket),
86 version: String::new(),
87 line: Vec::new(),
88 };
89 let greeting = conn.read_line().await?;
90 conn.version = protocol::parse_greeting(&greeting)?;
91 if let Some(password) = password.filter(|p| !p.is_empty()) {
92 conn.execute(&Command::new("password").arg(password))
93 .await?;
94 }
95 Ok(conn)
96 }
97
98 /// The protocol version announced by the server.
99 pub fn version(&self) -> &str {
100 &self.version
101 }
102
103 /// Runs one command and collects its response.
104 pub async fn execute(&mut self, command: &Command) -> MpdResult<Vec<Pair>> {
105 self.send(command.as_str()).await?;
106 self.read_response().await
107 }
108
109 /// Runs commands atomically as a command list, returning one response per
110 /// command.
111 pub async fn execute_list(&mut self, commands: &[Command]) -> MpdResult<Vec<Vec<Pair>>> {
112 let mut request = String::from("command_list_ok_begin\n");
113 for command in commands {
114 request.push_str(command.as_str());
115 request.push('\n');
116 }
117 request.push_str("command_list_end");
118 self.send(&request).await?;
119
120 let mut responses = Vec::with_capacity(commands.len());
121 let mut current = Vec::new();
122 loop {
123 let line = self.read_line().await?;
124 match protocol::parse_line(&line)? {
125 Line::Pair(pair) => current.push(pair),
126 Line::ListOk => responses.push(std::mem::take(&mut current)),
127 Line::Ok => return Ok(responses),
128 }
129 }
130 }
131
132 /// Runs commands as a list when there is more than one, discarding output.
133 pub async fn execute_all(&mut self, commands: &[Command]) -> MpdResult<()> {
134 match commands {
135 [] => Ok(()),
136 [command] => self.execute(command).await.map(drop),
137 _ => self.execute_list(commands).await.map(drop),
138 }
139 }
140
141 async fn send(&mut self, request: &str) -> MpdResult<()> {
142 self.stream.write_all(request.as_bytes()).await?;
143 self.stream.write_all(b"\n").await?;
144 self.stream.flush().await?;
145 Ok(())
146 }
147
148 async fn read_response(&mut self) -> MpdResult<Vec<Pair>> {
149 let mut pairs = Vec::new();
150 loop {
151 let line = self.read_line().await?;
152 match protocol::parse_line(&line)? {
153 Line::Pair(pair) => pairs.push(pair),
154 Line::Ok => return Ok(pairs),
155 Line::ListOk => {
156 return Err(MpdError::Protocol("unexpected list_OK".into()));
157 }
158 }
159 }
160 }
161
162 async fn read_line(&mut self) -> MpdResult<String> {
163 self.line.clear();
164 if self.stream.read_until(b'\n', &mut self.line).await? == 0 {
165 return Err(MpdError::Closed);
166 }
167 if self.line.last() == Some(&b'\n') {
168 self.line.pop();
169 }
170 // Tags are UTF-8 by protocol; tolerate broken files instead of failing.
171 Ok(String::from_utf8_lossy(&self.line).into_owned())
172 }
173}