use std::{ error::Error, fs, path::PathBuf, sync::{Arc, Mutex}, time::Duration, }; use clap::Parser; use mpris::{PlaybackStatus, PlayerFinder}; use serde::{Deserialize, Serialize}; use tokio::{ net::{UnixListener, UnixStream}, time, }; use tokio_util::codec::{Framed, LengthDelimitedCodec}; use futures::{prelude::stream::StreamExt, SinkExt}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Request { GetLastActive, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Response { None, Players(Vec), } #[derive(Debug, Parser)] pub struct Cli { #[arg(short = 'c', long, default_value = r"config.json", value_hint = clap::ValueHint::FilePath)] pub config: PathBuf, #[arg(short = 's', long, default_value = r"/tmp/mpris-ctl.sock", value_hint = clap::ValueHint::FilePath)] pub socket: PathBuf, } type SharedData = Arc>>; #[tokio::main] async fn main() { let args = Cli::parse(); if args.socket.exists() { fs::remove_file(&args.socket).unwrap(); } let last_active_players = Arc::new(Mutex::new(Vec::::new())); let last_active_mpris = last_active_players.clone(); let _mpris_handle = tokio::spawn(async move { handle_mpris_daemon_loop(last_active_mpris).await; }); let listener = UnixListener::bind(&args.socket).expect("failed to bind socket"); loop { let last_active_listener = last_active_players.clone(); match listener.accept().await { Ok((stream, _addr)) => { tokio::spawn(async move { process(stream, last_active_listener).await; }); } Err(e) => { eprintln!("{:?}", e); } } } } async fn handle_mpris_daemon_loop(shared_data: SharedData) { let mut interval = time::interval(Duration::from_millis(250)); loop { interval.tick().await; match handle_mpris_daemon(&shared_data).await { Ok(..) => (), Err(err) => eprintln!("Got an error in the daemon player finder loop: {}", err), } } } async fn handle_mpris_daemon(shared_data: &SharedData) -> Result<(), Box> { let player_finder = PlayerFinder::new()?; let mut errors = vec![]; let active_players: Vec<_> = player_finder .iter_players()? .filter_map(|x| { let player_result = x.map_err(|e| errors.push(e)).ok(); let status = if let Some(player) = &player_result { player .get_playback_status() .map_err(|e| errors.push(e)) .ok() } else { Option::None }; if status.is_some() && status.unwrap() == PlaybackStatus::Playing { player_result } else { Option::None } }) .map(|x| String::from(x.identity())) .collect(); if active_players.len() != 0 { let mut guard = shared_data.lock()?; *guard = active_players; } for error in errors { // TODO: return the errors correctly (this is not so easy as the DBusError does not contain copy trait) // maybe create a custom error type somehow wrapping dbus error? eprintln!("An errorw hen obtaining a player has occurred: {}", error); } Ok(()) } async fn process(stream: UnixStream, shared_data: SharedData) { let mut framed = Framed::new(stream, LengthDelimitedCodec::new()); while let Some(frame) = framed.next().await { match frame { Ok(data) => { let request: serde_json::Result = serde_json::from_slice(&data); if request.is_err() { eprintln!("Could not parse the frame from client: {:?}", request.err()); break; } let request = request.unwrap(); let response = handle_request(request, shared_data.clone()).await; let buffer = serde_json::to_vec(&response).unwrap(); let send = framed.send(buffer.into()).await; if send.is_err() { eprintln!("Could not send to the client: {:?}", send.err()); break; } } Err(err) => { eprintln!("Could not read from client: {:?}", err); break; } } } } async fn handle_request(request: Request, shared_data: SharedData) -> Response { match request { Request::GetLastActive => { let guard = shared_data.lock().expect("Could not lock the mutex."); Response::Players(guard.clone()) } _ => Response::None, } }