diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-05 21:12:32 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-05 21:12:32 +0200 |
| commit | bd29978b2a0d0d6def486fcdaa63bc8934d68045 (patch) | |
| tree | 5df0efb17d350ae7f095eb9c0940b1bbf568de96 /src/cli.rs | |
| download | rsplayer-bd29978b2a0d0d6def486fcdaa63bc8934d68045.tar.gz rsplayer-bd29978b2a0d0d6def486fcdaa63bc8934d68045.zip | |
fix(daemon): robust auto-advance; ensure next track starts even if playback fails
Diffstat (limited to '')
| -rw-r--r-- | src/cli.rs | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..13ef67c --- /dev/null +++ b/src/cli.rs | |||
| @@ -0,0 +1,186 @@ | |||
| 1 | use crate::config::Config; | ||
| 2 | use crate::database::Database; | ||
| 3 | use clap::{Parser, Subcommand}; | ||
| 4 | use std::path::PathBuf; | ||
| 5 | |||
| 6 | #[derive(Parser)] | ||
| 7 | #[command(name = "rsp")] | ||
| 8 | #[command(about = "Rust music player daemon", long_about = None)] | ||
| 9 | pub struct Cli { | ||
| 10 | #[command(subcommand)] | ||
| 11 | pub command: Commands, | ||
| 12 | } | ||
| 13 | |||
| 14 | #[derive(Subcommand)] | ||
| 15 | pub enum Commands { | ||
| 16 | /// Start the daemon | ||
| 17 | Start, | ||
| 18 | /// Stop the daemon | ||
| 19 | Stop, | ||
| 20 | /// Restart the daemon (kill existing and start new) | ||
| 21 | Restart, | ||
| 22 | /// Play a track, album, or artist | ||
| 23 | Play { | ||
| 24 | #[arg(value_name = "QUERY")] | ||
| 25 | query: Option<String>, | ||
| 26 | }, | ||
| 27 | /// Pause playback | ||
| 28 | Pause, | ||
| 29 | /// Resume playback | ||
| 30 | Resume, | ||
| 31 | /// Stop playback | ||
| 32 | StopPlayback, | ||
| 33 | /// Skip to next track | ||
| 34 | Next, | ||
| 35 | /// Skip to previous track | ||
| 36 | Previous, | ||
| 37 | /// Set volume (0-100) | ||
| 38 | Volume { | ||
| 39 | #[arg(value_name = "LEVEL")] | ||
| 40 | level: Option<u8>, | ||
| 41 | }, | ||
| 42 | /// Shuffle mode | ||
| 43 | Shuffle { | ||
| 44 | #[arg(value_name = "MODE")] | ||
| 45 | mode: Option<String>, | ||
| 46 | }, | ||
| 47 | /// Queue management | ||
| 48 | Queue { | ||
| 49 | #[arg(value_name = "POSITION")] | ||
| 50 | position: Option<String>, | ||
| 51 | #[arg(value_name = "QUERY")] | ||
| 52 | query: Option<String>, | ||
| 53 | }, | ||
| 54 | /// Show player status | ||
| 55 | Status, | ||
| 56 | /// Scan music paths to populate database | ||
| 57 | Scan { | ||
| 58 | #[arg(long)] | ||
| 59 | force: bool, | ||
| 60 | #[arg(long, value_name = "PATHS")] | ||
| 61 | paths: Option<Vec<PathBuf>>, | ||
| 62 | }, | ||
| 63 | /// Settings management | ||
| 64 | Settings { | ||
| 65 | #[command(subcommand)] | ||
| 66 | command: SettingsCommands, | ||
| 67 | }, | ||
| 68 | } | ||
| 69 | |||
| 70 | #[derive(Subcommand)] | ||
| 71 | pub enum SettingsCommands { | ||
| 72 | /// Get a setting value | ||
| 73 | Get { | ||
| 74 | #[arg(value_name = "KEY")] | ||
| 75 | key: String, | ||
| 76 | }, | ||
| 77 | /// Set a setting value | ||
| 78 | Set { | ||
| 79 | #[arg(value_name = "KEY")] | ||
| 80 | key: String, | ||
| 81 | #[arg(value_name = "VALUE")] | ||
| 82 | value: String, | ||
| 83 | }, | ||
| 84 | /// List all settings | ||
| 85 | List, | ||
| 86 | /// Unset a setting | ||
| 87 | Unset { | ||
| 88 | #[arg(value_name = "KEY")] | ||
| 89 | key: String, | ||
| 90 | }, | ||
| 91 | } | ||
| 92 | |||
| 93 | impl Cli { | ||
| 94 | pub fn run(&self) -> Result<(), Box<dyn std::error::Error>> { | ||
| 95 | match &self.command { | ||
| 96 | Commands::Start => { | ||
| 97 | crate::daemon::start_daemon()?; | ||
| 98 | } | ||
| 99 | Commands::Stop => { | ||
| 100 | crate::daemon::stop_daemon()?; | ||
| 101 | } | ||
| 102 | Commands::Restart => { | ||
| 103 | crate::daemon::restart_daemon()?; | ||
| 104 | } | ||
| 105 | Commands::Play { query } => { | ||
| 106 | if let Some(q) = query { | ||
| 107 | crate::daemon::send_command(format!("play {}", q))?; | ||
| 108 | } else { | ||
| 109 | crate::daemon::send_command("play".to_string())?; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | Commands::Pause => { | ||
| 113 | crate::daemon::send_command("pause".to_string())?; | ||
| 114 | } | ||
| 115 | Commands::Resume => { | ||
| 116 | crate::daemon::send_command("resume".to_string())?; | ||
| 117 | } | ||
| 118 | Commands::StopPlayback => { | ||
| 119 | crate::daemon::send_command("stop".to_string())?; | ||
| 120 | } | ||
| 121 | Commands::Next => { | ||
| 122 | crate::daemon::send_command("next".to_string())?; | ||
| 123 | } | ||
| 124 | Commands::Previous => { | ||
| 125 | crate::daemon::send_command("previous".to_string())?; | ||
| 126 | } | ||
| 127 | Commands::Volume { level } => { | ||
| 128 | if let Some(l) = level { | ||
| 129 | crate::daemon::send_command(format!("volume {}", l))?; | ||
| 130 | } else { | ||
| 131 | crate::daemon::send_command("volume".to_string())?; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | Commands::Shuffle { mode } => { | ||
| 135 | if let Some(m) = mode { | ||
| 136 | crate::daemon::send_command(format!("shuffle {}", m))?; | ||
| 137 | } else { | ||
| 138 | crate::daemon::send_command("shuffle".to_string())?; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | Commands::Queue { position, query } => match (position, query) { | ||
| 142 | (None, None) => { | ||
| 143 | crate::daemon::send_command("queue".to_string())?; | ||
| 144 | } | ||
| 145 | (pos, Some(q)) => { | ||
| 146 | let pos = pos.as_deref().unwrap_or("last"); | ||
| 147 | crate::daemon::send_command(format!("queue {} {}", pos, q))?; | ||
| 148 | } | ||
| 149 | _ => {} | ||
| 150 | }, | ||
| 151 | Commands::Status => { | ||
| 152 | crate::daemon::send_command("status".to_string())?; | ||
| 153 | } | ||
| 154 | Commands::Scan { force, paths } => { | ||
| 155 | let config = Config::load()?; | ||
| 156 | let db = Database::open(&config)?; | ||
| 157 | if let Some(custom_paths) = paths { | ||
| 158 | db.scan_paths(custom_paths, *force)?; | ||
| 159 | } else { | ||
| 160 | db.scan_paths(&config.music_paths, *force)?; | ||
| 161 | } | ||
| 162 | println!("Scan complete"); | ||
| 163 | } | ||
| 164 | Commands::Settings { command } => { | ||
| 165 | let mut config = Config::load()?; | ||
| 166 | match command { | ||
| 167 | SettingsCommands::Get { key } => { | ||
| 168 | println!("{}", config.get(key)?); | ||
| 169 | } | ||
| 170 | SettingsCommands::Set { key, value } => { | ||
| 171 | config.set(key, value)?; | ||
| 172 | config.save()?; | ||
| 173 | } | ||
| 174 | SettingsCommands::List => { | ||
| 175 | config.list(); | ||
| 176 | } | ||
| 177 | SettingsCommands::Unset { key } => { | ||
| 178 | config.unset(key)?; | ||
| 179 | config.save()?; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | } | ||
| 184 | Ok(()) | ||
| 185 | } | ||
| 186 | } | ||
