189
src/main.rs
189
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<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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user