Sort out string problems
This commit is contained in:
parent
13ff6a741d
commit
0731f61c41
|
|
@ -28,7 +28,7 @@ use std::{
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
vec,
|
vec,
|
||||||
};
|
};
|
||||||
use tokio::{select, task};
|
use tokio::{select, signal, task};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
// use
|
// use
|
||||||
use tonic::{transport::Channel, Request, Streaming};
|
use tonic::{transport::Channel, Request, Streaming};
|
||||||
|
|
@ -89,49 +89,110 @@ impl<T> StatefulList<T> {
|
||||||
}
|
}
|
||||||
self.state.select(None);
|
self.state.select(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_selected(&self) -> Option<&T> {
|
||||||
|
if let Some(idx) = self.state.selected() {
|
||||||
|
return Some(&self.items[idx]);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiItem {
|
||||||
|
uuid: String,
|
||||||
|
title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LibraryView {
|
||||||
|
title: String,
|
||||||
|
uuid: String,
|
||||||
|
list: StatefulList<UiItem>,
|
||||||
|
parent: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LibraryView {
|
||||||
|
fn update(&mut self, node: LibraryNode) {
|
||||||
|
if node.tracks.is_empty() && node.children.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if children empty and tracks empty return
|
||||||
|
self.uuid = node.uuid;
|
||||||
|
self.title = node.name;
|
||||||
|
self.parent = node.parent;
|
||||||
|
|
||||||
|
if !node.tracks.is_empty() {
|
||||||
|
self.list.items = node
|
||||||
|
.tracks
|
||||||
|
.iter()
|
||||||
|
.map(|t| UiItem {
|
||||||
|
uuid: t.uuid.clone(),
|
||||||
|
title: t.title.clone(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
} else {
|
||||||
|
// if tracks not empty use tracks instead
|
||||||
|
self.list.items = node
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|c| UiItem {
|
||||||
|
uuid: c.to_string(),
|
||||||
|
title: c.to_string(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
library: StatefulList<LibraryNode>,
|
library: LibraryView,
|
||||||
queue: StatefulList<LibraryNode>,
|
queue: StatefulList<UiItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new() -> App {
|
fn new() -> App {
|
||||||
let mut library = StatefulList::default();
|
let mut library = LibraryView {
|
||||||
library.focus();
|
title: "Library".to_string(),
|
||||||
|
uuid: "/".to_string(),
|
||||||
|
list: StatefulList::default(),
|
||||||
|
parent: None,
|
||||||
|
};
|
||||||
|
library.list.focus();
|
||||||
let mut queue = StatefulList::default();
|
let mut queue = StatefulList::default();
|
||||||
App { library, queue }
|
App { library, queue }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cycle_active(&mut self) {
|
fn cycle_active(&mut self) {
|
||||||
if self.library.is_focused() {
|
if self.library.list.is_focused() {
|
||||||
self.library.blur();
|
self.library.list.blur();
|
||||||
self.queue.focus();
|
self.queue.focus();
|
||||||
} else {
|
} else {
|
||||||
self.library.focus();
|
self.library.list.focus();
|
||||||
self.queue.blur();
|
self.queue.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Message<'a> {
|
// FIXME: Rename this
|
||||||
Quit,
|
enum MessageToUi {
|
||||||
// FIXME: Is String OK here?
|
ReplaceLibraryNode(LibraryNode),
|
||||||
GetLibraryNode(&'a str),
|
|
||||||
LibraryNodeReceived(LibraryNode),
|
|
||||||
QueueStreamUpdate(QueueUpdateResult),
|
QueueStreamUpdate(QueueUpdateResult),
|
||||||
TrackStreamUpdate(GetTrackUpdatesResponse),
|
TrackStreamUpdate(GetTrackUpdatesResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Rename this
|
||||||
|
enum MessageFromUi {
|
||||||
|
Quit,
|
||||||
|
GetLibraryNode(String),
|
||||||
|
}
|
||||||
|
|
||||||
async fn orchestrate<'a>(
|
async fn orchestrate<'a>(
|
||||||
(tx, rx): (Sender<Message<'a>>, Receiver<Message<'a>>),
|
(tx, rx): (Sender<MessageToUi>, Receiver<MessageFromUi>),
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let mut rpc_client = rpc::RpcClient::connect("http://[::1]:50051").await?;
|
let mut rpc_client = rpc::RpcClient::connect("http://[::1]:50051").await?;
|
||||||
|
|
||||||
if let Some(root_node) = rpc_client.get_library_node("/").await? {
|
if let Some(root_node) = rpc_client.get_library_node("/").await? {
|
||||||
// FIXME: Is it ok to clone here?
|
// FIXME: Is it ok to clone here?
|
||||||
tx.send(Message::LibraryNodeReceived(root_node.clone()));
|
tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: stream failures, do we need to re-establish the stream?
|
// FIXME: stream failures, do we need to re-establish the stream?
|
||||||
|
|
@ -142,27 +203,23 @@ async fn orchestrate<'a>(
|
||||||
select! {
|
select! {
|
||||||
Ok(msg) = &mut rx.recv_async() => {
|
Ok(msg) = &mut rx.recv_async() => {
|
||||||
match msg {
|
match msg {
|
||||||
// FIXME: How can I make sure I have all match arms implmenented?
|
MessageFromUi::Quit => {
|
||||||
// (Some messages are not applicable here)
|
|
||||||
Message::Quit => {
|
|
||||||
break Ok(());
|
break Ok(());
|
||||||
},
|
},
|
||||||
Message::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? {
|
||||||
// FIXME: Is it ok to clone here?
|
tx.send(MessageToUi::ReplaceLibraryNode(node.clone()));
|
||||||
tx.send(Message::LibraryNodeReceived(node.clone()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Ok(resp)) = queue_update_stream.next() => {
|
Some(Ok(resp)) = queue_update_stream.next() => {
|
||||||
if let Some(res) = resp.queue_update_result {
|
if let Some(res) = resp.queue_update_result {
|
||||||
tx.send_async(Message::QueueStreamUpdate(res)).await;
|
tx.send_async(MessageToUi::QueueStreamUpdate(res)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Ok(resp)) = track_update_stream.next() => {
|
Some(Ok(resp)) = track_update_stream.next() => {
|
||||||
tx.send(Message::TrackStreamUpdate(resp));
|
tx.send(MessageToUi::TrackStreamUpdate(resp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -170,8 +227,8 @@ async fn orchestrate<'a>(
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let (ui_tx, rx): (Sender<Message>, Receiver<Message>) = flume::unbounded();
|
let (ui_tx, rx): (Sender<MessageFromUi>, Receiver<MessageFromUi>) = flume::unbounded();
|
||||||
let (tx, ui_rx): (Sender<Message>, Receiver<Message>) = flume::unbounded();
|
let (tx, ui_rx): (Sender<MessageToUi>, Receiver<MessageToUi>) = flume::unbounded();
|
||||||
|
|
||||||
thread::spawn(|| {
|
thread::spawn(|| {
|
||||||
run_ui(ui_tx, ui_rx);
|
run_ui(ui_tx, ui_rx);
|
||||||
|
|
@ -180,19 +237,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// FIXME: unwrap
|
// FIXME: unwrap
|
||||||
tokio::spawn(async move { orchestrate((tx, rx)).await.unwrap() });
|
tokio::spawn(async move { orchestrate((tx, rx)).await.unwrap() });
|
||||||
|
|
||||||
// loop {
|
signal::ctrl_c().await.unwrap();
|
||||||
// match rx.recv() {
|
|
||||||
// Ok(Message::Quit) => {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// _ => {}
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_ui(tx: Sender<Message>, rx: Receiver<Message>) {
|
fn run_ui(tx: Sender<MessageFromUi>, rx: Receiver<MessageToUi>) {
|
||||||
// setup terminal
|
// setup terminal
|
||||||
enable_raw_mode().unwrap();
|
enable_raw_mode().unwrap();
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
|
|
@ -208,32 +258,15 @@ fn run_ui(tx: Sender<Message>, rx: Receiver<Message>) {
|
||||||
loop {
|
loop {
|
||||||
for message in rx.try_iter() {
|
for message in rx.try_iter() {
|
||||||
match message {
|
match message {
|
||||||
Message::LibraryNodeReceived(node) => {
|
MessageToUi::ReplaceLibraryNode(node) => {
|
||||||
// FIXME: this is obviously bullshit
|
app.library.update(node);
|
||||||
// FIXME: DO NOT PUSH LIBRARY_NODES ONTO THE UI, IT SHOULD GET ITS OWN TYPE
|
|
||||||
app.library.items.push(LibraryNode {
|
|
||||||
uuid: node.uuid,
|
|
||||||
name: node.name,
|
|
||||||
children: Vec::new(),
|
|
||||||
is_queable: false,
|
|
||||||
parent: None,
|
|
||||||
state: LibraryNodeState::Done.into(),
|
|
||||||
tracks: Vec::new(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Message::QueueStreamUpdate(queue_update) => match queue_update {
|
MessageToUi::QueueStreamUpdate(queue_update) => match queue_update {
|
||||||
QueueUpdateResult::Full(queue) => {}
|
QueueUpdateResult::Full(queue) => {}
|
||||||
QueueUpdateResult::PositionChange(pos) => {
|
QueueUpdateResult::PositionChange(pos) => {
|
||||||
// FIXME: this is obviously bullshit
|
app.queue.items.push(UiItem {
|
||||||
// FIXME: DO NOT PUSH LIBRARY_NODES ONTO THE UI, IT SHOULD GET ITS OWN TYPE
|
|
||||||
app.queue.items.push(LibraryNode {
|
|
||||||
uuid: pos.timestamp.to_string(),
|
uuid: pos.timestamp.to_string(),
|
||||||
name: pos.timestamp.to_string(),
|
title: pos.timestamp.to_string(),
|
||||||
children: Vec::new(),
|
|
||||||
is_queable: false,
|
|
||||||
parent: None,
|
|
||||||
state: LibraryNodeState::Done.into(),
|
|
||||||
tracks: Vec::new(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -252,24 +285,34 @@ fn run_ui(tx: Sender<Message>, rx: Receiver<Message>) {
|
||||||
if key.kind == KeyEventKind::Press {
|
if key.kind == KeyEventKind::Press {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('q') => {
|
KeyCode::Char('q') => {
|
||||||
tx.send(Message::Quit);
|
tx.send(MessageFromUi::Quit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
KeyCode::Char('j') => {
|
KeyCode::Char('j') => {
|
||||||
if app.library.is_focused() {
|
if app.library.list.is_focused() {
|
||||||
app.library.next();
|
app.library.list.next();
|
||||||
} else {
|
} else {
|
||||||
app.queue.next();
|
app.queue.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('k') => {
|
KeyCode::Char('k') => {
|
||||||
if app.library.is_focused() {
|
if app.library.list.is_focused() {
|
||||||
app.library.prev();
|
app.library.list.prev();
|
||||||
} else {
|
} else {
|
||||||
app.queue.prev();
|
app.queue.prev();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Tab => app.cycle_active(),
|
KeyCode::Tab => app.cycle_active(),
|
||||||
|
KeyCode::Char('h') => {
|
||||||
|
if let Some(parent) = app.library.parent.as_ref() {
|
||||||
|
tx.send(MessageFromUi::GetLibraryNode(parent.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char('l') => {
|
||||||
|
if let Some(item) = app.library.list.get_selected() {
|
||||||
|
tx.send(MessageFromUi::GetLibraryNode(item.uuid.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -302,21 +345,22 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
||||||
|
|
||||||
let library_items: Vec<ListItem> = app
|
let library_items: Vec<ListItem> = app
|
||||||
.library
|
.library
|
||||||
|
.list
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
// FIXME: why to_string() ??
|
// FIXME: why to_string() ??
|
||||||
.map(|i| ListItem::new(Span::from(i.name.to_string())))
|
.map(|i| ListItem::new(Span::from(i.title.to_string())))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let library_list = List::new(library_items)
|
let library_list = List::new(library_items)
|
||||||
.block(Block::default().borders(Borders::ALL).title("Library"))
|
.block(Block::default().borders(Borders::ALL).title(app.library.title.clone()))
|
||||||
.highlight_style(
|
.highlight_style(
|
||||||
Style::default()
|
Style::default()
|
||||||
.bg(Color::LightBlue)
|
.bg(Color::LightBlue)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
);
|
);
|
||||||
|
|
||||||
f.render_stateful_widget(library_list, main[0], &mut app.library.state);
|
f.render_stateful_widget(library_list, main[0], &mut app.library.list.state);
|
||||||
|
|
||||||
let now_playing = Layout::default()
|
let now_playing = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
|
|
@ -328,7 +372,7 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
// FIXME: why to_string() ??
|
// FIXME: why to_string() ??
|
||||||
.map(|i| ListItem::new(Span::from(i.name.to_string())))
|
.map(|i| ListItem::new(Span::from(i.title.to_string())))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let queue_list = List::new(queue_items)
|
let queue_list = List::new(queue_items)
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,15 @@ impl fmt::Display for RpcClientError {
|
||||||
|
|
||||||
impl Error for RpcClientError {}
|
impl Error for RpcClientError {}
|
||||||
|
|
||||||
pub struct RpcClient<'a> {
|
pub struct RpcClient {
|
||||||
library_node_cache: HashMap<&'a str, LibraryNode>,
|
library_node_cache: HashMap<String, LibraryNode>,
|
||||||
client: CrabidyServiceClient<Channel>,
|
client: CrabidyServiceClient<Channel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RpcClient<'a> {
|
impl RpcClient {
|
||||||
pub async fn connect(addr: &'static str) -> Result<RpcClient<'a>, tonic::transport::Error> {
|
pub async fn connect(addr: &'static str) -> Result<RpcClient, tonic::transport::Error> {
|
||||||
let client = CrabidyServiceClient::connect(addr).await?;
|
let client = CrabidyServiceClient::connect(addr).await?;
|
||||||
let library_node_cache: HashMap<&str, LibraryNode> = HashMap::new();
|
let library_node_cache: HashMap<String, LibraryNode> = HashMap::new();
|
||||||
Ok(RpcClient {
|
Ok(RpcClient {
|
||||||
client,
|
client,
|
||||||
library_node_cache,
|
library_node_cache,
|
||||||
|
|
@ -46,7 +46,7 @@ impl<'a> RpcClient<'a> {
|
||||||
}
|
}
|
||||||
pub async fn get_library_node(
|
pub async fn get_library_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
uuid: &'a str,
|
uuid: &str,
|
||||||
) -> Result<Option<&LibraryNode>, Box<dyn Error>> {
|
) -> Result<Option<&LibraryNode>, Box<dyn Error>> {
|
||||||
if self.library_node_cache.contains_key(uuid) {
|
if self.library_node_cache.contains_key(uuid) {
|
||||||
return Ok(self.library_node_cache.get(uuid));
|
return Ok(self.library_node_cache.get(uuid));
|
||||||
|
|
@ -62,7 +62,8 @@ impl<'a> RpcClient<'a> {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(library_node) = response.into_inner().node {
|
if let Some(library_node) = response.into_inner().node {
|
||||||
self.library_node_cache.insert(uuid, library_node);
|
self.library_node_cache
|
||||||
|
.insert(uuid.to_string(), library_node);
|
||||||
// FIXME: is that necessary?
|
// FIXME: is that necessary?
|
||||||
return Ok(self.library_node_cache.get(uuid));
|
return Ok(self.library_node_cache.get(uuid));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue