diff options
Diffstat (limited to 'src/daemon.rs')
| -rw-r--r-- | src/daemon.rs | 99 |
1 files changed, 93 insertions, 6 deletions
diff --git a/src/daemon.rs b/src/daemon.rs index 41c21c6..8f470c1 100644 --- a/src/daemon.rs +++ b/src/daemon.rs | |||
| @@ -10,6 +10,7 @@ use crate::config::Config; | |||
| 10 | use crate::notification; | 10 | use crate::notification; |
| 11 | use crate::queue::{Queue, ShuffleMode, Track}; | 11 | use crate::queue::{Queue, ShuffleMode, Track}; |
| 12 | use crate::scrobbler::Scrobbler; | 12 | use crate::scrobbler::Scrobbler; |
| 13 | use crate::state; | ||
| 13 | use reqwest::blocking::Client; | 14 | use reqwest::blocking::Client; |
| 14 | use serde::Deserialize; | 15 | use serde::Deserialize; |
| 15 | use std::error::Error; | 16 | use std::error::Error; |
| @@ -231,7 +232,24 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | |||
| 231 | if let Some(vol) = config.volume { | 232 | if let Some(vol) = config.volume { |
| 232 | audio_engine.set_volume(vol as f32 / 100.0); | 233 | audio_engine.set_volume(vol as f32 / 100.0); |
| 233 | } | 234 | } |
| 234 | if let Some(ref shuffle_str) = config.shuffle | 235 | |
| 236 | // Restore the previous session's queue. It beats the config default for | ||
| 237 | // shuffle because it is the newer choice, and it is what makes a restart | ||
| 238 | // (or a reboot) pick up where playback stopped. | ||
| 239 | let mut restored_queue = false; | ||
| 240 | let mut resume_track: Option<Track> = None; | ||
| 241 | if let Some(saved) = state::load_from(&state::state_path()) | ||
| 242 | && let Ok(mut q) = queue.lock() | ||
| 243 | && !saved.tracks.is_empty() | ||
| 244 | { | ||
| 245 | *q = Queue::from_snapshot(saved.snapshot()); | ||
| 246 | restored_queue = true; | ||
| 247 | if saved.playing { | ||
| 248 | resume_track = q.current().cloned(); | ||
| 249 | } | ||
| 250 | } | ||
| 251 | if !restored_queue | ||
| 252 | && let Some(ref shuffle_str) = config.shuffle | ||
| 235 | && let Ok(mut q) = queue.lock() | 253 | && let Ok(mut q) = queue.lock() |
| 236 | { | 254 | { |
| 237 | match shuffle_str.as_str() { | 255 | match shuffle_str.as_str() { |
| @@ -270,6 +288,16 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | |||
| 270 | } | 288 | } |
| 271 | let scrobble_state = Arc::new(Mutex::new(ScrobbleState::new())); | 289 | let scrobble_state = Arc::new(Mutex::new(ScrobbleState::new())); |
| 272 | 290 | ||
| 291 | // Pick the restored track back up. No notification: this happens on daemon | ||
| 292 | // start (usually at login), nobody asked for a popup there. | ||
| 293 | if let Some(track) = resume_track { | ||
| 294 | audio_engine.play(track.path.clone()); | ||
| 295 | spawn_send_now_playing(&scrobbler, &track); | ||
| 296 | if let Ok(mut s) = scrobble_state.lock() { | ||
| 297 | s.start(track); | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 273 | // Save full config | 301 | // Save full config |
| 274 | config.save()?; | 302 | config.save()?; |
| 275 | 303 | ||
| @@ -308,6 +336,7 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | |||
| 308 | } | 336 | } |
| 309 | false | 337 | false |
| 310 | } | 338 | } |
| 339 | let mut last_state: Option<(usize, usize, ShuffleMode, bool)> = None; | ||
| 311 | loop { | 340 | loop { |
| 312 | // Check for shutdown signal | 341 | // Check for shutdown signal |
| 313 | if shutdown_flag.load(Ordering::SeqCst) { | 342 | if shutdown_flag.load(Ordering::SeqCst) { |
| @@ -356,6 +385,26 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | |||
| 356 | } | 385 | } |
| 357 | } | 386 | } |
| 358 | 387 | ||
| 388 | // Keep the saved state in step with the queue so a restart resumes | ||
| 389 | // here. The file is small and only rewritten when something moved. | ||
| 390 | let pending_save = if let Ok(q) = queue.lock() { | ||
| 391 | let playing = audio_engine.is_playing(); | ||
| 392 | let fingerprint = (q.len(), q.position(), *q.shuffle_mode(), playing); | ||
| 393 | if last_state == Some(fingerprint) { | ||
| 394 | None | ||
| 395 | } else { | ||
| 396 | last_state = Some(fingerprint); | ||
| 397 | Some(state::SavedState::new(q.snapshot(), playing)) | ||
| 398 | } | ||
| 399 | } else { | ||
| 400 | None | ||
| 401 | }; | ||
| 402 | if let Some(saved) = pending_save | ||
| 403 | && let Err(e) = state::save_to(&state::state_path(), &saved) | ||
| 404 | { | ||
| 405 | eprintln!("Failed to save queue state: {}", e); | ||
| 406 | } | ||
| 407 | |||
| 359 | match listener.accept() { | 408 | match listener.accept() { |
| 360 | Ok((stream, _)) => { | 409 | Ok((stream, _)) => { |
| 361 | let audio_clone = Arc::clone(&audio_engine); | 410 | let audio_clone = Arc::clone(&audio_engine); |
| @@ -384,6 +433,14 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | |||
| 384 | thread::sleep(Duration::from_millis(50)); | 433 | thread::sleep(Duration::from_millis(50)); |
| 385 | } | 434 | } |
| 386 | 435 | ||
| 436 | // Final write so a shutdown while paused keeps the current queue. | ||
| 437 | if let Ok(q) = queue.lock() { | ||
| 438 | let saved = state::SavedState::new(q.snapshot(), audio_engine.is_playing()); | ||
| 439 | if let Err(e) = state::save_to(&state::state_path(), &saved) { | ||
| 440 | eprintln!("Failed to save queue state: {}", e); | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 387 | // Cleanup | 444 | // Cleanup |
| 388 | cleanup_daemon(&socket_path, &pid_path); | 445 | cleanup_daemon(&socket_path, &pid_path); |
| 389 | println!("Daemon stopped"); | 446 | println!("Daemon stopped"); |
| @@ -871,6 +928,32 @@ fn stream_synced_lyrics( | |||
| 871 | } | 928 | } |
| 872 | } | 929 | } |
| 873 | 930 | ||
| 931 | /// Unpauses the stream, or starts the queue's current track when nothing is | ||
| 932 | /// loaded in the engine yet — which is the state right after a daemon start | ||
| 933 | /// with a restored queue, where a plain `resume` would otherwise do nothing. | ||
| 934 | /// | ||
| 935 | /// Returns whether there was anything to resume. | ||
| 936 | fn resume_queue_track( | ||
| 937 | audio: &AudioEngine, | ||
| 938 | queue: &Arc<Mutex<Queue>>, | ||
| 939 | scrobbler: &Arc<RwLock<Scrobbler>>, | ||
| 940 | scrobble_state: &Arc<Mutex<ScrobbleState>>, | ||
| 941 | ) -> bool { | ||
| 942 | if audio.current_track().is_some() { | ||
| 943 | audio.resume(); | ||
| 944 | return true; | ||
| 945 | } | ||
| 946 | let Some(track) = queue.lock().ok().and_then(|q| q.current().cloned()) else { | ||
| 947 | return false; | ||
| 948 | }; | ||
| 949 | audio.play(track.path.clone()); | ||
| 950 | spawn_send_now_playing(scrobbler, &track); | ||
| 951 | if let Ok(mut s) = scrobble_state.lock() { | ||
| 952 | s.start(track); | ||
| 953 | } | ||
| 954 | true | ||
| 955 | } | ||
| 956 | |||
| 874 | fn process_command( | 957 | fn process_command( |
| 875 | cmd: &str, | 958 | cmd: &str, |
| 876 | audio: &AudioEngine, | 959 | audio: &AudioEngine, |
| @@ -903,19 +986,23 @@ fn process_command( | |||
| 903 | "Paused\n".to_string() | 986 | "Paused\n".to_string() |
| 904 | } | 987 | } |
| 905 | "resume" => { | 988 | "resume" => { |
| 906 | audio.resume(); | 989 | if resume_queue_track(audio, queue, scrobbler, scrobble_state) { |
| 907 | notification::notify_change("Resumed"); | 990 | notification::notify_change("Resumed"); |
| 908 | "Resumed\n".to_string() | 991 | "Resumed\n".to_string() |
| 992 | } else { | ||
| 993 | "Nothing to resume\n".to_string() | ||
| 994 | } | ||
| 909 | } | 995 | } |
| 910 | "toggle" => { | 996 | "toggle" => { |
| 911 | if audio.is_playing() { | 997 | if audio.is_playing() { |
| 912 | audio.pause(); | 998 | audio.pause(); |
| 913 | notification::notify_change("Paused"); | 999 | notification::notify_change("Paused"); |
| 914 | "Paused\n".to_string() | 1000 | "Paused\n".to_string() |
| 915 | } else { | 1001 | } else if resume_queue_track(audio, queue, scrobbler, scrobble_state) { |
| 916 | audio.resume(); | ||
| 917 | notification::notify_change("Resumed"); | 1002 | notification::notify_change("Resumed"); |
| 918 | "Resumed\n".to_string() | 1003 | "Resumed\n".to_string() |
| 1004 | } else { | ||
| 1005 | "Nothing to resume\n".to_string() | ||
| 919 | } | 1006 | } |
| 920 | } | 1007 | } |
| 921 | "stop" => { | 1008 | "stop" => { |
