use super::*;
use crate::{CONFIG, visualizer};
use std::{
fs,
sync::RwLock,
os::unix::net::UnixListener
};
#[cfg(feature = "thread-dump")]
use std::process::Command;
use rust_utils::utils;
mod bg_player;
mod mpris;
use bg_player::BackgroundPlayer;
use mpris::MprisController;
lazy_static! {
static ref LOG: Log = Log::get("mlounged", "music-lounge");
static ref PLAYER: RwLock<BackgroundPlayer> = RwLock::new(BackgroundPlayer::new());
}
#[allow(clippy::manual_flatten)]
pub fn spawn() {
if audio_cmd::<usize>(PlayerCommand::ProcessID, true).is_ok() {
LOG.line(LogLevel::Error, "Music Lounge is already running!", true);
process::exit(101);
}
fs::create_dir_all(&*TMP_DIR).unwrap_or_default();
let socket_file = format!("{}/socket", *TMP_DIR);
fs::remove_file(&socket_file).unwrap_or(());
LOG.report_panics(true);
LOG.line_basic("Starting up Music Lounge background process...", true);
let listener = UnixListener::bind(&socket_file).expect("What went wrong?!");
let mut wave_id = 0;
let mut matrix_id = 0;
let mut info_id = 0;
if CONFIG.show_wave_vis {
match fork::fork() {
Ok(fork::Fork::Parent(child)) => {
wave_id = child;
LOG.line_basic(format!("Wave Visualizer process ID: {child}"), true)
}
Ok(fork::Fork::Child) => visualizer::run("wave"),
_ => LOG.line(LogLevel::Error, "Unable to start visualizer process!", true)
}
}
if CONFIG.show_matrix_vis {
match fork::fork() {
Ok(fork::Fork::Parent(child)) => {
matrix_id = child;
LOG.line_basic(format!("Matrix Visualizer process ID: {child}"), true)
}
Ok(fork::Fork::Child) => visualizer::run("matrix"),
_ => LOG.line(LogLevel::Error, "Unable to start visualizer process!", true)
}
}
if CONFIG.show_info {
match fork::fork() {
Ok(fork::Fork::Parent(child)) => {
info_id = child;
LOG.line_basic(format!("Info process ID: {child}"), true)
}
Ok(fork::Fork::Child) => visualizer::run("info"),
_ => LOG.line(LogLevel::Error, "Unable to start info process!", true)
}
}
ctrlc::set_handler(move || {
LOG.line_basic("Shutting down!", true);
if wave_id != 0 {
utils::run_command("kill", false, ["-9", wave_id.to_string().as_str()]);
}
if matrix_id != 0 {
utils::run_command("kill", false, ["-9", matrix_id.to_string().as_str()]);
}
if info_id != 0 {
utils::run_command("kill", false, ["-9", info_id.to_string().as_str()]);
}
#[cfg(feature = "thread-dump")]
thread_dump();
process::exit(0);
}).expect("Error setting Ctrl-C handler");
thread::Builder::new().name("player-ctl".to_string()).spawn(|| {
loop {
if let Ok(mut player) = PLAYER.try_write() {
player.auto_advance();
}
}
}).expect("Why didn't the thread spawn?!");
if CONFIG.use_mpris {
thread::Builder::new().name("mpris-ctl".to_string()).spawn(|| {
let mut mpris = MprisController::new();
mpris.run();
}).expect("Why didn't the thread spawn?!");
}
LOG.line_basic("Startup complete!", true);
for request in listener.incoming() {
if let Ok(stream) = request {
let mut out_stream = stream.try_clone().expect("Why can't I clone this value?!");
let buffer = BufReader::new(&stream);
let encoded: Vec<u8> = buffer.bytes().map(|r| r.unwrap_or(0)).collect();
let command: PlayerCommand = bincode::deserialize(&encoded).expect("Error parsing request!");
if command.is_mut() {
let mut player = PLAYER.write().expect("What went wrong?!");
match command {
PlayerCommand::Load(playlist) => player.load_list(&playlist),
PlayerCommand::CycleRepeat => player.cycle_repeat(),
PlayerCommand::Play => player.play(),
PlayerCommand::Restart => player.restart(),
PlayerCommand::Next => player.next(),
PlayerCommand::Prev => player.prev(),
PlayerCommand::Resume => player.resume(),
PlayerCommand::Pause => player.pause(),
PlayerCommand::Stop => player.stop(),
PlayerCommand::Seek(time) => player.seek(time),
PlayerCommand::Shuffle => {
player.shuffle_queue();
player.find_pos();
}
PlayerCommand::SetPos(song) => {
player.set_pos(&song);
player.find_pos();
}
PlayerCommand::SetQueue(playlist) => {
player.queue = playlist;
player.find_pos();
}
_ => panic!("Invalid player action!")
}
}
else {
let player = PLAYER.read().expect("What went wrong?!");
match command {
PlayerCommand::ProcessID => {
let id = process::id() as usize;
send_val(&mut out_stream, &id);
}
PlayerCommand::CurrentTime => {
let time = player.cur_time_secs();
send_val(&mut out_stream, &time);
}
PlayerCommand::Status => {
let status = PlayerStatus {
stopped: player.is_stopped(),
paused: player.is_paused(),
position: player.position,
repeat_mode: player.repeat,
state: player.state,
song_id: player.song.song_id()
};
send_val(&mut out_stream, &status);
}
PlayerCommand::GetQueue => {
send_val(&mut out_stream, &player.queue);
}
_ => panic!("Invalid player action!")
}
}
}
}
}
fn send_val<V: serde::Serialize + for <'de> serde::Deserialize<'de> + ?Sized>(stream: &mut UnixStream, val: &V) {
let encoded = bincode::serialize(val).expect("What went wrong?!");
if let Err(why) = stream.write_all(&encoded) {
LOG.line(LogLevel::Error, format!("Unable to write to socket: {why}"), false);
};
stream.shutdown(Shutdown::Write).expect("What went wrong?!");
}
#[cfg(feature = "thread-dump")]
fn thread_dump() {
let exe = env::current_exe().unwrap();
let trace = rstack_self::trace(Command::new(exe).arg("--dump")).unwrap();
for thread in trace.threads() {
println!("\nThread: {}", thread.name());
for (i, frame) in thread.frames().iter().enumerate() {
print!("{i}: ");
let num_syms = frame.symbols().len();
for (i, sym) in frame.symbols().iter().enumerate() {
let file = if let Some(path) = sym.file() {
path.display().to_string()
} else {
String::new()
};
if num_syms > 1 && i >= 1 {
println!("{}: {}", roman::to(i as i32).unwrap_or_default().to_lowercase(), sym.name().unwrap_or_default());
} else {
println!("{}", sym.name().unwrap_or_default());
}
println!(" at {file}:{}", sym.line().unwrap_or_default());
}
if num_syms == 0 { println!(); }
}
}
}