#[cfg(not(tarpaulin_include))]
mod complete;
pub mod implementations;
pub mod printing;
pub mod utils;
#[cfg(test)]
mod smoke_tests;
use std::{path::PathBuf, str::FromStr};
use clap::{Args, Subcommand, ValueEnum, ValueHint};
use clap_complete::{ArgValueCandidates, ArgValueCompleter, PathCompleter};
use complete::CompletableTable;
use mecomp_storage::db::schemas::dynamic::query::Query;
use crate::handlers::utils::StdIn;
pub trait CommandHandler {
type Output;
async fn handle<W1: std::fmt::Write + Send, W2: std::fmt::Write + Send, R: StdIn>(
&self,
client: mecomp_prost::MusicPlayerClient,
stdout: &mut W1,
stderr: &mut W2,
stdin: &R,
) -> Self::Output;
}
#[derive(Debug, Subcommand, PartialEq)]
pub enum Command {
Ping,
#[clap(alias = "exit")]
Stop,
Library {
#[clap(subcommand)]
command: LibraryCommand,
},
Status {
#[clap(subcommand)]
command: StatusCommand,
},
State,
Current {
#[clap(value_enum)]
target: CurrentTarget,
},
Rand {
#[clap(value_enum)]
target: RandTarget,
},
Search {
#[clap(long, short, action = clap::ArgAction::SetTrue)]
quiet: bool,
#[clap(value_enum)]
target: SearchTarget,
#[clap(value_hint = ValueHint::Other)]
query: String,
#[clap(default_value = "10", value_hint = ValueHint::Other)]
limit: u64,
},
Playback {
#[clap(subcommand)]
command: PlaybackCommand,
},
Queue {
#[clap(subcommand)]
command: QueueCommand,
},
Playlist {
#[clap(subcommand)]
command: PlaylistCommand,
},
Dynamic {
#[clap(subcommand)]
command: DynamicCommand,
},
Collection {
#[clap(subcommand)]
command: CollectionCommand,
},
Radio(RadioCommand),
}
#[derive(Debug, Subcommand, PartialEq, Eq)]
pub enum LibraryCommand {
Rescan,
Analyze {
#[clap(long)]
overwrite: bool,
},
Recluster,
Brief,
Full,
Health,
List {
#[clap(long, short, action = clap::ArgAction::SetTrue)]
quiet: bool,
#[clap(value_enum)]
target: LibraryListTarget,
},
Get {
#[clap(subcommand)]
command: LibraryGetCommand,
},
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
pub enum LibraryListTarget {
Artists,
Albums,
Songs,
Playlists,
DynamicPlaylists,
Collections,
}
#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum LibraryGetCommand {
Artist {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Artist)))]
id: String,
},
Album {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Album)))]
id: String,
},
Song {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Song)))]
id: String,
},
Playlist {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
id: String,
},
Dynamic {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Dynamic)))]
id: String,
},
Collection {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Collection)))]
id: String,
},
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
pub enum CurrentTarget {
Artist,
Album,
Song,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
pub enum RandTarget {
Artist,
Album,
Song,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
pub enum SearchTarget {
All,
Artist,
Album,
Song,
}
#[derive(Debug, Subcommand, PartialEq, Eq)]
pub enum StatusCommand {
Rescan,
Analyze,
Recluster,
}
#[derive(Debug, Subcommand, PartialEq)]
pub enum PlaybackCommand {
Toggle,
Play,
Pause,
Stop,
Restart,
Next,
Previous,
Seek {
#[clap(subcommand)]
command: SeekCommand,
},
Volume {
#[clap(subcommand)]
command: VolumeCommand,
},
Repeat {
#[clap(value_enum)]
mode: RepeatMode,
},
Shuffle,
}
#[derive(Debug, Subcommand, PartialEq)]
pub enum SeekCommand {
#[clap(alias = "f", visible_alias = "+", alias = "ahead")]
Forward {
#[clap(default_value = "5.0", value_hint = ValueHint::Other)]
amount: f32,
},
#[clap(alias = "b", visible_alias = "-", alias = "back")]
Backward {
#[clap(default_value = "5.0", value_hint = ValueHint::Other)]
amount: f32,
},
#[clap(alias = "a", visible_alias = "=", alias = "to")]
Absolute {
#[clap(value_hint = ValueHint::Other)]
position: f32,
},
}
fn float_value_parser(s: &str) -> Result<f32, String> {
let volume = s.parse::<f32>().map_err(|_| "Invalid volume".to_string())?;
if !(0.0..=100.0).contains(&volume) {
return Err("Volume must be between 0 and 100".to_string());
}
Ok(volume)
}
#[derive(Debug, Subcommand, PartialEq)]
pub enum VolumeCommand {
#[clap(visible_alias = "=")]
Set {
#[clap(value_hint = ValueHint::Other, value_parser = float_value_parser)]
volume: f32,
},
#[clap(alias = "up", visible_alias = "+")]
Increase {
#[clap(value_hint = ValueHint::Other, value_parser = float_value_parser)]
amount: f32,
},
#[clap(alias = "down", visible_alias = "-")]
Decrease {
#[clap(value_hint = ValueHint::Other, value_parser = float_value_parser)]
amount: f32,
},
Mute,
Unmute,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
pub enum RepeatMode {
None,
One,
All,
}
impl From<RepeatMode> for mecomp_core::state::RepeatMode {
fn from(mode: RepeatMode) -> Self {
match mode {
RepeatMode::None => Self::None,
RepeatMode::One => Self::One,
RepeatMode::All => Self::All,
}
}
}
#[derive(Debug, Subcommand, PartialEq, Eq)]
pub enum QueueCommand {
Clear,
List {
#[clap(long, short, action = clap::ArgAction::SetTrue)]
quiet: bool,
},
Add {
#[clap(value_hint = ValueHint::Other, add = ArgValueCompleter::new(complete::thing_completer))]
items: Vec<String>,
},
Remove {
#[clap(value_hint = ValueHint::Other)]
start: u64,
#[clap(value_hint = ValueHint::Other)]
end: u64,
},
Set {
#[clap(value_hint = ValueHint::Other)]
index: u64,
},
}
#[derive(Debug, Subcommand, PartialEq, Eq)]
pub enum PlaylistCommand {
List,
Get {
#[clap(value_enum)]
method: PlaylistGetMethod,
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
target: String,
},
Create {
#[clap(value_hint = ValueHint::Other)]
name: String,
},
Update {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
id: String,
#[clap(short, long, value_hint = ValueHint::Other)]
name: String,
},
Songs {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
id: String,
},
Delete {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
id: String,
},
Add(PlaylistAddCommand),
Remove {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
id: String,
#[clap(value_hint = ValueHint::Other)]
item_ids: Vec<String>,
},
Export {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
id: String,
#[clap(value_hint = ValueHint::FilePath, add = ArgValueCompleter::new(PathCompleter::file()))]
path: PathBuf,
},
Import {
#[clap(value_hint = ValueHint::FilePath, add = ArgValueCompleter::new(PathCompleter::file()))]
path: PathBuf,
#[clap(value_hint = ValueHint::Other)]
name: Option<String>,
},
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
pub enum PlaylistGetMethod {
Id,
Name,
}
#[derive(Debug, Args, Clone, PartialEq, Eq)]
pub struct PlaylistAddCommand {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Playlist)))]
id: String,
#[clap(value_hint = ValueHint::Other, add = ArgValueCompleter::new(complete::thing_completer))]
items: Vec<String>,
}
#[derive(Debug, Subcommand, PartialEq, Eq)]
pub enum DynamicCommand {
List,
Get {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Dynamic)))]
id: String,
},
Songs {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Dynamic)))]
id: String,
},
Create {
#[clap(value_hint = ValueHint::Other)]
name: String,
#[clap(value_parser = Query::from_str)]
query: Query,
},
Delete {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Dynamic)))]
id: String,
},
Update {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Dynamic)))]
id: String,
#[clap(flatten)]
update: DynamicUpdate,
},
#[clap(alias = "bnf")]
ShowBNF,
Export {
#[clap(value_hint = ValueHint::FilePath, add = ArgValueCompleter::new(PathCompleter::file()))]
path: PathBuf,
},
Import {
#[clap(value_hint = ValueHint::FilePath, add = ArgValueCompleter::new(PathCompleter::file()))]
path: PathBuf,
},
}
#[derive(Debug, clap::Args, PartialEq, Eq)]
#[group(required = true)]
pub struct DynamicUpdate {
#[clap(short, long, value_hint = ValueHint::Other)]
pub name: Option<String>,
#[clap(short, long, value_parser = Query::from_str, value_hint = ValueHint::Other)]
pub query: Option<Query>,
}
#[derive(Debug, Subcommand, PartialEq, Eq)]
pub enum CollectionCommand {
List,
Get {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Collection)))]
id: String,
},
Songs {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Collection)))]
id: String,
},
Recluster,
Freeze {
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Collection)))]
id: String,
#[clap(value_hint = ValueHint::Other, add = ArgValueCandidates::new(complete::thing_candidates(CompletableTable::Collection)))]
name: String,
},
}
#[derive(Debug, clap::Args, Clone, PartialEq, Eq)]
pub struct RadioCommand {
#[clap(value_hint = ValueHint::Other)]
n: u32,
#[clap(value_hint = ValueHint::Other, add = ArgValueCompleter::new(complete::thing_completer))]
items: Vec<String>,
}