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