mirror of
https://github.com/NotAShelf/mpvrc.git
synced 2026-04-17 16:29:50 +00:00
commands: add confirmation prompts for destructive operations
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: If84f778b42864acb13ba0a70aa93bbb56a6a6964
This commit is contained in:
parent
1768c14a33
commit
f3f5b9de93
1 changed files with 101 additions and 71 deletions
170
src/commands.rs
170
src/commands.rs
|
|
@ -7,24 +7,25 @@
|
||||||
//!
|
//!
|
||||||
//! # async fn example() -> mrc::Result<()> {
|
//! # async fn example() -> mrc::Result<()> {
|
||||||
//! // Play media at a specific playlist index
|
//! // Play media at a specific playlist index
|
||||||
//! Commands::play(Some(0)).await?;
|
//! Commands::play(Some(0), None).await;
|
||||||
//!
|
//!
|
||||||
//! // Pause playback
|
//! // Pause playback
|
||||||
//! Commands::pause().await?;
|
//! Commands::pause(None).await;
|
||||||
//!
|
//!
|
||||||
//! // Add files to playlist
|
//! // Add files to playlist
|
||||||
//! let files = vec!["movie1.mp4".to_string(), "movie2.mp4".to_string()];
|
//! let files = vec!["movie1.mp4".to_string(), "movie2.mp4".to_string()];
|
||||||
//! Commands::add_files(&files).await?;
|
//! Commands::add_files(&files, None).await;
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use serde_json::json;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
MrcError, Result, get_property, loadfile, playlist_clear, playlist_move, playlist_next,
|
MrcError, Result, get_property, loadfile, playlist_clear, playlist_move, playlist_next,
|
||||||
playlist_prev, playlist_remove, quit, seek, set_property,
|
playlist_prev, playlist_remove, quit, seek, set_property,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
/// Centralized command processing for MPV operations.
|
/// Centralized command processing for MPV operations.
|
||||||
pub struct Commands;
|
pub struct Commands;
|
||||||
|
|
@ -38,114 +39,113 @@ impl Commands {
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `index` - Optional playlist index to play from
|
/// * `index` - Optional playlist index to play from
|
||||||
pub async fn play(index: Option<usize>) -> Result<()> {
|
pub async fn play(index: Option<usize>, socket_path: Option<&str>) -> Result<()> {
|
||||||
if let Some(idx) = index {
|
if let Some(idx) = index {
|
||||||
info!("Playing media at index: {}", idx);
|
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");
|
info!("Unpausing playback");
|
||||||
set_property("pause", &json!(false), None).await?;
|
set_property("pause", &json!(false), socket_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pauses the currently playing media.
|
/// Pauses the currently playing media.
|
||||||
pub async fn pause() -> Result<()> {
|
pub async fn pause(socket_path: Option<&str>) -> Result<()> {
|
||||||
info!("Pausing playback");
|
info!("Pausing playback");
|
||||||
set_property("pause", &json!(true), None).await?;
|
set_property("pause", &json!(true), socket_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops playback and quits MPV.
|
/// Stops playback and quits MPV.
|
||||||
///
|
///
|
||||||
/// This is a destructive operation that will terminate the MPV process.
|
/// 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");
|
info!("Stopping playback and quitting MPV");
|
||||||
quit(None).await?;
|
quit(socket_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advances to the next item in the playlist.
|
/// 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");
|
info!("Skipping to next item in the playlist");
|
||||||
playlist_next(None).await?;
|
playlist_next(socket_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Goes back to the previous item in the playlist.
|
/// 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");
|
info!("Skipping to previous item in the playlist");
|
||||||
playlist_prev(None).await?;
|
playlist_prev(socket_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seeks to a specific time position in the current media.
|
/// Seeks to a specific position in the currently playing media.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `seconds` - The time position to seek to, in seconds
|
/// * `seconds` - The position in seconds to seek to
|
||||||
pub async fn seek_to(seconds: f64) -> Result<()> {
|
pub async fn seek_to(seconds: f64, socket_path: Option<&str>) -> Result<()> {
|
||||||
info!("Seeking to {} seconds", seconds);
|
info!("Seeking to {} seconds", seconds);
|
||||||
seek(seconds, None).await?;
|
seek(seconds, socket_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves a playlist item from one index to another.
|
/// Moves an item in the playlist from one index to another.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `from_index` - The current index of the item to move
|
/// * `from_index` - The current index of the item
|
||||||
/// * `to_index` - The target index to move the item to
|
/// * `to_index` - The desired new index for the item
|
||||||
pub async fn move_item(from_index: usize, to_index: usize) -> Result<()> {
|
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);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes an item from the playlist.
|
/// Removes an item from the playlist.
|
||||||
///
|
///
|
||||||
|
/// If no index is provided, removes the currently playing item.
|
||||||
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `index` - Optional index of the item to remove. If `None`, removes the current item
|
/// * `index` - Optional specific index to remove
|
||||||
pub async fn remove_item(index: Option<usize>) -> Result<()> {
|
pub async fn remove_item(index: Option<usize>, socket_path: Option<&str>) -> Result<()> {
|
||||||
if let Some(idx) = index {
|
if let Some(idx) = index {
|
||||||
info!("Removing item at index {}", idx);
|
info!("Removing item at index {}", idx);
|
||||||
playlist_remove(Some(idx), None).await?;
|
playlist_remove(Some(idx), socket_path).await?;
|
||||||
} else {
|
} else {
|
||||||
info!("Removing current item from playlist");
|
info!("Removing current item from playlist");
|
||||||
playlist_remove(None, None).await?;
|
playlist_remove(None, socket_path).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears all items from the playlist.
|
/// Clears the entire playlist.
|
||||||
pub async fn clear_playlist() -> Result<()> {
|
pub async fn clear_playlist(socket_path: Option<&str>) -> Result<()> {
|
||||||
info!("Clearing the playlist");
|
info!("Clearing the playlist");
|
||||||
playlist_clear(None).await?;
|
playlist_clear(socket_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lists all items in the playlist.
|
/// Lists all items in the playlist.
|
||||||
///
|
pub async fn list_playlist(socket_path: Option<&str>) -> Result<()> {
|
||||||
/// This outputs the playlist as formatted JSON to stdout.
|
|
||||||
pub async fn list_playlist() -> Result<()> {
|
|
||||||
info!("Listing playlist items");
|
info!("Listing playlist items");
|
||||||
if let Some(data) = get_property("playlist", None).await? {
|
if let Some(data) = get_property("playlist", socket_path).await? {
|
||||||
let pretty_json = serde_json::to_string_pretty(&data).map_err(MrcError::ParseError)?;
|
print_playlist(&data);
|
||||||
println!("{}", pretty_json);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds multiple files to the playlist.
|
/// Adds files to the playlist.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `filenames` - A slice of file paths to add to the playlist
|
/// * `filenames` - List of file paths to add
|
||||||
///
|
pub async fn add_files(filenames: &[String], socket_path: Option<&str>) -> Result<()> {
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns [`MrcError::InvalidInput`] if the filenames slice is empty.
|
|
||||||
pub async fn add_files(filenames: &[String]) -> Result<()> {
|
|
||||||
if filenames.is_empty() {
|
if filenames.is_empty() {
|
||||||
let e = "No files provided to add to the playlist";
|
let e = "No files provided to add to the playlist";
|
||||||
return Err(MrcError::InvalidInput(e.to_string()));
|
return Err(MrcError::InvalidInput(e.to_string()));
|
||||||
|
|
@ -153,76 +153,108 @@ impl Commands {
|
||||||
|
|
||||||
info!("Adding {} files to the playlist", filenames.len());
|
info!("Adding {} files to the playlist", filenames.len());
|
||||||
for filename in filenames {
|
for filename in filenames {
|
||||||
loadfile(filename, true, None).await?;
|
loadfile(filename, true, socket_path).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces the current playlist with new files.
|
/// Replaces the current playlist with new files.
|
||||||
///
|
///
|
||||||
/// The first file replaces the current playlist, and subsequent files are appended.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `filenames` - A slice of file paths to replace the playlist with
|
/// * `filenames` - List of file paths to replace the playlist with
|
||||||
pub async fn replace_playlist(filenames: &[String]) -> Result<()> {
|
pub async fn replace_playlist(filenames: &[String], socket_path: Option<&str>) -> Result<()> {
|
||||||
info!("Replacing current playlist with {} files", filenames.len());
|
info!("Replacing current playlist with {} files", filenames.len());
|
||||||
if let Some(first_file) = filenames.first() {
|
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..] {
|
for filename in &filenames[1..] {
|
||||||
loadfile(filename, true, None).await?;
|
loadfile(filename, true, socket_path).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and displays multiple MPV properties.
|
/// Fetches multiple properties and prints them.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `properties` - A slice of property names to retrieve
|
/// * `properties` - List of property names to fetch
|
||||||
pub async fn get_properties(properties: &[String]) -> Result<()> {
|
pub async fn get_properties(properties: &[String], socket_path: Option<&str>) -> Result<()> {
|
||||||
info!("Fetching properties: {:?}", properties);
|
info!("Fetching properties: {:?}", properties);
|
||||||
for property in 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}");
|
println!("{property}: {data}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and displays a single MPV property.
|
/// Fetches a single property and prints it.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `property` - The name of the property to retrieve
|
/// * `property` - The property name to fetch
|
||||||
pub async fn get_single_property(property: &str) -> Result<()> {
|
pub async fn get_single_property(property: &str, socket_path: Option<&str>) -> Result<()> {
|
||||||
if let Some(data) = get_property(property, None).await? {
|
if let Some(data) = get_property(property, socket_path).await? {
|
||||||
println!("{property}: {data}");
|
println!("{property}: {data}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets an MPV property to a specific value.
|
/// Sets a property and prints a confirmation.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `property` - The name of the property to set
|
/// * `property` - The property name to set
|
||||||
/// * `value` - The JSON value to set the property to
|
/// * `value` - The JSON value to set
|
||||||
pub async fn set_single_property(property: &str, value: &serde_json::Value) -> Result<()> {
|
pub async fn set_single_property(
|
||||||
set_property(property, value, None).await?;
|
property: &str,
|
||||||
|
value: &serde_json::Value,
|
||||||
|
socket_path: Option<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
|
set_property(property, value, socket_path).await?;
|
||||||
println!("Set {property} to {value}");
|
println!("Set {property} to {value}");
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_add_files_empty_list() {
|
async fn test_add_files_empty_list() {
|
||||||
let result = Commands::add_files(&[]).await;
|
let result = Commands::add_files(&[], None).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(MrcError::InvalidInput(msg)) = result {
|
if let Err(MrcError::InvalidInput(msg)) = result {
|
||||||
assert_eq!(msg, "No files provided to add to the playlist");
|
assert_eq!(msg, "No files provided to add to the playlist");
|
||||||
|
|
@ -233,15 +265,13 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_replace_playlist_empty() {
|
async fn test_replace_playlist_empty() {
|
||||||
let result = Commands::replace_playlist(&[]).await;
|
let result = Commands::replace_playlist(&[], None).await;
|
||||||
// Should succeed (no-op) with empty list
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_properties_empty() {
|
async fn test_get_properties_empty() {
|
||||||
let result = Commands::get_properties(&[]).await;
|
let result = Commands::get_properties(&[], None).await;
|
||||||
// Should succeed (no-op) with empty list
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue