Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2026-01-28 01:21:11 +02:00
parent 7a5a7b0565
commit bc0309b644

View File

@@ -1,3 +1,4 @@
use std::process;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
io::{BufRead, BufReader, Cursor, Read, Write}, io::{BufRead, BufReader, Cursor, Read, Write},
@@ -5,21 +6,20 @@ use std::{
os::unix::net::UnixStream, os::unix::net::UnixStream,
path::PathBuf, path::PathBuf,
sync::{ sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
mpsc, mpsc, Arc, Mutex,
}, },
time::{Duration, Instant, SystemTime, UNIX_EPOCH}, time::{Duration, Instant, SystemTime, UNIX_EPOCH},
}; };
use clap::Parser; use clap::Parser;
use discord_presence::{ use discord_presence::{
Client, Event,
models::{ActivityAssets, ActivityTimestamps, ActivityType}, models::{ActivityAssets, ActivityTimestamps, ActivityType},
Client, Event,
}; };
use image::{GenericImageView, ImageFormat}; use image::{GenericImageView, ImageFormat};
use mpd::Idle as _; 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 reqwest::blocking::Client as HttpClient;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -57,6 +57,10 @@ struct Application {
update_tx: mpsc::Sender<UpdateMessage>, update_tx: mpsc::Sender<UpdateMessage>,
update_rx: mpsc::Receiver<UpdateMessage>, update_rx: mpsc::Receiver<UpdateMessage>,
running: Arc<AtomicBool>, running: Arc<AtomicBool>,
player_update_pending: Arc<AtomicBool>,
mpd_last_ok: Arc<Mutex<Instant>>,
mpd_disconnected: Arc<AtomicBool>,
mpd_connected: Arc<AtomicBool>,
} }
const MPD_LOGO: &str = "https://www.musicpd.org/logo.png"; const MPD_LOGO: &str = "https://www.musicpd.org/logo.png";
@@ -71,6 +75,7 @@ enum UpdateMessage {
cover_url: Option<String>, cover_url: Option<String>,
ttl: Option<Duration>, ttl: Option<Duration>,
}, },
ClearActivity,
Stop, Stop,
} }
@@ -129,6 +134,10 @@ impl Application {
update_tx, update_tx,
update_rx, update_rx,
running: Arc::new(AtomicBool::new(true)), 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, config,
}; };
@@ -338,8 +347,74 @@ impl Application {
} }
fn run_update(&mut self) { fn run_update(&mut self) {
let status = self.mpdc.lock().unwrap().status().unwrap(); self.player_update_pending.store(false, Ordering::Relaxed);
let song = self.mpdc.lock().unwrap().currentsong().unwrap(); 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(); let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
@@ -408,20 +483,103 @@ impl Application {
let running = self.running.clone(); let running = self.running.clone();
let mpdc = self.mpdc.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 tx = self.update_tx.clone();
let running = running.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 || { std::thread::spawn(move || {
let mut last_state: Option<State> = None; let mut last_state: Option<State> = None;
let mut last_song_id: Option<Id> = None; let mut last_song_id: Option<Id> = None;
while running.load(Ordering::Relaxed) { 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) { if !running.load(Ordering::Relaxed) {
break; 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); let song_id = status.song.map(|place| place.id);
if last_state != Some(status.state) || last_song_id != song_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 tx = self.update_tx.clone();
let running = running.clone(); let running = running.clone();
let last_ctrlc = Arc::new(Mutex::new(None::<Instant>));
let last_ctrlc_handler = last_ctrlc.clone();
ctrlc::set_handler(move || { 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); running.store(false, Ordering::Relaxed);
let _ = tx.send(UpdateMessage::Stop); let _ = tx.send(UpdateMessage::Stop);
}) })
@@ -467,6 +637,9 @@ impl Application {
); );
self.run_update(); self.run_update();
} }
UpdateMessage::ClearActivity => {
let _ = self.client.clear_activity();
}
UpdateMessage::Stop => break, UpdateMessage::Stop => break,
} }
} }