diff --git a/src/main.rs b/src/main.rs index 6593587..ce5b866 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use std::process; use std::{ collections::{HashMap, HashSet}, io::{BufRead, BufReader, Cursor, Read, Write}, @@ -5,21 +6,20 @@ use std::{ os::unix::net::UnixStream, path::PathBuf, sync::{ - Arc, Mutex, atomic::{AtomicBool, Ordering}, - mpsc, + mpsc, Arc, Mutex, }, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; use clap::Parser; use discord_presence::{ - Client, Event, models::{ActivityAssets, ActivityTimestamps, ActivityType}, + Client, Event, }; use image::{GenericImageView, ImageFormat}; use mpd::Idle as _; -use mpd::{Song, State, Subsystem, song::Id}; +use mpd::{song::Id, Song, State, Subsystem}; use reqwest::blocking::Client as HttpClient; use serde::{Deserialize, Serialize}; @@ -57,6 +57,10 @@ struct Application { update_tx: mpsc::Sender, update_rx: mpsc::Receiver, running: Arc, + player_update_pending: Arc, + mpd_last_ok: Arc>, + mpd_disconnected: Arc, + mpd_connected: Arc, } const MPD_LOGO: &str = "https://www.musicpd.org/logo.png"; @@ -71,6 +75,7 @@ enum UpdateMessage { cover_url: Option, ttl: Option, }, + ClearActivity, Stop, } @@ -129,6 +134,10 @@ impl Application { update_tx, update_rx, running: Arc::new(AtomicBool::new(true)), + player_update_pending: Arc::new(AtomicBool::new(false)), + mpd_last_ok: Arc::new(Mutex::new(Instant::now())), + mpd_disconnected: Arc::new(AtomicBool::new(false)), + mpd_connected: Arc::new(AtomicBool::new(true)), config, }; @@ -338,8 +347,74 @@ impl Application { } fn run_update(&mut self) { - let status = self.mpdc.lock().unwrap().status().unwrap(); - let song = self.mpdc.lock().unwrap().currentsong().unwrap(); + self.player_update_pending.store(false, Ordering::Relaxed); + let (status, song) = { + let mut mpdc = match self.mpdc.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + let status = match mpdc.status() { + Ok(status) => status, + Err(_) => { + if self.mpd_connected.swap(false, Ordering::Relaxed) { + eprintln!("Lost MPD connection."); + } + eprintln!("Connecting to MPD..."); + let Ok(client) = mpd::Client::connect(self.config.address.clone()) else { + return; + }; + *mpdc = client; + if !self.mpd_connected.swap(true, Ordering::Relaxed) { + eprintln!("Regained MPD connection."); + } + match mpdc.status() { + Ok(status) => status, + Err(_) => return, + } + } + }; + let song = match mpdc.currentsong() { + Ok(song) => song, + Err(_) => { + if self.mpd_connected.swap(false, Ordering::Relaxed) { + eprintln!("Lost MPD connection."); + } + eprintln!("Connecting to MPD..."); + let Ok(client) = mpd::Client::connect(self.config.address.clone()) else { + return; + }; + *mpdc = client; + if !self.mpd_connected.swap(true, Ordering::Relaxed) { + eprintln!("Regained MPD connection."); + } + match mpdc.currentsong() { + Ok(song) => song, + Err(_) => return, + } + } + }; + (status, song) + }; + self.mpd_disconnected.store(false, Ordering::Relaxed); + if !self.mpd_connected.swap(true, Ordering::Relaxed) { + eprintln!("Gained MPD connection."); + } + if let Ok(mut last_ok) = self.mpd_last_ok.lock() { + *last_ok = Instant::now(); + } + + if song.is_none() && status.state == State::Play { + if !self.player_update_pending.swap(true, Ordering::Relaxed) { + let tx = self.update_tx.clone(); + let pending = self.player_update_pending.clone(); + std::thread::spawn(move || { + std::thread::sleep(Duration::from_millis(500)); + pending.store(false, Ordering::Relaxed); + let _ = tx.send(UpdateMessage::Player); + }); + } + return; + } let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); @@ -408,20 +483,103 @@ impl Application { let running = self.running.clone(); let mpdc = self.mpdc.clone(); + let mpd_address = self.config.address.clone(); + let mpd_last_ok = self.mpd_last_ok.clone(); + let mpd_disconnected = self.mpd_disconnected.clone(); + let mpd_connected = self.mpd_connected.clone(); { let tx = self.update_tx.clone(); let running = running.clone(); + let mpd_address = mpd_address.clone(); + let mpd_last_ok = mpd_last_ok.clone(); + let mpd_disconnected = mpd_disconnected.clone(); + let mpd_connected = mpd_connected.clone(); std::thread::spawn(move || { let mut last_state: Option = None; let mut last_song_id: Option = None; while running.load(Ordering::Relaxed) { - let _ = mpdc.lock().unwrap().idle(&[Subsystem::Player]); + let idle_ok = { + let mut guard = match mpdc.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + guard.idle(&[Subsystem::Player]).is_ok() + }; + if !idle_ok { + if mpd_connected.swap(false, Ordering::Relaxed) { + eprintln!("Lost MPD connection."); + } + eprintln!("Connecting to MPD..."); + if let Ok(client) = mpd::Client::connect(mpd_address.clone()) { + if let Ok(mut guard) = mpdc.lock() { + *guard = client; + } + if !mpd_connected.swap(true, Ordering::Relaxed) { + eprintln!("Regained MPD connection."); + } + let _ = tx.send(UpdateMessage::Player); + } + let disconnected_for = mpd_last_ok + .lock() + .ok() + .map(|last_ok| last_ok.elapsed()) + .unwrap_or_default(); + if disconnected_for > Duration::from_secs(5) + && !mpd_disconnected.swap(true, Ordering::Relaxed) + { + let _ = tx.send(UpdateMessage::ClearActivity); + } + std::thread::sleep(Duration::from_millis(200)); + continue; + } if !running.load(Ordering::Relaxed) { break; } - let status = mpdc.lock().unwrap().status().unwrap(); + let status = { + let mut guard = match mpdc.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + guard.status() + }; + let status = match status { + Ok(status) => status, + Err(_) => { + if mpd_connected.swap(false, Ordering::Relaxed) { + eprintln!("Lost MPD connection."); + } + eprintln!("Connecting to MPD..."); + if let Ok(client) = mpd::Client::connect(mpd_address.clone()) { + if let Ok(mut guard) = mpdc.lock() { + *guard = client; + } + if !mpd_connected.swap(true, Ordering::Relaxed) { + eprintln!("Regained MPD connection."); + } + let _ = tx.send(UpdateMessage::Player); + } + let disconnected_for = mpd_last_ok + .lock() + .ok() + .map(|last_ok| last_ok.elapsed()) + .unwrap_or_default(); + if disconnected_for > Duration::from_secs(5) + && !mpd_disconnected.swap(true, Ordering::Relaxed) + { + let _ = tx.send(UpdateMessage::ClearActivity); + } + continue; + } + }; + if !mpd_connected.swap(true, Ordering::Relaxed) { + eprintln!("Gained MPD connection."); + let _ = tx.send(UpdateMessage::Player); + } + if let Ok(mut last_ok) = mpd_last_ok.lock() { + *last_ok = Instant::now(); + } let song_id = status.song.map(|place| place.id); if last_state != Some(status.state) || last_song_id != song_id { @@ -436,7 +594,19 @@ impl Application { { let tx = self.update_tx.clone(); let running = running.clone(); + let last_ctrlc = Arc::new(Mutex::new(None::)); + let last_ctrlc_handler = last_ctrlc.clone(); ctrlc::set_handler(move || { + let mut last = last_ctrlc_handler + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + if let Some(previous) = *last { + if previous.elapsed() <= Duration::from_secs(2) { + eprintln!("Force quitting after second Ctrl-C."); + process::exit(1); + } + } + *last = Some(Instant::now()); running.store(false, Ordering::Relaxed); let _ = tx.send(UpdateMessage::Stop); }) @@ -467,6 +637,9 @@ impl Application { ); self.run_update(); } + UpdateMessage::ClearActivity => { + let _ = self.client.clear_activity(); + } UpdateMessage::Stop => break, } }