This commit is contained in:
figsoda 2020-10-31 11:50:14 -04:00
parent 2fc23792a4
commit a155feb233
3 changed files with 72 additions and 42 deletions

View file

@ -11,17 +11,11 @@ use std::{
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
#[serde(default = "fps_default")]
pub fps: f64,
#[serde(default = "ups_default")] #[serde(default = "ups_default")]
pub ups: f64, pub ups: f64,
pub layout: Widget, pub layout: Widget,
} }
fn fps_default() -> f64 {
30.0
}
fn ups_default() -> f64 { fn ups_default() -> f64 {
4.0 4.0
} }

View file

@ -14,19 +14,22 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
use tokio::{ use tokio::{
sync::Mutex, sync::mpsc,
time::{sleep_until, Duration, Instant}, time::{sleep_until, Duration, Instant},
}; };
use tui::{backend::CrosstermBackend, Terminal}; use tui::{backend::CrosstermBackend, Terminal};
use std::{ use std::{
fmt::Display,
io::{stdout, Write}, io::{stdout, Write},
net::{IpAddr, Ipv4Addr, SocketAddr}, net::{IpAddr, Ipv4Addr, SocketAddr},
process::exit, process::exit,
sync::Arc,
}; };
use crate::config::Config; use crate::{
config::Config,
mpd::{Status, Track},
};
fn cleanup() -> Result<()> { fn cleanup() -> Result<()> {
disable_raw_mode().context("Failed to clean up terminal")?; disable_raw_mode().context("Failed to clean up terminal")?;
@ -35,7 +38,7 @@ fn cleanup() -> Result<()> {
Ok(()) Ok(())
} }
fn die<T>(e: impl std::fmt::Display) -> T { fn die<T>(e: impl Display) -> T {
if let Err(e) = cleanup() { if let Err(e) = cleanup() {
eprintln!("{}", e); eprintln!("{}", e);
}; };
@ -43,48 +46,67 @@ fn die<T>(e: impl std::fmt::Display) -> T {
exit(1); exit(1);
} }
#[derive(Debug)]
enum Command {
Quit,
UpdateFrame,
UpdateQueue(Vec<Track>),
UpdateStatus(Status),
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let res = run().await; let res = run().await;
cleanup()?; cleanup().and_then(|_| res).map_or_else(Err, |_| exit(0))
res
} }
async fn run() -> Result<()> { async fn run() -> Result<()> {
let cfg: Config = ron::from_str(&std::fs::read_to_string("mmtc.ron").unwrap()).unwrap(); let cfg: Config = ron::from_str(&std::fs::read_to_string("mmtc.ron").unwrap()).unwrap();
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6600); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6600);
let mut idle_cl = mpd::init(addr).await.with_context(fail::connect(addr))?; let mut idle_cl = mpd::init(addr).await?;
let mut status_cl = mpd::init(addr).await.with_context(fail::connect(addr))?; let mut status_cl = mpd::init(addr).await?;
let queue = Arc::new(Mutex::new(mpd::queue(&mut idle_cl).await?)); let mut queue = mpd::queue(&mut idle_cl).await?;
let queue1 = Arc::clone(&queue); let mut status = mpd::status(&mut status_cl).await?;
let status = Arc::new(Mutex::new(mpd::status(&mut status_cl).await?));
let status1 = Arc::clone(&status);
let frame_interval = Duration::from_secs_f64(1.0 / cfg.fps);
let update_interval = Duration::from_secs_f64(1.0 / cfg.ups); let update_interval = Duration::from_secs_f64(1.0 / cfg.ups);
let (tx, mut rx) = mpsc::channel(32);
let tx1 = tx.clone();
let tx2 = tx.clone();
let tx3 = tx.clone();
tokio::spawn(async move { tokio::spawn(async move {
let tx = tx1;
loop { loop {
mpd::idle_playlist(&mut idle_cl) mpd::idle_playlist(&mut idle_cl)
.await .await
.context("Failed to idle") .context("Failed to idle")
.unwrap_or_else(die); .unwrap_or_else(die);
*queue1.lock().await = mpd::queue(&mut idle_cl) tx.send(Command::UpdateQueue(
.await mpd::queue(&mut idle_cl)
.context("Failed to query queue information") .await
.unwrap_or_else(die); .context("Failed to query queue information")
.unwrap_or_else(die),
))
.await
.unwrap_or_else(die);
} }
}); });
tokio::spawn(async move { tokio::spawn(async move {
let tx = tx2;
loop { loop {
let deadline = Instant::now() + update_interval; let deadline = Instant::now() + update_interval;
*status1.lock().await = mpd::status(&mut status_cl) tx.send(Command::UpdateStatus(
.await mpd::status(&mut status_cl)
.context("Failed to query status") .await
.unwrap_or_else(die); .context("Failed to query status")
.unwrap_or_else(die),
))
.await
.unwrap_or_else(die);
sleep_until(deadline).await; sleep_until(deadline).await;
} }
}); });
@ -96,28 +118,40 @@ async fn run() -> Result<()> {
let mut term = let mut term =
Terminal::new(CrosstermBackend::new(stdout)).context("Failed to initialize terminal")?; Terminal::new(CrosstermBackend::new(stdout)).context("Failed to initialize terminal")?;
loop { tokio::spawn(async move {
let deadline = Instant::now() + frame_interval; let tx = tx3;
let queue = &*queue.lock().await; while let Ok(ev) = event::read() {
let status = &*status.lock().await; match ev {
term.draw(|frame| {
layout::render(frame, frame.size(), &cfg.layout, queue, status);
})
.context("Failed to draw to terminal")?;
while event::poll(Duration::new(0, 0)).context("Failed to poll events")? {
match event::read().context("Failed to read events")? {
Event::Key(KeyEvent { code, .. }) => match code { Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char('q') | KeyCode::Esc => { KeyCode::Char('q') | KeyCode::Esc => {
return Ok(()); tx.send(Command::Quit).await.unwrap_or_else(die)
} }
_ => (), _ => (),
}, },
Event::Resize(..) => tx.send(Command::UpdateFrame).await.unwrap_or_else(die),
_ => (), _ => (),
} }
} }
});
sleep_until(deadline).await; while let Some(cmd) = rx.recv().await {
match cmd {
Command::Quit => return Ok(()),
Command::UpdateFrame => term
.draw(|frame| {
layout::render(frame, frame.size(), &cfg.layout, &queue, &status);
})
.context("Failed to draw to terminal")?,
Command::UpdateQueue(new_queue) => {
queue = new_queue;
tx.send(Command::UpdateFrame).await?;
}
Command::UpdateStatus(new_status) => {
status = new_status;
tx.send(Command::UpdateFrame).await?;
}
}
} }
Ok(())
} }

View file

@ -11,6 +11,7 @@ use crate::fail;
pub type Client = BufReader<TcpStream>; pub type Client = BufReader<TcpStream>;
#[derive(Debug)]
pub struct Status { pub struct Status {
pub repeat: bool, pub repeat: bool,
pub random: bool, pub random: bool,
@ -19,12 +20,13 @@ pub struct Status {
pub song: Option<Song>, pub song: Option<Song>,
} }
#[derive(Debug)]
pub struct Song { pub struct Song {
pub pos: usize, pub pos: usize,
pub elapsed: u16, pub elapsed: u16,
} }
#[derive(Debug)]
pub struct Track { pub struct Track {
pub file: String, pub file: String,
pub artist: Option<String>, pub artist: Option<String>,