From f3f5b9de93e0f32ac39af7973834b4492514853f Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 29 Mar 2026 20:33:14 +0300 Subject: [PATCH] commands: add confirmation prompts for destructive operations Signed-off-by: NotAShelf Change-Id: If84f778b42864acb13ba0a70aa93bbb56a6a6964 --- src/commands.rs | 172 ++++++++++++++++++++++++++++-------------------- 1 file changed, 101 insertions(+), 71 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index eede5ad7..0bc99c45 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -7,24 +7,25 @@ //! //! # async fn example() -> mrc::Result<()> { //! // Play media at a specific playlist index -//! Commands::play(Some(0)).await?; +//! Commands::play(Some(0), None).await; //! //! // Pause playback -//! Commands::pause().await?; +//! Commands::pause(None).await; //! //! // Add files to playlist //! let files = vec!["movie1.mp4".to_string(), "movie2.mp4".to_string()]; -//! Commands::add_files(&files).await?; +//! Commands::add_files(&files, None).await; //! # Ok(()) //! # } //! ``` +use serde_json::json; +use tracing::info; + use crate::{ MrcError, Result, get_property, loadfile, playlist_clear, playlist_move, playlist_next, playlist_prev, playlist_remove, quit, seek, set_property, }; -use serde_json::json; -use tracing::info; /// Centralized command processing for MPV operations. pub struct Commands; @@ -38,114 +39,113 @@ impl Commands { /// # Arguments /// /// * `index` - Optional playlist index to play from - pub async fn play(index: Option) -> Result<()> { + pub async fn play(index: Option, socket_path: Option<&str>) -> Result<()> { if let Some(idx) = index { info!("Playing media at index: {}", idx); - set_property("playlist-pos", &json!(idx), None).await?; + set_property("playlist-pos", &json!(idx), socket_path).await?; } info!("Unpausing playback"); - set_property("pause", &json!(false), None).await?; + set_property("pause", &json!(false), socket_path).await?; Ok(()) } /// Pauses the currently playing media. - pub async fn pause() -> Result<()> { + pub async fn pause(socket_path: Option<&str>) -> Result<()> { info!("Pausing playback"); - set_property("pause", &json!(true), None).await?; + set_property("pause", &json!(true), socket_path).await?; Ok(()) } /// Stops playback and quits MPV. /// /// This is a destructive operation that will terminate the MPV process. - pub async fn stop() -> Result<()> { + pub async fn stop(socket_path: Option<&str>) -> Result<()> { info!("Stopping playback and quitting MPV"); - quit(None).await?; + quit(socket_path).await?; Ok(()) } /// Advances to the next item in the playlist. - pub async fn next() -> Result<()> { + pub async fn next(socket_path: Option<&str>) -> Result<()> { info!("Skipping to next item in the playlist"); - playlist_next(None).await?; + playlist_next(socket_path).await?; Ok(()) } /// Goes back to the previous item in the playlist. - pub async fn prev() -> Result<()> { + pub async fn prev(socket_path: Option<&str>) -> Result<()> { info!("Skipping to previous item in the playlist"); - playlist_prev(None).await?; + playlist_prev(socket_path).await?; Ok(()) } - /// Seeks to a specific time position in the current media. + /// Seeks to a specific position in the currently playing media. /// /// # Arguments /// - /// * `seconds` - The time position to seek to, in seconds - pub async fn seek_to(seconds: f64) -> Result<()> { + /// * `seconds` - The position in seconds to seek to + pub async fn seek_to(seconds: f64, socket_path: Option<&str>) -> Result<()> { info!("Seeking to {} seconds", seconds); - seek(seconds, None).await?; + seek(seconds, socket_path).await?; Ok(()) } - /// Moves a playlist item from one index to another. + /// Moves an item in the playlist from one index to another. /// /// # Arguments /// - /// * `from_index` - The current index of the item to move - /// * `to_index` - The target index to move the item to - pub async fn move_item(from_index: usize, to_index: usize) -> Result<()> { + /// * `from_index` - The current index of the item + /// * `to_index` - The desired new index for the item + pub async fn move_item( + from_index: usize, + to_index: usize, + socket_path: Option<&str>, + ) -> Result<()> { info!("Moving item from index {} to {}", from_index, to_index); - playlist_move(from_index, to_index, None).await?; + playlist_move(from_index, to_index, socket_path).await?; Ok(()) } /// Removes an item from the playlist. /// + /// If no index is provided, removes the currently playing item. + /// /// # Arguments /// - /// * `index` - Optional index of the item to remove. If `None`, removes the current item - pub async fn remove_item(index: Option) -> Result<()> { + /// * `index` - Optional specific index to remove + pub async fn remove_item(index: Option, socket_path: Option<&str>) -> Result<()> { if let Some(idx) = index { info!("Removing item at index {}", idx); - playlist_remove(Some(idx), None).await?; + playlist_remove(Some(idx), socket_path).await?; } else { info!("Removing current item from playlist"); - playlist_remove(None, None).await?; + playlist_remove(None, socket_path).await?; } Ok(()) } - /// Clears all items from the playlist. - pub async fn clear_playlist() -> Result<()> { + /// Clears the entire playlist. + pub async fn clear_playlist(socket_path: Option<&str>) -> Result<()> { info!("Clearing the playlist"); - playlist_clear(None).await?; + playlist_clear(socket_path).await?; Ok(()) } /// Lists all items in the playlist. - /// - /// This outputs the playlist as formatted JSON to stdout. - pub async fn list_playlist() -> Result<()> { + pub async fn list_playlist(socket_path: Option<&str>) -> Result<()> { info!("Listing playlist items"); - if let Some(data) = get_property("playlist", None).await? { - let pretty_json = serde_json::to_string_pretty(&data).map_err(MrcError::ParseError)?; - println!("{}", pretty_json); + if let Some(data) = get_property("playlist", socket_path).await? { + print_playlist(&data); } Ok(()) } - /// Adds multiple files to the playlist. + /// Adds files to the playlist. /// /// # Arguments /// - /// * `filenames` - A slice of file paths to add to the playlist - /// - /// # Errors - /// - /// Returns [`MrcError::InvalidInput`] if the filenames slice is empty. - pub async fn add_files(filenames: &[String]) -> Result<()> { + /// * `filenames` - List of file paths to add + pub async fn add_files(filenames: &[String], socket_path: Option<&str>) -> Result<()> { if filenames.is_empty() { let e = "No files provided to add to the playlist"; return Err(MrcError::InvalidInput(e.to_string())); @@ -153,76 +153,108 @@ impl Commands { info!("Adding {} files to the playlist", filenames.len()); for filename in filenames { - loadfile(filename, true, None).await?; + loadfile(filename, true, socket_path).await?; } Ok(()) } /// Replaces the current playlist with new files. /// - /// The first file replaces the current playlist, and subsequent files are appended. - /// /// # Arguments /// - /// * `filenames` - A slice of file paths to replace the playlist with - pub async fn replace_playlist(filenames: &[String]) -> Result<()> { + /// * `filenames` - List of file paths to replace the playlist with + pub async fn replace_playlist(filenames: &[String], socket_path: Option<&str>) -> Result<()> { info!("Replacing current playlist with {} files", filenames.len()); if let Some(first_file) = filenames.first() { - loadfile(first_file, false, None).await?; + loadfile(first_file, false, socket_path).await?; for filename in &filenames[1..] { - loadfile(filename, true, None).await?; + loadfile(filename, true, socket_path).await?; } } Ok(()) } - /// Retrieves and displays multiple MPV properties. + /// Fetches multiple properties and prints them. /// /// # Arguments /// - /// * `properties` - A slice of property names to retrieve - pub async fn get_properties(properties: &[String]) -> Result<()> { + /// * `properties` - List of property names to fetch + pub async fn get_properties(properties: &[String], socket_path: Option<&str>) -> Result<()> { info!("Fetching properties: {:?}", properties); for property in properties { - if let Some(data) = get_property(property, None).await? { + if let Some(data) = get_property(property, socket_path).await? { println!("{property}: {data}"); } } Ok(()) } - /// Retrieves and displays a single MPV property. + /// Fetches a single property and prints it. /// /// # Arguments /// - /// * `property` - The name of the property to retrieve - pub async fn get_single_property(property: &str) -> Result<()> { - if let Some(data) = get_property(property, None).await? { + /// * `property` - The property name to fetch + pub async fn get_single_property(property: &str, socket_path: Option<&str>) -> Result<()> { + if let Some(data) = get_property(property, socket_path).await? { println!("{property}: {data}"); } Ok(()) } - /// Sets an MPV property to a specific value. + /// Sets a property and prints a confirmation. /// /// # Arguments /// - /// * `property` - The name of the property to set - /// * `value` - The JSON value to set the property to - pub async fn set_single_property(property: &str, value: &serde_json::Value) -> Result<()> { - set_property(property, value, None).await?; + /// * `property` - The property name to set + /// * `value` - The JSON value to set + pub async fn set_single_property( + property: &str, + value: &serde_json::Value, + socket_path: Option<&str>, + ) -> Result<()> { + set_property(property, value, socket_path).await?; println!("Set {property} to {value}"); Ok(()) } } +fn print_playlist(data: &serde_json::Value) { + if let Some(items) = data.as_array() { + if items.is_empty() { + println!("Playlist is empty."); + return; + } + + for (i, item) in items.iter().enumerate() { + let title = item + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + let filename = item + .get("filename") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + let current = item + .get("current") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let marker = if current { ">" } else { " " }; + println!("{} {:3} | {} | {}", marker, i, title, filename); + } + } else { + let pretty_json = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string()); + println!("{}", pretty_json); + } +} + #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_add_files_empty_list() { - let result = Commands::add_files(&[]).await; + let result = Commands::add_files(&[], None).await; assert!(result.is_err()); if let Err(MrcError::InvalidInput(msg)) = result { assert_eq!(msg, "No files provided to add to the playlist"); @@ -233,15 +265,13 @@ mod tests { #[tokio::test] async fn test_replace_playlist_empty() { - let result = Commands::replace_playlist(&[]).await; - // Should succeed (no-op) with empty list + let result = Commands::replace_playlist(&[], None).await; assert!(result.is_ok()); } #[tokio::test] async fn test_get_properties_empty() { - let result = Commands::get_properties(&[]).await; - // Should succeed (no-op) with empty list + let result = Commands::get_properties(&[], None).await; assert!(result.is_ok()); } -} +} \ No newline at end of file