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::{
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<UpdateMessage>,
update_rx: mpsc::Receiver<UpdateMessage>,
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";
@@ -71,6 +75,7 @@ enum UpdateMessage {
cover_url: Option<String>,
ttl: Option<Duration>,
},
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<State> = None;
let mut last_song_id: Option<Id> = 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::<Instant>));
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,
}
}