This commit is contained in:
Connor Johnstone
2026-03-02 23:30:54 -05:00
parent 59d0674d77
commit 34977ea54b
2 changed files with 200 additions and 7 deletions

111
src/mpd.rs Normal file
View File

@@ -0,0 +1,111 @@
use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
pub struct MpdClient {
reader: BufReader<TcpStream>,
stream: TcpStream,
}
impl MpdClient {
pub fn connect() -> Result<Self, String> {
let host = std::env::var("MPD_HOST").unwrap_or_else(|_| "127.0.0.1".into());
let port = std::env::var("MPD_PORT").unwrap_or_else(|_| "6600".into());
let addr = format!("{host}:{port}");
let stream = TcpStream::connect(&addr).map_err(|e| format!("connect to {addr}: {e}"))?;
let reader = BufReader::new(stream.try_clone().map_err(|e| e.to_string())?);
let mut client = MpdClient { reader, stream };
// Consume greeting line ("OK MPD ...")
let mut greeting = String::new();
client
.reader
.read_line(&mut greeting)
.map_err(|e| format!("read greeting: {e}"))?;
if !greeting.starts_with("OK MPD") {
return Err(format!("unexpected greeting: {greeting}"));
}
Ok(client)
}
fn send_command(&mut self, cmd: &str) -> Result<(), String> {
self.stream
.write_all(format!("{cmd}\n").as_bytes())
.map_err(|e| format!("write '{cmd}': {e}"))?;
loop {
let mut line = String::new();
self.reader
.read_line(&mut line)
.map_err(|e| format!("read response to '{cmd}': {e}"))?;
if line == "OK\n" {
return Ok(());
}
if line.starts_with("ACK") {
return Err(line.trim().to_string());
}
}
}
/// Send `update` and wait for the updating_db job to finish.
fn update_and_wait(&mut self) -> Result<(), String> {
self.send_command("update")?;
// Poll `status` until `updating_db` key disappears.
loop {
let status = self.send_command_read("status")?;
if !status.iter().any(|l| l.starts_with("updating_db:")) {
return Ok(());
}
std::thread::sleep(std::time::Duration::from_millis(200));
}
}
/// Send a command and return all response lines (before OK/ACK).
fn send_command_read(&mut self, cmd: &str) -> Result<Vec<String>, String> {
self.stream
.write_all(format!("{cmd}\n").as_bytes())
.map_err(|e| format!("write '{cmd}': {e}"))?;
let mut lines = Vec::new();
loop {
let mut line = String::new();
self.reader
.read_line(&mut line)
.map_err(|e| format!("read response to '{cmd}': {e}"))?;
if line == "OK\n" {
return Ok(lines);
}
if line.starts_with("ACK") {
return Err(line.trim().to_string());
}
lines.push(line.trim().to_string());
}
}
pub fn queue_playlist(&mut self, tracks: &[String], music_dir: &str) {
if let Err(e) = self.update_and_wait() {
eprintln!("MPD update: {e}");
}
if let Err(e) = self.send_command("clear") {
eprintln!("MPD clear: {e}");
return;
}
for track in tracks {
let uri = track
.strip_prefix(music_dir)
.map(|p| p.trim_start_matches('/'))
.unwrap_or(track);
let escaped = uri.replace('\\', "\\\\").replace('"', "\\\"");
if let Err(e) = self.send_command(&format!("add \"{escaped}\"")) {
eprintln!("MPD add {uri}: {e}");
}
}
if let Err(e) = self.send_command("play") {
eprintln!("MPD play: {e}");
}
}
}