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)]
pub struct Config {
#[serde(default = "fps_default")]
pub fps: f64,
#[serde(default = "ups_default")]
pub ups: f64,
pub layout: Widget,
}
fn fps_default() -> f64 {
30.0
}
fn ups_default() -> f64 {
4.0
}

View file

@ -14,19 +14,22 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use tokio::{
sync::Mutex,
sync::mpsc,
time::{sleep_until, Duration, Instant},
};
use tui::{backend::CrosstermBackend, Terminal};
use std::{
fmt::Display,
io::{stdout, Write},
net::{IpAddr, Ipv4Addr, SocketAddr},
process::exit,
sync::Arc,
};
use crate::config::Config;
use crate::{
config::Config,
mpd::{Status, Track},
};
fn cleanup() -> Result<()> {
disable_raw_mode().context("Failed to clean up terminal")?;
@ -35,7 +38,7 @@ fn cleanup() -> Result<()> {
Ok(())
}
fn die<T>(e: impl std::fmt::Display) -> T {
fn die<T>(e: impl Display) -> T {
if let Err(e) = cleanup() {
eprintln!("{}", e);
};
@ -43,47 +46,66 @@ fn die<T>(e: impl std::fmt::Display) -> T {
exit(1);
}
#[derive(Debug)]
enum Command {
Quit,
UpdateFrame,
UpdateQueue(Vec<Track>),
UpdateStatus(Status),
}
#[tokio::main]
async fn main() -> Result<()> {
let res = run().await;
cleanup()?;
res
cleanup().and_then(|_| res).map_or_else(Err, |_| exit(0))
}
async fn run() -> Result<()> {
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 mut idle_cl = mpd::init(addr).await.with_context(fail::connect(addr))?;
let mut status_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?;
let queue = Arc::new(Mutex::new(mpd::queue(&mut idle_cl).await?));
let queue1 = Arc::clone(&queue);
let status = Arc::new(Mutex::new(mpd::status(&mut status_cl).await?));
let status1 = Arc::clone(&status);
let mut queue = mpd::queue(&mut idle_cl).await?;
let mut status = mpd::status(&mut status_cl).await?;
let frame_interval = Duration::from_secs_f64(1.0 / cfg.fps);
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 {
let tx = tx1;
loop {
mpd::idle_playlist(&mut idle_cl)
.await
.context("Failed to idle")
.unwrap_or_else(die);
*queue1.lock().await = mpd::queue(&mut idle_cl)
tx.send(Command::UpdateQueue(
mpd::queue(&mut idle_cl)
.await
.context("Failed to query queue information")
.unwrap_or_else(die),
))
.await
.unwrap_or_else(die);
}
});
tokio::spawn(async move {
let tx = tx2;
loop {
let deadline = Instant::now() + update_interval;
*status1.lock().await = mpd::status(&mut status_cl)
tx.send(Command::UpdateStatus(
mpd::status(&mut status_cl)
.await
.context("Failed to query status")
.unwrap_or_else(die),
))
.await
.unwrap_or_else(die);
sleep_until(deadline).await;
}
@ -96,28 +118,40 @@ async fn run() -> Result<()> {
let mut term =
Terminal::new(CrosstermBackend::new(stdout)).context("Failed to initialize terminal")?;
loop {
let deadline = Instant::now() + frame_interval;
let queue = &*queue.lock().await;
let status = &*status.lock().await;
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")? {
tokio::spawn(async move {
let tx = tx3;
while let Ok(ev) = event::read() {
match ev {
Event::Key(KeyEvent { code, .. }) => match code {
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>;
#[derive(Debug)]
pub struct Status {
pub repeat: bool,
pub random: bool,
@ -19,12 +20,13 @@ pub struct Status {
pub song: Option<Song>,
}
#[derive(Debug)]
pub struct Song {
pub pos: usize,
pub elapsed: u16,
}
#[derive(Debug)]
pub struct Track {
pub file: String,
pub artist: Option<String>,