mirror of
https://github.com/NotAShelf/mpvrc.git
synced 2026-04-17 08:19:51 +00:00
treewide: unify command processing and improve error handling
This commit is contained in:
parent
74f2927b86
commit
69f69ece07
5 changed files with 331 additions and 163 deletions
78
src/cli.rs
78
src/cli.rs
|
|
@ -1,13 +1,10 @@
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use mrc::SOCKET_PATH;
|
use mrc::SOCKET_PATH;
|
||||||
|
use mrc::commands::Commands;
|
||||||
use mrc::interactive::InteractiveMode;
|
use mrc::interactive::InteractiveMode;
|
||||||
use mrc::{
|
use mrc::{MrcError, Result};
|
||||||
MrcError, Result, get_property, loadfile, playlist_clear, playlist_move, playlist_next,
|
|
||||||
playlist_prev, playlist_remove, quit, seek, set_property,
|
|
||||||
};
|
|
||||||
use serde_json::json;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
|
|
@ -109,98 +106,55 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
CommandOptions::Play { index } => {
|
CommandOptions::Play { index } => {
|
||||||
if let Some(idx) = index {
|
Commands::play(index).await?;
|
||||||
info!("Playing media at index: {}", idx);
|
|
||||||
set_property("playlist-pos", &json!(idx), None).await?;
|
|
||||||
}
|
|
||||||
info!("Unpausing playback");
|
|
||||||
set_property("pause", &json!(false), None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Pause => {
|
CommandOptions::Pause => {
|
||||||
info!("Pausing playback");
|
Commands::pause().await?;
|
||||||
set_property("pause", &json!(true), None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Stop => {
|
CommandOptions::Stop => {
|
||||||
info!("Stopping playback and quitting MPV");
|
Commands::stop().await?;
|
||||||
quit(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Next => {
|
CommandOptions::Next => {
|
||||||
info!("Skipping to next item in the playlist");
|
Commands::next().await?;
|
||||||
playlist_next(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Prev => {
|
CommandOptions::Prev => {
|
||||||
info!("Skipping to previous item in the playlist");
|
Commands::prev().await?;
|
||||||
playlist_prev(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Seek { seconds } => {
|
CommandOptions::Seek { seconds } => {
|
||||||
info!("Seeking to {} seconds", seconds);
|
Commands::seek_to(seconds.into()).await?;
|
||||||
seek(seconds.into(), None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Move { index1, index2 } => {
|
CommandOptions::Move { index1, index2 } => {
|
||||||
info!("Moving item from index {} to {}", index1, index2);
|
Commands::move_item(index1, index2).await?;
|
||||||
playlist_move(index1, index2, None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Remove { index } => {
|
CommandOptions::Remove { index } => {
|
||||||
if let Some(idx) = index {
|
Commands::remove_item(index).await?;
|
||||||
info!("Removing item at index {}", idx);
|
|
||||||
playlist_remove(Some(idx), None).await?;
|
|
||||||
} else {
|
|
||||||
info!("Removing current item from playlist");
|
|
||||||
playlist_remove(None, None).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Clear => {
|
CommandOptions::Clear => {
|
||||||
info!("Clearing the playlist");
|
Commands::clear_playlist().await?;
|
||||||
playlist_clear(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::List => {
|
CommandOptions::List => {
|
||||||
info!("Listing playlist items");
|
Commands::list_playlist().await?;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Add { filenames } => {
|
CommandOptions::Add { filenames } => {
|
||||||
if filenames.is_empty() {
|
Commands::add_files(&filenames).await?;
|
||||||
let e = "No files provided to add to the playlist";
|
|
||||||
error!("{}", e);
|
|
||||||
return Err(MrcError::InvalidInput(e.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Adding {} files to the playlist", filenames.len());
|
|
||||||
for filename in filenames {
|
|
||||||
loadfile(&filename, true, None).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Replace { filenames } => {
|
CommandOptions::Replace { filenames } => {
|
||||||
info!("Replacing current playlist with {} files", filenames.len());
|
Commands::replace_playlist(&filenames).await?;
|
||||||
if let Some(first_file) = filenames.first() {
|
|
||||||
loadfile(first_file, false, None).await?;
|
|
||||||
for filename in &filenames[1..] {
|
|
||||||
loadfile(filename, true, None).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Prop { properties } => {
|
CommandOptions::Prop { properties } => {
|
||||||
info!("Fetching properties: {:?}", properties);
|
Commands::get_properties(&properties).await?;
|
||||||
for property in properties {
|
|
||||||
if let Some(data) = get_property(&property, None).await? {
|
|
||||||
println!("{property}: {data}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Interactive => {
|
CommandOptions::Interactive => {
|
||||||
|
|
|
||||||
217
src/commands.rs
Normal file
217
src/commands.rs
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
//! Command processing module for MRC.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use mrc::commands::Commands;
|
||||||
|
//!
|
||||||
|
//! # async fn example() -> mrc::Result<()> {
|
||||||
|
//! // Play media at a specific playlist index
|
||||||
|
//! Commands::play(Some(0)).await?;
|
||||||
|
//!
|
||||||
|
//! // Pause playback
|
||||||
|
//! Commands::pause().await?;
|
||||||
|
//!
|
||||||
|
//! // Add files to playlist
|
||||||
|
//! let files = vec!["movie1.mp4".to_string(), "movie2.mp4".to_string()];
|
||||||
|
//! Commands::add_files(&files).await?;
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
impl Commands {
|
||||||
|
/// Plays media, optionally at a specific playlist index.
|
||||||
|
///
|
||||||
|
/// If an index is provided, seeks to that position in the playlist first.
|
||||||
|
/// Always unpauses playback regardless of whether an index is specified.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `index` - Optional playlist index to play from
|
||||||
|
pub async fn play(index: Option<usize>) -> Result<()> {
|
||||||
|
if let Some(idx) = index {
|
||||||
|
info!("Playing media at index: {}", idx);
|
||||||
|
set_property("playlist-pos", &json!(idx), None).await?;
|
||||||
|
}
|
||||||
|
info!("Unpausing playback");
|
||||||
|
set_property("pause", &json!(false), None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pauses the currently playing media.
|
||||||
|
pub async fn pause() -> Result<()> {
|
||||||
|
info!("Pausing playback");
|
||||||
|
set_property("pause", &json!(true), None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops playback and quits MPV.
|
||||||
|
///
|
||||||
|
/// This is a destructive operation that will terminate the MPV process.
|
||||||
|
pub async fn stop() -> Result<()> {
|
||||||
|
info!("Stopping playback and quitting MPV");
|
||||||
|
quit(None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advances to the next item in the playlist.
|
||||||
|
pub async fn next() -> Result<()> {
|
||||||
|
info!("Skipping to next item in the playlist");
|
||||||
|
playlist_next(None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Goes back to the previous item in the playlist.
|
||||||
|
pub async fn prev() -> Result<()> {
|
||||||
|
info!("Skipping to previous item in the playlist");
|
||||||
|
playlist_prev(None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seeks to a specific time position in the current media.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `seconds` - The time position to seek to, in seconds
|
||||||
|
pub async fn seek_to(seconds: f64) -> Result<()> {
|
||||||
|
info!("Seeking to {} seconds", seconds);
|
||||||
|
seek(seconds, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves a playlist item 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<()> {
|
||||||
|
info!("Moving item from index {} to {}", from_index, to_index);
|
||||||
|
playlist_move(from_index, to_index, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an item from the playlist.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `index` - Optional index of the item to remove. If `None`, removes the current item
|
||||||
|
pub async fn remove_item(index: Option<usize>) -> Result<()> {
|
||||||
|
if let Some(idx) = index {
|
||||||
|
info!("Removing item at index {}", idx);
|
||||||
|
playlist_remove(Some(idx), None).await?;
|
||||||
|
} else {
|
||||||
|
info!("Removing current item from playlist");
|
||||||
|
playlist_remove(None, None).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all items from the playlist.
|
||||||
|
pub async fn clear_playlist() -> Result<()> {
|
||||||
|
info!("Clearing the playlist");
|
||||||
|
playlist_clear(None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists all items in the playlist.
|
||||||
|
///
|
||||||
|
/// This outputs the playlist as formatted JSON to stdout.
|
||||||
|
pub async fn list_playlist() -> 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);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds multiple 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<()> {
|
||||||
|
if filenames.is_empty() {
|
||||||
|
let e = "No files provided to add to the playlist";
|
||||||
|
return Err(MrcError::InvalidInput(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Adding {} files to the playlist", filenames.len());
|
||||||
|
for filename in filenames {
|
||||||
|
loadfile(filename, true, None).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<()> {
|
||||||
|
info!("Replacing current playlist with {} files", filenames.len());
|
||||||
|
if let Some(first_file) = filenames.first() {
|
||||||
|
loadfile(first_file, false, None).await?;
|
||||||
|
for filename in &filenames[1..] {
|
||||||
|
loadfile(filename, true, None).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves and displays multiple MPV properties.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `properties` - A slice of property names to retrieve
|
||||||
|
pub async fn get_properties(properties: &[String]) -> Result<()> {
|
||||||
|
info!("Fetching properties: {:?}", properties);
|
||||||
|
for property in properties {
|
||||||
|
if let Some(data) = get_property(property, None).await? {
|
||||||
|
println!("{property}: {data}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves and displays a single MPV property.
|
||||||
|
///
|
||||||
|
/// # 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? {
|
||||||
|
println!("{property}: {data}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets an MPV property to a specific value.
|
||||||
|
///
|
||||||
|
/// # 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?;
|
||||||
|
println!("Set {property} to {value}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::{
|
use crate::commands::Commands;
|
||||||
MrcError, Result, get_property, loadfile, playlist_clear, playlist_next, playlist_prev, quit,
|
use crate::{MrcError, Result};
|
||||||
seek, set_property,
|
|
||||||
};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
pub struct InteractiveMode;
|
pub struct InteractiveMode;
|
||||||
|
|
||||||
|
|
@ -76,85 +73,66 @@ impl InteractiveMode {
|
||||||
|
|
||||||
match parts.as_slice() {
|
match parts.as_slice() {
|
||||||
["play"] => {
|
["play"] => {
|
||||||
info!("Unpausing playback");
|
Commands::play(None).await?;
|
||||||
set_property("pause", &json!(false), None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["play", index] => {
|
["play", index] => {
|
||||||
if let Ok(idx) = index.parse::<usize>() {
|
if let Ok(idx) = index.parse::<usize>() {
|
||||||
info!("Playing media at index: {}", idx);
|
Commands::play(Some(idx)).await?;
|
||||||
set_property("playlist-pos", &json!(idx), None).await?;
|
|
||||||
set_property("pause", &json!(false), None).await?;
|
|
||||||
} else {
|
} else {
|
||||||
println!("Invalid index: {}", index);
|
println!("Invalid index: {}", index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
["pause"] => {
|
["pause"] => {
|
||||||
info!("Pausing playback");
|
Commands::pause().await?;
|
||||||
set_property("pause", &json!(true), None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["stop"] => {
|
["stop"] => {
|
||||||
info!("Stopping playback and quitting MPV");
|
Commands::stop().await?;
|
||||||
quit(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["next"] => {
|
["next"] => {
|
||||||
info!("Skipping to next item in the playlist");
|
Commands::next().await?;
|
||||||
playlist_next(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["prev"] => {
|
["prev"] => {
|
||||||
info!("Skipping to previous item in the playlist");
|
Commands::prev().await?;
|
||||||
playlist_prev(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["seek", seconds] => {
|
["seek", seconds] => {
|
||||||
if let Ok(sec) = seconds.parse::<i32>() {
|
if let Ok(sec) = seconds.parse::<i32>() {
|
||||||
info!("Seeking to {} seconds", sec);
|
Commands::seek_to(sec.into()).await?;
|
||||||
seek(sec.into(), None).await?;
|
|
||||||
} else {
|
} else {
|
||||||
println!("Invalid seconds: {}", seconds);
|
println!("Invalid seconds: {}", seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
["clear"] => {
|
["clear"] => {
|
||||||
info!("Clearing the playlist");
|
Commands::clear_playlist().await?;
|
||||||
playlist_clear(None).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["list"] => {
|
["list"] => {
|
||||||
info!("Listing playlist items");
|
Commands::list_playlist().await?;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["add", files @ ..] => {
|
["add", files @ ..] => {
|
||||||
if files.is_empty() {
|
let file_strings: Vec<String> = files.iter().map(|s| s.to_string()).collect();
|
||||||
|
if file_strings.is_empty() {
|
||||||
println!("No files provided to add to the playlist");
|
println!("No files provided to add to the playlist");
|
||||||
} else {
|
} else {
|
||||||
info!("Adding {} files to the playlist", files.len());
|
Commands::add_files(&file_strings).await?;
|
||||||
for file in files {
|
|
||||||
loadfile(file, true, None).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
["get", property] => {
|
["get", property] => {
|
||||||
if let Some(data) = get_property(property, None).await? {
|
Commands::get_single_property(property).await?;
|
||||||
println!("{}: {}", property, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["set", property, value] => {
|
["set", property, value] => {
|
||||||
let json_value = serde_json::from_str::<serde_json::Value>(value)
|
let json_value = serde_json::from_str::<serde_json::Value>(value)
|
||||||
.unwrap_or_else(|_| json!(value));
|
.unwrap_or_else(|_| json!(value));
|
||||||
set_property(property, &json_value, None).await?;
|
Commands::set_single_property(property, &json_value).await?;
|
||||||
println!("Set {} to {}", property, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
//! Default path for the MPV IPC socket: `/tmp/mpvsocket`
|
//! Default path for the MPV IPC socket: `/tmp/mpvsocket`
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
pub mod commands;
|
||||||
pub mod interactive;
|
pub mod interactive;
|
||||||
|
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
|
||||||
144
src/server.rs
144
src/server.rs
|
|
@ -4,15 +4,11 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use native_tls::{Identity, TlsAcceptor as NativeTlsAcceptor};
|
use native_tls::{Identity, TlsAcceptor as NativeTlsAcceptor};
|
||||||
use serde_json::json;
|
|
||||||
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};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use mrc::{
|
use mrc::{MrcError, Result as MrcResult, commands::Commands};
|
||||||
MrcError, Result as MrcResult, get_property, playlist_clear, playlist_next, playlist_prev,
|
|
||||||
quit, seek, set_property,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
|
|
@ -56,111 +52,133 @@ async fn handle_connection(
|
||||||
let auth_token = match env::var("AUTH_TOKEN") {
|
let auth_token = match env::var("AUTH_TOKEN") {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error!("Authentication token is not set. Connection cannot be accepted.");
|
warn!("AUTH_TOKEN environment variable not set. Authentication disabled.");
|
||||||
stream.write_all(b"Authentication token not set\n").await?;
|
let response = "HTTP/1.1 401 Unauthorized\r\nContent-Length: 29\r\n\r\nAuthentication token not set\n";
|
||||||
|
stream.write_all(response.as_bytes()).await?;
|
||||||
// You know what? I do not care to panic when the authentication token is
|
|
||||||
// missing in the environment. Start the goddamned server and hell, even
|
|
||||||
// accept incoming connections. Authenticated requests will be refused
|
|
||||||
// when the token is incorrect or not set, so we can simply continue here.
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if token != auth_token {
|
if token.is_empty() || token != auth_token {
|
||||||
stream.write_all(b"Authentication failed\n").await?;
|
warn!(
|
||||||
|
"Authentication failed for token: {}",
|
||||||
|
if token.is_empty() {
|
||||||
|
"<empty>"
|
||||||
|
} else {
|
||||||
|
"<redacted>"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let response =
|
||||||
|
"HTTP/1.1 401 Unauthorized\r\nContent-Length: 21\r\n\r\nAuthentication failed\n";
|
||||||
|
stream.write_all(response.as_bytes()).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Client authenticated");
|
info!("Client authenticated successfully");
|
||||||
stream.write_all(b"Authenticated\n").await?;
|
|
||||||
|
|
||||||
let command = request.split("\r\n\r\n").last().unwrap_or("");
|
let command = request.split("\r\n\r\n").last().unwrap_or("").trim();
|
||||||
info!("Received command: {}", command);
|
|
||||||
|
|
||||||
let response = match process_command(command.trim()).await {
|
if command.is_empty() {
|
||||||
Ok(response) => response,
|
warn!("Received empty command");
|
||||||
|
let response =
|
||||||
|
"HTTP/1.1 400 Bad Request\r\nContent-Length: 20\r\n\r\nNo command provided\n";
|
||||||
|
stream.write_all(response.as_bytes()).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Processing command: {}", command);
|
||||||
|
|
||||||
|
let (status_code, response_body) = match process_command(command).await {
|
||||||
|
Ok(response) => ("200 OK", response),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error processing command: {}", e);
|
error!("Error processing command '{}': {}", command, e);
|
||||||
format!("Error: {:?}", e)
|
("400 Bad Request", format!("Error: {}\n", e))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let http_response = format!(
|
let http_response = format!(
|
||||||
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
|
"HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: text/plain\r\n\r\n{}",
|
||||||
response.len(),
|
status_code,
|
||||||
response
|
response_body.len(),
|
||||||
|
response_body
|
||||||
);
|
);
|
||||||
|
|
||||||
stream.write_all(http_response.as_bytes()).await?;
|
stream.write_all(http_response.as_bytes()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_command(command: &str) -> MrcResult<String> {
|
async fn process_command(command: &str) -> MrcResult<String> {
|
||||||
match command {
|
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||||
"pause" => {
|
|
||||||
info!("Pausing playback");
|
match parts.as_slice() {
|
||||||
set_property("pause", &json!(true), None).await?;
|
["pause"] => {
|
||||||
|
Commands::pause().await?;
|
||||||
Ok("Paused playback\n".to_string())
|
Ok("Paused playback\n".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
"play" => {
|
["play"] => {
|
||||||
info!("Unpausing playback");
|
Commands::play(None).await?;
|
||||||
set_property("pause", &json!(false), None).await?;
|
|
||||||
Ok("Resumed playback\n".to_string())
|
Ok("Resumed playback\n".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
"stop" => {
|
["play", index] => {
|
||||||
info!("Stopping playback and quitting MPV");
|
if let Ok(idx) = index.parse::<usize>() {
|
||||||
quit(None).await?;
|
Commands::play(Some(idx)).await?;
|
||||||
|
Ok(format!("Playing from index {}\n", idx))
|
||||||
|
} else {
|
||||||
|
Err(MrcError::InvalidInput(format!("Invalid index: {}", index)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
["stop"] => {
|
||||||
|
Commands::stop().await?;
|
||||||
Ok("Stopped playback\n".to_string())
|
Ok("Stopped playback\n".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
"next" => {
|
["next"] => {
|
||||||
info!("Skipping to next item in the playlist");
|
Commands::next().await?;
|
||||||
playlist_next(None).await?;
|
|
||||||
Ok("Skipped to next item\n".to_string())
|
Ok("Skipped to next item\n".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
"prev" => {
|
["prev"] => {
|
||||||
info!("Skipping to previous item in the playlist");
|
Commands::prev().await?;
|
||||||
playlist_prev(None).await?;
|
|
||||||
Ok("Skipped to previous item\n".to_string())
|
Ok("Skipped to previous item\n".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
"seek" => {
|
["seek", seconds] => {
|
||||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
if let Ok(sec) = seconds.parse::<f64>() {
|
||||||
if let Some(seconds) = parts.get(1) {
|
Commands::seek_to(sec).await?;
|
||||||
if let Ok(sec) = seconds.parse::<i32>() {
|
Ok(format!("Seeking to {} seconds\n", sec))
|
||||||
info!("Seeking to {} seconds", sec);
|
} else {
|
||||||
seek(sec.into(), None).await?;
|
Err(MrcError::InvalidInput(format!(
|
||||||
return Ok(format!("Seeking to {} seconds\n", sec));
|
"Invalid seconds: {}",
|
||||||
}
|
seconds
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
Err(MrcError::InvalidInput("Invalid seek command".to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"clear" => {
|
["clear"] => {
|
||||||
info!("Clearing the playlist");
|
Commands::clear_playlist().await?;
|
||||||
playlist_clear(None).await?;
|
|
||||||
Ok("Cleared playlist\n".to_string())
|
Ok("Cleared playlist\n".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
"list" => {
|
["list"] => {
|
||||||
info!("Listing playlist items");
|
// For server response, we need to capture the output differently
|
||||||
match get_property("playlist", None).await {
|
// since Commands::list_playlist() prints to stdout
|
||||||
Ok(Some(data)) => {
|
match mrc::get_property("playlist", None).await? {
|
||||||
|
Some(data) => {
|
||||||
let pretty_json =
|
let pretty_json =
|
||||||
serde_json::to_string_pretty(&data).map_err(MrcError::ParseError)?;
|
serde_json::to_string_pretty(&data).map_err(MrcError::ParseError)?;
|
||||||
Ok(format!("Playlist: {}", pretty_json))
|
Ok(format!("Playlist: {}\n", pretty_json))
|
||||||
}
|
}
|
||||||
Ok(None) => Err(MrcError::PropertyNotFound("playlist".to_string())),
|
None => Ok("Playlist is empty\n".to_string()),
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => Err(MrcError::InvalidInput(format!(
|
_ => Err(MrcError::InvalidInput(format!(
|
||||||
"Unknown command: {}",
|
"Unknown command: {}. Available commands: pause, play [index], stop, next, prev, seek <seconds>, clear, list",
|
||||||
command
|
command.trim()
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue