shuffle
This commit is contained in:
111
src/mpd.rs
Normal file
111
src/mpd.rs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user