From d30a19a7c90977e3083794d3d253d5d480ded82c Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Fri, 13 Dec 2024 04:16:49 +0300 Subject: [PATCH] move generic functions into lib target --- src/lib.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/main.rs | 49 ++++++++++--------- 2 files changed, 154 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 083e1904..990c8762 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use serde_json::{Value, json}; +use serde_json::{json, Value}; use std::io::{self}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::UnixStream; @@ -6,20 +6,26 @@ use tracing::{debug, error}; pub const SOCKET_PATH: &str = "/tmp/mpvsocket"; -/// Sends an IPC command to the MPV socket and returns the parsed response data. -pub async fn send_ipc_command(command: &str, args: &[Value]) -> io::Result> { +/// Sends a generic IPC command to the specified socket and returns the parsed response data. +/// +/// If a socket path is not provided, it will fall back to the example path of `/tmp/mpvsocket` +pub async fn send_ipc_command( + command: &str, + args: &[Value], + socket_path: Option<&str>, +) -> io::Result> { + let socket_path = socket_path.unwrap_or(SOCKET_PATH); debug!( "Sending IPC command: {} with arguments: {:?}", command, args ); - match UnixStream::connect(SOCKET_PATH).await { + match UnixStream::connect(socket_path).await { Ok(mut socket) => { - debug!("Found MPV socket at {}", SOCKET_PATH); + debug!("Connected to socket at {}", socket_path); let mut command_array = vec![json!(command)]; command_array.extend_from_slice(args); - let message = json!({ "command": command_array }); let message_str = format!("{}\n", serde_json::to_string(&message)?); debug!("Serialized message to send with newline: {}", message_str); @@ -52,3 +58,121 @@ pub async fn send_ipc_command(command: &str, args: &[Value]) -> io::Result, +) -> io::Result> { + send_ipc_command( + MpvCommand::SetProperty.as_str(), + &[json!(property), value.clone()], + socket_path, + ) + .await +} + +pub async fn playlist_next(socket_path: Option<&str>) -> io::Result> { + send_ipc_command(MpvCommand::PlaylistNext.as_str(), &[], socket_path).await +} + +pub async fn playlist_prev(socket_path: Option<&str>) -> io::Result> { + send_ipc_command(MpvCommand::PlaylistPrev.as_str(), &[], socket_path).await +} + +pub async fn seek(seconds: f64, socket_path: Option<&str>) -> io::Result> { + send_ipc_command(MpvCommand::Seek.as_str(), &[json!(seconds)], socket_path).await +} + +pub async fn quit(socket_path: Option<&str>) -> io::Result> { + send_ipc_command(MpvCommand::Quit.as_str(), &[], socket_path).await +} + +pub async fn playlist_move( + from_index: usize, + to_index: usize, + socket_path: Option<&str>, +) -> io::Result> { + send_ipc_command( + MpvCommand::PlaylistMove.as_str(), + &[json!(from_index), json!(to_index)], + socket_path, + ) + .await +} + +pub async fn playlist_remove( + index: Option, + socket_path: Option<&str>, +) -> io::Result> { + let args = match index { + Some(idx) => vec![json!(idx)], + None => vec![json!("current")], + }; + send_ipc_command(MpvCommand::PlaylistRemove.as_str(), &args, socket_path).await +} + +pub async fn playlist_clear(socket_path: Option<&str>) -> io::Result> { + send_ipc_command(MpvCommand::PlaylistClear.as_str(), &[], socket_path).await +} + +pub async fn get_property(property: &str, socket_path: Option<&str>) -> io::Result> { + send_ipc_command( + MpvCommand::GetProperty.as_str(), + &[json!(property)], + socket_path, + ) + .await +} + +pub async fn loadfile( + filename: &str, + append: bool, + socket_path: Option<&str>, +) -> io::Result> { + let append_flag = if append { + json!("append-play") + } else { + json!("replace") + }; + send_ipc_command( + MpvCommand::LoadFile.as_str(), + &[json!(filename), append_flag], + socket_path, + ) + .await +} + +impl MpvCommand { + // Convert commands to their string equivalents + pub fn as_str(&self) -> &str { + match self { + MpvCommand::SetProperty => "set_property", + MpvCommand::PlaylistNext => "playlist-next", + MpvCommand::PlaylistPrev => "playlist-prev", + MpvCommand::Seek => "seek", + MpvCommand::Quit => "quit", + MpvCommand::PlaylistMove => "playlist-move", + MpvCommand::PlaylistRemove => "playlist-remove", + MpvCommand::PlaylistClear => "playlist-clear", + MpvCommand::GetProperty => "get_property", + MpvCommand::LoadFile => "loadfile", + } + } +} diff --git a/src/main.rs b/src/main.rs index 3920f5ac..29d42c1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ use clap::{Parser, Subcommand}; -use serde_json::{Value, json}; +use serde_json::json; use std::io::{self}; use std::path::PathBuf; use tracing::{debug, error, info}; -// TODO: SOCKET_PATH should be an argument to send_ipc_command -// and in the CLI, it should be expected as a command line argument -// or/and environment variable. -use mrc::{SOCKET_PATH, send_ipc_command}; +use mrc::set_property; +use mrc::SOCKET_PATH; +use mrc::{ + get_property, loadfile, playlist_clear, playlist_move, playlist_next, playlist_prev, + playlist_remove, quit, seek, +}; #[derive(Parser)] #[command(author, version, about)] @@ -89,10 +91,6 @@ enum CommandOptions { }, } -pub async fn execute_command(command: &str, args: &[Value]) -> io::Result> { - send_ipc_command(command, args).await -} - #[tokio::main] async fn main() -> io::Result<()> { tracing_subscriber::fmt::init(); @@ -108,60 +106,61 @@ async fn main() -> io::Result<()> { CommandOptions::Play { index } => { if let Some(idx) = index { info!("Playing media at index: {}", idx); - execute_command("set_property", &[json!("playlist-pos"), json!(idx)]).await?; + set_property("playlist-pos", &json!(idx), None).await?; } + info!("Unpausing playback"); - execute_command("set_property", &[json!("pause"), json!(false)]).await?; + set_property("pause", &json!(false), None).await?; } CommandOptions::Pause => { info!("Pausing playback"); - execute_command("set_property", &[json!("pause"), json!(true)]).await?; + set_property("pause", &json!(true), None).await?; } CommandOptions::Stop => { info!("Stopping playback and quitting MPV"); - execute_command("quit", &[]).await?; + quit(None).await?; } CommandOptions::Next => { info!("Skipping to next item in the playlist"); - execute_command("playlist-next", &[]).await?; + playlist_next(None).await?; } CommandOptions::Prev => { info!("Skipping to previous item in the playlist"); - execute_command("playlist-prev", &[]).await?; + playlist_prev(None).await?; } CommandOptions::Seek { seconds } => { info!("Seeking to {} seconds", seconds); - execute_command("seek", &[json!(seconds)]).await?; + seek(seconds.into(), None).await?; } CommandOptions::Move { index1, index2 } => { info!("Moving item from index {} to {}", index1, index2); - execute_command("playlist-move", &[json!(index1), json!(index2)]).await?; + playlist_move(index1, index2, None).await?; } CommandOptions::Remove { index } => { if let Some(idx) = index { info!("Removing item at index {}", idx); - execute_command("playlist-remove", &[json!(idx)]).await?; + playlist_remove(Some(idx), None).await?; } else { info!("Removing current item from playlist"); - execute_command("playlist-remove", &[json!("current")]).await?; + playlist_remove(None, None).await?; } } CommandOptions::Clear => { info!("Clearing the playlist"); - execute_command("playlist-clear", &[]).await?; + playlist_clear(None).await?; } CommandOptions::List => { info!("Listing playlist items"); - if let Some(data) = send_ipc_command("get_property", &[json!("playlist")]).await? { + if let Some(data) = get_property("playlist", None).await? { println!("{}", serde_json::to_string_pretty(&data)?); } } @@ -174,16 +173,16 @@ async fn main() -> io::Result<()> { } info!("Adding {} files to the playlist", filenames.len()); for filename in filenames { - execute_command("loadfile", &[json!(filename), json!("append-play")]).await?; + loadfile(&filename, true, None).await?; } } CommandOptions::Replace { filenames } => { info!("Replacing current playlist with {} files", filenames.len()); if let Some(first_file) = filenames.first() { - execute_command("loadfile", &[json!(first_file), json!("replace")]).await?; + loadfile(first_file, false, None).await?; for filename in &filenames[1..] { - execute_command("loadfile", &[json!(filename), json!("append-play")]).await?; + loadfile(filename, true, None).await?; } } } @@ -191,7 +190,7 @@ async fn main() -> io::Result<()> { CommandOptions::Prop { properties } => { info!("Fetching properties: {:?}", properties); for property in properties { - if let Some(data) = send_ipc_command("get_property", &[json!(property)]).await? { + if let Some(data) = get_property(&property, None).await? { println!("{property}: {data}"); } }