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