music-lounge 0.5.0

Yet another music player
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() {
    // only one player is allowed to run
    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;

    // launch the wave visualizer process
    if CONFIG.show_wave_vis {
        match fork::fork() {
            // the current process
            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)
        }
    }

    // launch the matrix visualizer process
    if CONFIG.show_matrix_vis {
        match fork::fork() {
            // the current process
            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() {
            // the current process
            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)
        }
    }

    // if Ctrl-C is pressed
    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");

    // move to the next song when it ends
    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!(); }
        }
    }
}