server: use configurable socket path; remove unused import

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icf8542f6d43f2a4c6424714886afb8416a6a6964
This commit is contained in:
raf 2026-03-29 20:38:15 +03:00
commit 637dc564fe
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 46 additions and 38 deletions

View file

@ -42,12 +42,15 @@
pub mod commands; pub mod commands;
pub mod interactive; pub mod interactive;
use serde_json::{Value, json};
use std::io; use std::io;
use serde_json::{Value, json};
use thiserror::Error; use thiserror::Error;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::{
use tokio::net::UnixStream; io::{AsyncReadExt, AsyncWriteExt},
use tracing::{debug, error}; net::UnixStream,
};
use tracing::debug;
pub const SOCKET_PATH: &str = "/tmp/mpvsocket"; pub const SOCKET_PATH: &str = "/tmp/mpvsocket";
const SOCKET_TIMEOUT_SECS: u64 = 5; const SOCKET_TIMEOUT_SECS: u64 = 5;
@ -459,10 +462,12 @@ pub async fn loadfile(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use serde_json::json;
use std::error::Error; use std::error::Error;
use serde_json::json;
use super::*;
#[test] #[test]
fn test_mrc_error_display() { fn test_mrc_error_display() {
let error = MrcError::InvalidInput("test message".to_string()); let error = MrcError::InvalidInput("test message".to_string());

View file

@ -1,15 +1,12 @@
use std::env; use std::{env, io::Read, sync::Arc};
use std::io::Read;
use std::sync::Arc;
use clap::Parser; use clap::Parser;
use mrc::{MrcError, Result as MrcResult, SOCKET_PATH, commands::Commands};
use native_tls::{Identity, TlsAcceptor as NativeTlsAcceptor}; use native_tls::{Identity, TlsAcceptor as NativeTlsAcceptor};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_native_tls::TlsAcceptor; use tokio_native_tls::TlsAcceptor;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use mrc::{MrcError, Result as MrcResult, commands::Commands};
#[derive(Parser)] #[derive(Parser)]
#[command(author, version, about)] #[command(author, version, about)]
struct Config { struct Config {
@ -18,13 +15,22 @@ struct Config {
bind: String, bind: String,
/// Path to MPV IPC socket /// Path to MPV IPC socket
#[arg(short, long, default_value = "/tmp/mpvsocket")] #[arg(short, long)]
socket: String, socket: Option<String>,
}
impl Config {
fn socket_path(&self) -> String {
self.socket
.clone()
.unwrap_or_else(|| SOCKET_PATH.to_string())
}
} }
async fn handle_connection( async fn handle_connection(
stream: tokio::net::TcpStream, stream: tokio::net::TcpStream,
acceptor: Arc<TlsAcceptor>, acceptor: Arc<TlsAcceptor>,
socket_path: String,
) -> MrcResult<()> { ) -> MrcResult<()> {
let mut stream = acceptor let mut stream = acceptor
.accept(stream) .accept(stream)
@ -88,7 +94,7 @@ async fn handle_connection(
info!("Processing command: {}", command); info!("Processing command: {}", command);
let (status_code, response_body) = match process_command(command).await { let (status_code, response_body) = match process_command(command, &socket_path).await {
Ok(response) => ("200 OK", response), Ok(response) => ("200 OK", response),
Err(e) => { Err(e) => {
error!("Error processing command '{}': {}", command, e); error!("Error processing command '{}': {}", command, e);
@ -108,23 +114,23 @@ async fn handle_connection(
Ok(()) Ok(())
} }
async fn process_command(command: &str) -> MrcResult<String> { async fn process_command(command: &str, socket_path: &str) -> MrcResult<String> {
let parts: Vec<&str> = command.split_whitespace().collect(); let parts: Vec<&str> = command.split_whitespace().collect();
match parts.as_slice() { match parts.as_slice() {
["pause"] => { ["pause"] => {
Commands::pause().await?; Commands::pause(Some(socket_path)).await?;
Ok("Paused playback\n".to_string()) Ok("Paused playback\n".to_string())
} }
["play"] => { ["play"] => {
Commands::play(None).await?; Commands::play(None, Some(socket_path)).await?;
Ok("Resumed playback\n".to_string()) Ok("Resumed playback\n".to_string())
} }
["play", index] => { ["play", index] => {
if let Ok(idx) = index.parse::<usize>() { if let Ok(idx) = index.parse::<usize>() {
Commands::play(Some(idx)).await?; Commands::play(Some(idx), Some(socket_path)).await?;
Ok(format!("Playing from index {}\n", idx)) Ok(format!("Playing from index {}\n", idx))
} else { } else {
Err(MrcError::InvalidInput(format!("Invalid index: {}", index))) Err(MrcError::InvalidInput(format!("Invalid index: {}", index)))
@ -132,23 +138,23 @@ async fn process_command(command: &str) -> MrcResult<String> {
} }
["stop"] => { ["stop"] => {
Commands::stop().await?; Commands::stop(Some(socket_path)).await?;
Ok("Stopped playback\n".to_string()) Ok("Stopped playback\n".to_string())
} }
["next"] => { ["next"] => {
Commands::next().await?; Commands::next(Some(socket_path)).await?;
Ok("Skipped to next item\n".to_string()) Ok("Skipped to next item\n".to_string())
} }
["prev"] => { ["prev"] => {
Commands::prev().await?; Commands::prev(Some(socket_path)).await?;
Ok("Skipped to previous item\n".to_string()) Ok("Skipped to previous item\n".to_string())
} }
["seek", seconds] => { ["seek", seconds] => {
if let Ok(sec) = seconds.parse::<f64>() { if let Ok(sec) = seconds.parse::<f64>() {
Commands::seek_to(sec).await?; Commands::seek_to(sec, Some(socket_path)).await?;
Ok(format!("Seeking to {} seconds\n", sec)) Ok(format!("Seeking to {} seconds\n", sec))
} else { } else {
Err(MrcError::InvalidInput(format!( Err(MrcError::InvalidInput(format!(
@ -159,22 +165,18 @@ async fn process_command(command: &str) -> MrcResult<String> {
} }
["clear"] => { ["clear"] => {
Commands::clear_playlist().await?; Commands::clear_playlist(Some(socket_path)).await?;
Ok("Cleared playlist\n".to_string()) Ok("Cleared playlist\n".to_string())
} }
["list"] => { ["list"] => match mrc::get_property("playlist", Some(socket_path)).await? {
// For server response, we need to capture the output differently Some(data) => {
// since Commands::list_playlist() prints to stdout let pretty_json =
match mrc::get_property("playlist", None).await? { serde_json::to_string_pretty(&data).map_err(MrcError::ParseError)?;
Some(data) => { Ok(format!("Playlist: {}\n", pretty_json))
let pretty_json =
serde_json::to_string_pretty(&data).map_err(MrcError::ParseError)?;
Ok(format!("Playlist: {}\n", pretty_json))
}
None => Ok("Playlist is empty\n".to_string()),
} }
} None => Ok("Playlist is empty\n".to_string()),
},
_ => Err(MrcError::InvalidInput(format!( _ => Err(MrcError::InvalidInput(format!(
"Unknown command: {}. Available commands: pause, play [index], stop, next, prev, seek <seconds>, clear, list", "Unknown command: {}. Available commands: pause, play [index], stop, next, prev, seek <seconds>, clear, list",
@ -205,15 +207,16 @@ fn create_tls_acceptor() -> MrcResult<TlsAcceptor> {
async fn main() -> MrcResult<()> { async fn main() -> MrcResult<()> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let config = Config::parse(); let config = Config::parse();
let socket_path = config.socket_path();
if !std::path::Path::new(&config.socket).exists() { if !std::path::Path::new(&socket_path).exists() {
error!( error!(
"Error: MPV socket not found at '{}'. Is MPV running?", "Error: MPV socket not found at '{}'. Is MPV running?",
config.socket socket_path
); );
return Err(MrcError::ConnectionError(std::io::Error::new( return Err(MrcError::ConnectionError(std::io::Error::new(
std::io::ErrorKind::NotFound, std::io::ErrorKind::NotFound,
format!("MPV socket not found at '{}'", config.socket), format!("MPV socket not found at '{}'", socket_path),
))); )));
} }
@ -231,7 +234,7 @@ async fn main() -> MrcResult<()> {
info!("New connection accepted."); info!("New connection accepted.");
let acceptor = Arc::clone(&acceptor); let acceptor = Arc::clone(&acceptor);
tokio::spawn(handle_connection(stream, acceptor)); tokio::spawn(handle_connection(stream, acceptor, socket_path.clone()));
} }
} }