Handle cbd-tui results
This commit is contained in:
parent
0c66165598
commit
1433ae9e7f
|
|
@ -384,6 +384,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs::File, sync::atomic::Ordering};
|
use std::{fs::File, sync::atomic::Ordering};
|
||||||
use symphonia::core::probe::Hint;
|
use symphonia::core::probe::Hint;
|
||||||
use tracing::{debug, warn};
|
use tracing::{warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::decoder::{MediaInfo, SymphoniaDecoder};
|
use crate::decoder::{MediaInfo, SymphoniaDecoder};
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,4 @@ tokio-stream = "0.1"
|
||||||
tonic = "0.9"
|
tonic = "0.9"
|
||||||
notify-rust = "4.8.0"
|
notify-rust = "4.8.0"
|
||||||
serde = "1.0.164"
|
serde = "1.0.164"
|
||||||
|
tracing = "0.1.37"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use ratatui::{
|
||||||
},
|
},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crabidy_core::proto::crabidy::LibraryNode;
|
use crabidy_core::proto::crabidy::LibraryNode;
|
||||||
|
|
||||||
|
|
@ -57,15 +58,21 @@ impl Library {
|
||||||
}
|
}
|
||||||
pub fn ascend(&mut self) {
|
pub fn ascend(&mut self) {
|
||||||
if let Some(parent) = self.parent.as_ref() {
|
if let Some(parent) = self.parent.as_ref() {
|
||||||
self.tx.send(MessageFromUi::GetLibraryNode(parent.clone()));
|
if let Err(err) = self.tx.send(MessageFromUi::GetLibraryNode(parent.clone())) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn dive(&mut self) {
|
pub fn dive(&mut self) {
|
||||||
if let Some(idx) = self.list_state.selected() {
|
if let Some(idx) = self.list_state.selected() {
|
||||||
let item = &self.list[idx];
|
let item = &self.list[idx];
|
||||||
if let UiItemKind::Node = item.kind {
|
if let UiItemKind::Node = item.kind {
|
||||||
self.tx
|
if let Err(err) = self
|
||||||
.send(MessageFromUi::GetLibraryNode(item.uuid.clone()));
|
.tx
|
||||||
|
.send(MessageFromUi::GetLibraryNode(item.uuid.clone()))
|
||||||
|
{
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +80,7 @@ impl Library {
|
||||||
if let Some(items) = self.get_selected() {
|
if let Some(items) = self.get_selected() {
|
||||||
match self.tx.send(MessageFromUi::AppendTracks(items)) {
|
match self.tx.send(MessageFromUi::AppendTracks(items)) {
|
||||||
Ok(_) => self.remove_marks(),
|
Ok(_) => self.remove_marks(),
|
||||||
Err(_) => { /* FIXME: warn */ }
|
Err(err) => error!("Send error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +88,7 @@ impl Library {
|
||||||
if let Some(items) = self.get_selected() {
|
if let Some(items) = self.get_selected() {
|
||||||
match self.tx.send(MessageFromUi::QueueTracks(items)) {
|
match self.tx.send(MessageFromUi::QueueTracks(items)) {
|
||||||
Ok(_) => self.remove_marks(),
|
Ok(_) => self.remove_marks(),
|
||||||
Err(_) => { /* FIXME: warn */ }
|
Err(err) => error!("Send error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +96,7 @@ impl Library {
|
||||||
if let Some(items) = self.get_selected() {
|
if let Some(items) = self.get_selected() {
|
||||||
match self.tx.send(MessageFromUi::ReplaceQueue(items)) {
|
match self.tx.send(MessageFromUi::ReplaceQueue(items)) {
|
||||||
Ok(_) => self.remove_marks(),
|
Ok(_) => self.remove_marks(),
|
||||||
Err(_) => { /* FIXME: warn */ }
|
Err(err) => error!("Send error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +104,7 @@ impl Library {
|
||||||
if let Some(items) = self.get_selected() {
|
if let Some(items) = self.get_selected() {
|
||||||
match self.tx.send(MessageFromUi::InsertTracks(items, pos)) {
|
match self.tx.send(MessageFromUi::InsertTracks(items, pos)) {
|
||||||
Ok(_) => self.remove_marks(),
|
Ok(_) => self.remove_marks(),
|
||||||
Err(_) => { /* FIXME: warn */ }
|
Err(err) => error!("Send error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ impl App {
|
||||||
let main = Layout::default()
|
let main = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
.split(f.size());
|
.split(full_screen);
|
||||||
|
|
||||||
self.library.render(f, main[0], library_focused);
|
self.library.render(f, main[0], library_focused);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,14 @@ impl NowPlaying {
|
||||||
}
|
}
|
||||||
pub fn update_track(&mut self, active: Option<Track>) {
|
pub fn update_track(&mut self, active: Option<Track>) {
|
||||||
if let Some(track) = &active {
|
if let Some(track) = &active {
|
||||||
|
let body = if let Some(ref album) = track.album {
|
||||||
|
format!("{} by {} ({})", track.title, track.artist, album.title)
|
||||||
|
} else {
|
||||||
|
format!("{} by {}", track.title, track.artist)
|
||||||
|
};
|
||||||
Notification::new()
|
Notification::new()
|
||||||
.summary("Crabidy playing")
|
.summary("Playing")
|
||||||
// FIXME: album
|
.body(&body)
|
||||||
.body(&format!("{} by {}", track.title, track.artist))
|
|
||||||
.show()
|
.show()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +122,7 @@ impl NowPlaying {
|
||||||
|
|
||||||
f.render_widget(media_info_p, now_playing_layout[0]);
|
f.render_widget(media_info_p, now_playing_layout[0]);
|
||||||
|
|
||||||
if let (Some(position), Some(duration), Some(track)) =
|
if let (Some(position), Some(duration), Some(_track)) =
|
||||||
(self.position, self.duration, &self.track)
|
(self.position, self.duration, &self.track)
|
||||||
{
|
{
|
||||||
let pos = position.as_secs();
|
let pos = position.as_secs();
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use ratatui::{
|
||||||
},
|
},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crabidy_core::proto::crabidy::Queue as QueueData;
|
use crabidy_core::proto::crabidy::Queue as QueueData;
|
||||||
|
|
||||||
|
|
@ -33,20 +34,28 @@ impl Queue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn play_next(&self) {
|
pub fn play_next(&self) {
|
||||||
self.tx.send(MessageFromUi::NextTrack);
|
if let Err(err) = self.tx.send(MessageFromUi::NextTrack) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn play_prev(&self) {
|
pub fn play_prev(&self) {
|
||||||
self.tx.send(MessageFromUi::PrevTrack);
|
if let Err(err) = self.tx.send(MessageFromUi::PrevTrack) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn play_selected(&self) {
|
pub fn play_selected(&self) {
|
||||||
if let Some(pos) = self.selected() {
|
if let Some(pos) = self.selected() {
|
||||||
self.tx.send(MessageFromUi::SetCurrentTrack(pos));
|
if let Err(err) = self.tx.send(MessageFromUi::SetCurrentTrack(pos)) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn remove_track(&mut self) {
|
pub fn remove_track(&mut self) {
|
||||||
if let Some(pos) = self.selected() {
|
if let Some(pos) = self.selected() {
|
||||||
// FIXME: mark multiple tracks on queue and remove them
|
// FIXME: mark multiple tracks on queue and remove them
|
||||||
self.tx.send(MessageFromUi::RemoveTracks(vec![pos]));
|
if let Err(err) = self.tx.send(MessageFromUi::RemoveTracks(vec![pos])) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn update_position(&mut self, pos: usize) {
|
pub fn update_position(&mut self, pos: usize) {
|
||||||
|
|
@ -57,10 +66,9 @@ impl Queue {
|
||||||
self.list = queue
|
self.list = queue
|
||||||
.tracks
|
.tracks
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.map(|track| UiItem {
|
||||||
.map(|(i, t)| UiItem {
|
uuid: track.uuid.clone(),
|
||||||
uuid: t.uuid.clone(),
|
title: format!("{} - {}", track.artist, track.title),
|
||||||
title: format!("{} - {}", t.artist, t.title),
|
|
||||||
kind: UiItemKind::Track,
|
kind: UiItemKind::Track,
|
||||||
marked: false,
|
marked: false,
|
||||||
is_queable: false,
|
is_queable: false,
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
use tokio::{fs, select, signal, task};
|
use tokio::{fs, select, signal, task};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
use tonic::{transport::Channel, Request, Status, Streaming};
|
use tonic::{transport::Channel, Request, Status, Streaming};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use app::{App, MessageFromUi, MessageToUi, StatefulList, UiFocus};
|
use app::{App, MessageFromUi, MessageToUi, StatefulList, UiFocus};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
|
@ -44,6 +45,7 @@ use rpc::RpcClient;
|
||||||
static CONFIG: OnceLock<Config> = OnceLock::new();
|
static CONFIG: OnceLock<Config> = OnceLock::new();
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
// FIXME: use anyhow, thiserror
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let config = CONFIG.get_or_init(|| crabidy_core::init_config("cbd-tui.toml"));
|
let config = CONFIG.get_or_init(|| crabidy_core::init_config("cbd-tui.toml"));
|
||||||
|
|
||||||
|
|
@ -51,12 +53,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let (tx, ui_rx): (Sender<MessageToUi>, Receiver<MessageToUi>) = flume::unbounded();
|
let (tx, ui_rx): (Sender<MessageToUi>, Receiver<MessageToUi>) = flume::unbounded();
|
||||||
|
|
||||||
// FIXME: unwrap
|
// FIXME: unwrap
|
||||||
tokio::spawn(async move { orchestrate(config, (tx, rx)).await.unwrap() });
|
tokio::spawn(async move { orchestrate(config, (tx, rx)).await.unwrap() }).await?;
|
||||||
|
|
||||||
tokio::task::spawn_blocking(|| {
|
tokio::task::spawn_blocking(|| {
|
||||||
run_ui(ui_tx, ui_rx);
|
run_ui(ui_tx, ui_rx);
|
||||||
})
|
})
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -68,14 +70,16 @@ async fn orchestrate<'a>(
|
||||||
let mut rpc_client = rpc::RpcClient::connect(&config.server.address).await?;
|
let mut rpc_client = rpc::RpcClient::connect(&config.server.address).await?;
|
||||||
|
|
||||||
if let Some(root_node) = rpc_client.get_library_node("node:/").await? {
|
if let Some(root_node) = rpc_client.get_library_node("node:/").await? {
|
||||||
tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone()));
|
tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let init_data = rpc_client.init().await?;
|
let init_data = rpc_client.init().await?;
|
||||||
tx.send_async(MessageToUi::Init(init_data)).await?;
|
tx.send_async(MessageToUi::Init(init_data)).await?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
poll(&mut rpc_client, &rx, &tx).await.ok();
|
if let Err(err) = poll(&mut rpc_client, &rx, &tx).await {
|
||||||
|
error!("Poll error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +93,7 @@ async fn poll(
|
||||||
match msg {
|
match msg {
|
||||||
MessageFromUi::GetLibraryNode(uuid) => {
|
MessageFromUi::GetLibraryNode(uuid) => {
|
||||||
if let Some(node) = rpc_client.get_library_node(&uuid).await? {
|
if let Some(node) = rpc_client.get_library_node(&uuid).await? {
|
||||||
tx.send(MessageToUi::ReplaceLibraryNode(node.clone()));
|
tx.send(MessageToUi::ReplaceLibraryNode(node.clone()))?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MessageFromUi::AppendTracks(uuids) => {
|
MessageFromUi::AppendTracks(uuids) => {
|
||||||
|
|
@ -211,7 +215,7 @@ fn run_ui(tx: Sender<MessageFromUi>, rx: Receiver<MessageToUi>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.draw(|f| app.render(f));
|
terminal.draw(|f| app.render(f)).unwrap();
|
||||||
|
|
||||||
let timeout = tick_rate
|
let timeout = tick_rate
|
||||||
.checked_sub(last_tick.elapsed())
|
.checked_sub(last_tick.elapsed())
|
||||||
|
|
@ -226,25 +230,39 @@ fn run_ui(tx: Sender<MessageFromUi>, rx: Receiver<MessageToUi>) {
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::NONE, KeyCode::Tab) => app.cycle_active(),
|
(_, KeyModifiers::NONE, KeyCode::Tab) => app.cycle_active(),
|
||||||
(_, KeyModifiers::NONE, KeyCode::Char(' ')) => {
|
(_, KeyModifiers::NONE, KeyCode::Char(' ')) => {
|
||||||
tx.send(MessageFromUi::TogglePlay);
|
if let Err(err) = tx.send(MessageFromUi::TogglePlay) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::NONE, KeyCode::Char('r')) => {
|
(_, KeyModifiers::NONE, KeyCode::Char('r')) => {
|
||||||
tx.send(MessageFromUi::RestartTrack);
|
if let Err(err) = tx.send(MessageFromUi::RestartTrack) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::SHIFT, KeyCode::Char('J')) => {
|
(_, KeyModifiers::SHIFT, KeyCode::Char('J')) => {
|
||||||
tx.send(MessageFromUi::ChangeVolume(-0.1));
|
if let Err(err) = tx.send(MessageFromUi::ChangeVolume(-0.1)) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::SHIFT, KeyCode::Char('K')) => {
|
(_, KeyModifiers::SHIFT, KeyCode::Char('K')) => {
|
||||||
tx.send(MessageFromUi::ChangeVolume(0.1));
|
if let Err(err) = tx.send(MessageFromUi::ChangeVolume(0.1)) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::NONE, KeyCode::Char('m')) => {
|
(_, KeyModifiers::NONE, KeyCode::Char('m')) => {
|
||||||
tx.send(MessageFromUi::ToggleMute);
|
if let Err(err) = tx.send(MessageFromUi::ToggleMute) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::NONE, KeyCode::Char('z')) => {
|
(_, KeyModifiers::NONE, KeyCode::Char('z')) => {
|
||||||
tx.send(MessageFromUi::ToggleShuffle);
|
if let Err(err) = tx.send(MessageFromUi::ToggleShuffle) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::NONE, KeyCode::Char('x')) => {
|
(_, KeyModifiers::NONE, KeyCode::Char('x')) => {
|
||||||
tx.send(MessageFromUi::ToggleRepeat);
|
if let Err(err) = tx.send(MessageFromUi::ToggleRepeat) {
|
||||||
|
error!("Send error: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, KeyModifiers::CONTROL, KeyCode::Char('n')) => {
|
(_, KeyModifiers::CONTROL, KeyCode::Char('n')) => {
|
||||||
app.queue.play_next();
|
app.queue.play_next();
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,7 @@ impl RpcClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reconnect_update_stream(&mut self) {
|
pub async fn reconnect_update_stream(&mut self) {
|
||||||
let update_stream = Self::get_update_stream(&mut self.client).await;
|
self.update_stream = Self::get_update_stream(&mut self.client).await;
|
||||||
// FIXME: apparently mem::replace doesn't do anything here
|
|
||||||
mem::replace(&mut self.update_stream, update_stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init(&mut self) -> Result<InitResponse, Box<dyn Error>> {
|
pub async fn init(&mut self) -> Result<InitResponse, Box<dyn Error>> {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ where
|
||||||
if let Some(config_dir) = dirs::config_dir() {
|
if let Some(config_dir) = dirs::config_dir() {
|
||||||
let dir = Path::new(&config_dir).join("crabidy");
|
let dir = Path::new(&config_dir).join("crabidy");
|
||||||
if !dir.is_dir() {
|
if !dir.is_dir() {
|
||||||
create_dir_all(&dir);
|
create_dir_all(&dir).expect("Could not create crabidy directory in config dir");
|
||||||
}
|
}
|
||||||
let config_file_path = dir.join(config_file_name);
|
let config_file_path = dir.join(config_file_name);
|
||||||
if !config_file_path.is_file() {
|
if !config_file_path.is_file() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue