From bda30e2a72c8da33a6752a5e625d141e929a0397 Mon Sep 17 00:00:00 2001 From: plazmoid Date: Sun, 5 Dec 2021 23:29:27 +0500 Subject: [PATCH] biiiig tui, but still not working --- .gitignore | 12 +- bin/u_agent/src/lib.rs | 2 +- bin/u_panel/Cargo.toml | 1 + bin/u_panel/src/argparse.rs | 18 +- bin/u_panel/src/main.rs | 8 + bin/u_panel/src/tui/impls.rs | 18 +- bin/u_panel/src/tui/mod.rs | 54 +++--- bin/u_panel/src/tui/retval.rs | 42 +++++ bin/u_panel/src/tui/utils.rs | 31 ++++ bin/u_panel/src/tui/windows/confirm.rs | 133 +++++++++++++++ bin/u_panel/src/tui/windows/main_wnd.rs | 50 +++--- bin/u_panel/src/tui/windows/mod.rs | 199 ++++++++++++++++++---- bin/u_panel/src/tui/windows/processing.rs | 0 integration/tests/behaviour.rs | 4 +- integration/tests/helpers/panel.rs | 6 +- lib/u_lib/src/builder.rs | 4 +- lib/u_lib/src/cache.rs | 16 +- lib/u_lib/src/models/jobs/assigned.rs | 2 +- 18 files changed, 481 insertions(+), 119 deletions(-) create mode 100644 bin/u_panel/src/tui/retval.rs create mode 100644 bin/u_panel/src/tui/utils.rs create mode 100644 bin/u_panel/src/tui/windows/confirm.rs delete mode 100644 bin/u_panel/src/tui/windows/processing.rs diff --git a/.gitignore b/.gitignore index 8946455..3b8e7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,14 @@ target/ -**/*.rs.bk .idea/ data/ +certs/ +static/ +.vscode/ +release/ + +**/*.rs.bk **/*.pyc -certs/* *.log echoer .env.private -*.lock -static/ -.vscode/ \ No newline at end of file +*.lock \ No newline at end of file diff --git a/bin/u_agent/src/lib.rs b/bin/u_agent/src/lib.rs index a0b56f1..3f422b3 100644 --- a/bin/u_agent/src/lib.rs +++ b/bin/u_agent/src/lib.rs @@ -29,7 +29,7 @@ const ITERATION_LATENCY: u64 = 5; pub async fn process_request(job_requests: Vec, client: &ClientHandler) { if !job_requests.is_empty() { for jr in &job_requests { - if !JobCache::contains(&jr.job_id) { + if !JobCache::contains(jr.job_id) { debug!("Fetching job: {}", &jr.job_id); let fetched_job = loop { match client.get_jobs(Some(jr.job_id)).await { diff --git a/bin/u_panel/Cargo.toml b/bin/u_panel/Cargo.toml index 14442d0..924ac7c 100644 --- a/bin/u_panel/Cargo.toml +++ b/bin/u_panel/Cargo.toml @@ -23,3 +23,4 @@ anyhow = "1.0.44" strum = { version = "0.22.0", features = ["derive"] } async-trait = "0.1.51" once_cell = "1.8.0" +crossbeam = "0.8.1" diff --git a/bin/u_panel/src/argparse.rs b/bin/u_panel/src/argparse.rs index 53c93bc..3ee527a 100644 --- a/bin/u_panel/src/argparse.rs +++ b/bin/u_panel/src/argparse.rs @@ -1,10 +1,7 @@ -use once_cell::sync::Lazy; -use std::env; +use crate::CLIENT; use std::fmt; use structopt::StructOpt; -use u_lib::{ - api::ClientHandler, datatypes::DataResult, messaging::AsMsg, models::JobMeta, UError, UResult, -}; +use u_lib::{datatypes::DataResult, messaging::AsMsg, models::JobMeta, UError, UResult}; use uuid::Uuid; #[derive(StructOpt, Debug)] @@ -26,9 +23,8 @@ enum Cmd { #[derive(StructOpt, Debug)] enum JobALD { Add { - #[structopt(long, parse(try_from_str = parse_uuid))] - agent: Option, - + //#[structopt(long, parse(try_from_str = parse_uuid))] + //agent: Option, #[structopt(long)] alias: String, @@ -75,11 +71,6 @@ enum LD { }, } -pub static CLIENT: Lazy = Lazy::new(|| { - let token = env::var("ADMIN_AUTH_TOKEN").expect("access token is not set"); - ClientHandler::new(None).password(token.clone()) -}); - fn parse_uuid(src: &str) -> Result { Uuid::parse_str(src).map_err(|e| e.to_string()) } @@ -135,7 +126,6 @@ pub async fn process_cmd(args: Args) -> UResult<()> { JobMapALD::List { uid } => printer.print(CLIENT.get_agent_jobs(uid).await), JobMapALD::Delete { uid } => printer.print(CLIENT.del(Some(uid)).await), }, - //Cmd::Server => be::serve().unwrap(), Cmd::TUI => crate::tui::init_tui() .await .map_err(|e| UError::TUIError(e.to_string()))?, diff --git a/bin/u_panel/src/main.rs b/bin/u_panel/src/main.rs index b48a2c1..29a1275 100644 --- a/bin/u_panel/src/main.rs +++ b/bin/u_panel/src/main.rs @@ -5,10 +5,18 @@ mod tui; extern crate async_trait; use argparse::{process_cmd, Args}; +use once_cell::sync::Lazy; +use std::env; use std::process; use structopt::StructOpt; +use u_lib::api::ClientHandler; use u_lib::utils::init_env; +pub static CLIENT: Lazy = Lazy::new(|| { + let token = env::var("ADMIN_AUTH_TOKEN").expect("access token is not set"); + ClientHandler::new(None).password(token.clone()) +}); + #[tokio::main] async fn main() { init_env(); diff --git a/bin/u_panel/src/tui/impls.rs b/bin/u_panel/src/tui/impls.rs index 9e29a48..a20adb4 100644 --- a/bin/u_panel/src/tui/impls.rs +++ b/bin/u_panel/src/tui/impls.rs @@ -1,28 +1,30 @@ -use crate::argparse::CLIENT; +use crate::tui::windows::{ConfirmWnd, MainWnd, WndId}; +use crate::CLIENT; use u_lib::models::{Agent, AssignedJob, JobMeta}; use u_lib::UResult; use uuid::Uuid; -pub trait Id { - fn id(&self) -> Uuid; +pub trait Id { + fn id(&self) -> T; } #[macro_export] macro_rules! impl_id { - ($($type:ty),+) => { + ($id_type:tt => $($type:tt),+) => { $( - impl Id for $type { - fn id(&self) -> Uuid { + impl Id<$id_type> for $type { + fn id(&self) -> $id_type { self.id } })+ }; } -impl_id!(Agent, JobMeta, AssignedJob); +impl_id!(Uuid => Agent, JobMeta, AssignedJob); +impl_id!(WndId => MainWnd, ConfirmWnd); #[async_trait] -pub trait CRUD: Id +pub trait CRUD: Id where Self: Sized, { diff --git a/bin/u_panel/src/tui/mod.rs b/bin/u_panel/src/tui/mod.rs index 8ffd734..4c414f2 100644 --- a/bin/u_panel/src/tui/mod.rs +++ b/bin/u_panel/src/tui/mod.rs @@ -1,29 +1,39 @@ mod impls; +mod retval; +mod utils; mod windows; use anyhow::Result as AResult; use backtrace::Backtrace; -use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event}; +use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}; use crossterm::execute; use crossterm::terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }; +use once_cell::sync::Lazy; +use retval::{RetVal, ReturnValue}; use std::panic::set_hook; use std::process::exit; use std::{ io::{stdout, Stdout}, - sync::mpsc, thread, time::Duration, }; use tui::{backend::CrosstermBackend, Terminal}; -use windows::{MainWnd, WindowsHandler}; +use utils::Channel; +use windows::{MainWnd, SharedWnd, WindowsHandler, WndId}; pub type Backend = CrosstermBackend; pub type Frame<'f> = tui::Frame<'f, Backend>; -enum InputEvent { - Key(I), +const EVENT_GEN_PERIOD: Duration = Duration::from_millis(120); + +static GENERAL_EVENT_CHANNEL: Lazy> = Lazy::new(|| Channel::new()); + +enum GEvent { + CreateWnd(SharedWnd), + CloseWnd { wid: WndId, force: bool }, + Key(KeyCode), Tick, } @@ -51,32 +61,38 @@ async fn init() -> AResult<()> { enable_raw_mode()?; execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?; - let mut wh = WindowsHandler::new(get_terminal()?); - wh.push::(); - let (tx, rx) = mpsc::channel(); + WindowsHandler::lock().await.push_new::().await; thread::spawn(move || loop { - if event::poll(Duration::from_millis(10)).unwrap() { + if event::poll(EVENT_GEN_PERIOD).unwrap() { match event::read().unwrap() { - key @ Event::Key(_) => tx.send(InputEvent::Key(key)).unwrap(), + Event::Key(key) => GENERAL_EVENT_CHANNEL.send(GEvent::Key(key.code)), _ => (), } } else { - tx.send(InputEvent::Tick).unwrap() + GENERAL_EVENT_CHANNEL.send(GEvent::Tick) } }); - wh.clear()?; + WindowsHandler::lock().await.clear()?; loop { - wh.draw()?; - wh.update().await?; - match rx.recv()? { - InputEvent::Key(Event::Key(key)) => { - wh.handle_kbd(key.code).await?; + match GENERAL_EVENT_CHANNEL.recv() { + GEvent::Tick => { + let mut wh = WindowsHandler::lock().await; + wh.update().await?; + wh.draw().await?; + } + GEvent::CloseWnd { wid, force } => { + let mut wh = WindowsHandler::lock().await; + wh.close(wid, force).await; + } + GEvent::Key(key) => { + WindowsHandler::lock().await.send_handle_kbd(key); + } + GEvent::CreateWnd(wnd) => { + WindowsHandler::lock().await.push_dyn(wnd).await; } - InputEvent::Tick => (), - _ => unreachable!(), } } } diff --git a/bin/u_panel/src/tui/retval.rs b/bin/u_panel/src/tui/retval.rs new file mode 100644 index 0000000..6d470b7 --- /dev/null +++ b/bin/u_panel/src/tui/retval.rs @@ -0,0 +1,42 @@ +use once_cell::sync::Lazy; +use std::any::{type_name, Any}; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::sync::Mutex; + +type RV = Box; +static LAST_RETVAL: Lazy>> = Lazy::new(|| Mutex::new(None)); + +pub struct ReturnValue; + +impl ReturnValue { + pub async fn set(t: RV) { + *LAST_RETVAL.lock().await = Some(t); + } + + pub async fn get() -> Box { + ReturnValue + .await + .downcast::() + .expect(&format!("wrong type {}", type_name::())) + } +} + +impl Future for ReturnValue { + type Output = Box; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if let Poll::Ready(mut rv) = unsafe { Pin::new_unchecked(&mut LAST_RETVAL.lock()) }.poll(cx) + { + if let Some(rv) = rv.take() { + return Poll::Ready(rv); + } + } + Poll::Pending + } +} + +pub trait RetVal { + fn retval(&self) -> RV; +} diff --git a/bin/u_panel/src/tui/utils.rs b/bin/u_panel/src/tui/utils.rs new file mode 100644 index 0000000..f1a1f2c --- /dev/null +++ b/bin/u_panel/src/tui/utils.rs @@ -0,0 +1,31 @@ +use crossbeam::channel::{unbounded, Receiver, Sender}; + +pub struct Channel { + pub tx: Sender, + pub rx: Receiver, +} + +impl Channel { + pub fn new() -> Self { + let (tx, rx) = unbounded::(); + Self { tx, rx } + } + + pub fn send(&self, msg: T) { + self.tx.send(msg).unwrap() + } + + pub fn recv(&self) -> T { + self.rx.recv().unwrap() + } + + pub fn is_empty(&self) -> bool { + self.rx.is_empty() + } +} + +impl Default for Channel { + fn default() -> Self { + Channel::new() + } +} diff --git a/bin/u_panel/src/tui/windows/confirm.rs b/bin/u_panel/src/tui/windows/confirm.rs new file mode 100644 index 0000000..41ca76e --- /dev/null +++ b/bin/u_panel/src/tui/windows/confirm.rs @@ -0,0 +1,133 @@ +use super::{Window, WndId}; +use crate::tui::{Frame, GEvent, RetVal, ReturnValue, GENERAL_EVENT_CHANNEL}; +use anyhow::Result as AResult; +use crossterm::event::KeyCode; +use std::any::Any; +use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; +use tui::style::{Modifier, Style}; +use tui::widgets::{Block, Clear, List, ListItem, ListState, Paragraph}; + +#[derive(Default)] +pub struct ConfirmWnd { + pub id: WndId, + msg: String, + variants: Vec, + state: ListState, +} + +impl ConfirmWnd { + pub fn new_yn(msg: impl Into, variants: Option>) -> Self { + let default = vec!["Yes".to_string(), "No".to_string()]; + let variants = match variants { + Some(v) if !v.is_empty() => v, + _ => default, + }; + let mut this = Self { + msg: msg.into(), + variants, + ..Default::default() + }; + this.state.select(Some(0)); + this + } + + pub fn on_right(&mut self) { + let selected = self.state.selected().unwrap_or(0); + self.state + .select(Some((selected + 1).rem_euclid(self.variants.len()))); + } + + pub fn on_left(&mut self) { + let selected = self.state.selected().unwrap_or(0); + let vars_len = self.variants.len(); + self.state + .select(Some((selected + vars_len - 1).rem_euclid(vars_len))); + } +} + +impl RetVal for ConfirmWnd { + fn retval(&self) -> Box { + let value = self + .variants + .get(self.state.selected().unwrap()) + .unwrap() + .to_owned(); + Box::new(value) + } +} + +#[async_trait] +impl Window for ConfirmWnd { + async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()> { + match k { + KeyCode::Right => self.on_right(), + KeyCode::Left => self.on_left(), + KeyCode::Enter | KeyCode::Esc => self.close(false).await, + _ => (), + } + Ok(()) + } + + async fn handle_update(&mut self) -> AResult<()> { + Ok(()) + } + + async fn handle_close(&mut self) -> bool { + true + } + + fn draw(&mut self, f: &mut Frame) { + let size = f.size(); + let rect = centered_rect(60, 40, size); + f.render_widget(Clear, rect); + + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![Constraint::Percentage(70), Constraint::Percentage(30)]) + .split(rect); + let msg = Paragraph::new(self.msg.as_ref()); + f.render_widget(msg, chunks[0]); + + let options = self + .variants + .iter() + .map(AsRef::as_ref) + .map(ListItem::new) + .collect::>(); + let list = + List::new(options).highlight_style(Style::default().add_modifier(Modifier::BOLD)); + f.render_stateful_widget(list, chunks[1], &mut self.state); + } +} + +pub async fn confirm_wnd(msg: impl Into) -> bool { + let wnd = ConfirmWnd::new_yn(msg.into(), None); + GENERAL_EVENT_CHANNEL.send(GEvent::CreateWnd(wnd.into_shared())); + *ReturnValue::get().await +} + +fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ] + .as_ref(), + ) + .split(r); + + Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ] + .as_ref(), + ) + .split(popup_layout[1])[1] +} diff --git a/bin/u_panel/src/tui/windows/main_wnd.rs b/bin/u_panel/src/tui/windows/main_wnd.rs index 4cebc8f..e595e1f 100644 --- a/bin/u_panel/src/tui/windows/main_wnd.rs +++ b/bin/u_panel/src/tui/windows/main_wnd.rs @@ -1,7 +1,8 @@ -use super::Window; -use crate::tui::{impls::CRUD, Frame}; +use super::{confirm_wnd, Window}; +use crate::tui::{impls::CRUD, windows::WndId, Frame, RetVal}; use anyhow::Result as AResult; use crossterm::event::KeyCode; +use std::any::Any; use std::{fmt::Display, str::FromStr}; use strum::VariantNames; use tokio::join; @@ -32,13 +33,13 @@ impl UiTabs { } pub fn next(&self) -> Self { - let next_idx = (self.index() + 1) % Self::VARIANTS.len(); + let next_idx = (self.index() + 1).rem_euclid(Self::VARIANTS.len()); Self::from_str(Self::VARIANTS[next_idx]).unwrap() } pub fn prev(&self) -> Self { let vlen = Self::VARIANTS.len(); - let next_idx = (self.index() + vlen - 1) % vlen; + let next_idx = (self.index() + vlen - 1).rem_euclid(vlen); Self::from_str(Self::VARIANTS[next_idx]).unwrap() } } @@ -52,7 +53,7 @@ pub struct StatefulList { impl StatefulList { pub async fn update(&mut self) -> AResult<()> { if !self.updated { - let new_values = ::read().await?; + let new_values = T::read().await?; self.inner = new_values; self.updated = true; } @@ -62,7 +63,7 @@ impl StatefulList { pub async fn delete(&mut self) -> AResult<()> { if let Some(s) = self.state.selected() { let uid = self.inner[s].id(); - ::delete(uid).await?; + T::delete(uid).await?; } Ok(()) } @@ -90,6 +91,7 @@ impl Default for StatefulList { } pub struct MainWnd { + pub id: WndId, pub active_tab: UiTabs, pub last_error: Option, pub agents: StatefulList, @@ -105,6 +107,7 @@ impl Default for MainWnd { agents: Default::default(), jobs: Default::default(), map: Default::default(), + id: Default::default(), } } } @@ -194,7 +197,10 @@ impl MainWnd { pub fn update_tab(&mut self) { match self.active_tab { - UiTabs::Agents => self.agents.updated = false, + UiTabs::Agents => { + self.agents.updated = false; + self.jobs.updated = false; + } UiTabs::Jobs => self.jobs.updated = false, UiTabs::Map => self.map.updated = false, } @@ -209,8 +215,8 @@ impl MainWnd { if list_len == 0 { list_state.select(None); } else { - let selected = list_state.selected().unwrap_or(0); - list_state.select(Some((selected + 1) % list_len)); + let selected = list_state.selected().unwrap_or(list_len - 1); + list_state.select(Some((selected + 1).rem_euclid(list_len))); } } @@ -224,7 +230,7 @@ impl MainWnd { list_state.select(None); } else { let selected = list_state.selected().unwrap_or(1); - list_state.select(Some((selected + list_len - 1) % list_len)); + list_state.select(Some((selected + list_len - 1).rem_euclid(list_len))); } } @@ -244,18 +250,16 @@ impl MainWnd { impl Window for MainWnd { async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()> { match k { - KeyCode::Esc => { - /*teardown()?; - wh.show_cursor()?; - break;*/ - } + KeyCode::Esc => self.close(false).await, KeyCode::Left => self.prev_tab(), KeyCode::Right => self.next_tab(), KeyCode::Up => self.on_up(), KeyCode::Down => self.on_down(), KeyCode::Delete => { - self.delete().await; - self.update_tab(); + if confirm_wnd("Delete?").await { + self.delete().await; + self.update_tab(); + } } KeyCode::F(5) => self.update_tab(), _ => (), @@ -263,13 +267,13 @@ impl Window for MainWnd { Ok(()) } - async fn update(&mut self) -> AResult<()> { + async fn handle_update(&mut self) -> AResult<()> { self.check_updates().await; Ok(()) } - async fn close(&mut self) { - //self.show_cursor() + async fn handle_close(&mut self) -> bool { + true } fn draw(&mut self, f: &mut Frame) { @@ -308,6 +312,12 @@ impl Window for MainWnd { } } +impl RetVal for MainWnd { + fn retval(&self) -> Box { + Box::new(()) + } +} + fn crop(data: T, retain: usize) -> String { data.to_string()[..retain].to_string() } diff --git a/bin/u_panel/src/tui/windows/mod.rs b/bin/u_panel/src/tui/windows/mod.rs index 3cc7e31..87c430b 100644 --- a/bin/u_panel/src/tui/windows/mod.rs +++ b/bin/u_panel/src/tui/windows/mod.rs @@ -1,47 +1,158 @@ +mod confirm; mod main_wnd; -mod processing; +pub use confirm::{confirm_wnd, ConfirmWnd}; +use crossbeam::channel::Sender; pub use main_wnd::MainWnd; -use crate::tui::{Backend, Frame}; +use crate::tui::{ + get_terminal, impls::Id, teardown, utils::Channel, Backend, Frame, GEvent, RetVal, ReturnValue, + GENERAL_EVENT_CHANNEL, +}; use anyhow::Result as AResult; use crossterm::event::KeyCode; -use std::cell::RefCell; +use once_cell::sync::{Lazy, OnceCell}; +use std::collections::BTreeMap; use std::process::exit; -use std::rc::Rc; +use std::sync::Arc; +use std::sync::{Mutex as StdMutex, MutexGuard as StdMutexGuard}; +use tokio::sync::{Mutex, MutexGuard}; +use tokio::task::{self, JoinHandle}; use tui::Terminal; -type Wnd = Rc>; +static WINDOWS: Lazy>> = + Lazy::new(|| Arc::new(Mutex::new(WindowsHandler::new(get_terminal().unwrap())))); + +static LAST_WND_ID: OnceCell> = OnceCell::new(); + +static WND_EVENT_CHANNEL: Lazy> = Lazy::new(|| Channel::new()); + +pub type SharedWnd = Arc>; + +enum WndEvent { + Key(KeyCode), +} + +#[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Copy, Clone, Debug)] +pub struct WndId(u64); + +impl WndId { + pub fn last_wid() -> StdMutexGuard<'static, WndId> { + LAST_WND_ID + .get_or_init(|| StdMutex::new(WndId(0))) + .lock() + .unwrap() + } +} + +impl Default for WndId { + fn default() -> Self { + let mut wid = Self::last_wid(); + wid.0 += 1; + *wid + } +} #[async_trait] -pub trait Window { +pub trait Window: Id + RetVal + Send { + async fn check_wnd_events(&mut self) { + if !WND_EVENT_CHANNEL.is_empty() { + match WND_EVENT_CHANNEL.recv() { + WndEvent::Key(k) => { + self.handle_kbd(k).await.unwrap(); + } + } + } + } + + async fn close(&mut self, force: bool) { + let rv = self.retval(); + ReturnValue::set(rv).await; + GENERAL_EVENT_CHANNEL.send(GEvent::CloseWnd { + wid: self.id(), + force, + }); + } + + fn into_shared(self) -> SharedWnd + where + Self: Sized + 'static, + { + Arc::new(Mutex::new(self)) + } + + async fn handle_update(&mut self) -> AResult<()>; + + async fn handle_close(&mut self) -> bool; + async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()>; - async fn update(&mut self) -> AResult<()>; - async fn close(&mut self); + fn draw(&mut self, f: &mut Frame); } +pub struct WndLoop { + upd_tx: Sender<()>, + updater: JoinHandle<()>, + window: SharedWnd, +} + +impl WndLoop { + pub fn with_wnd(window: SharedWnd) -> Self { + let Channel { tx, rx } = Channel::new(); + let wnd = window.clone(); + let wnd_loop = async move { + loop { + rx.recv().unwrap(); + wnd.lock().await.check_wnd_events().await; + } + }; + WndLoop { + upd_tx: tx, + window, + updater: task::spawn(wnd_loop), + } + } + + pub fn send_update(&self) { + self.upd_tx.send(()).unwrap(); + } +} + pub struct WindowsHandler { - queue: Vec, + queue: BTreeMap, term: Terminal, redraw_all: bool, } impl WindowsHandler { - fn get_last_wnd(&self) -> Wnd { - self.queue.last().expect("No windows found").clone() + fn get_last_wnd(&self) -> &WndLoop { + let last_id = self.queue.keys().rev().next().expect("No windows found"); + self.queue.get(last_id).unwrap() + } + + pub async fn lock<'l>() -> MutexGuard<'l, Self> { + WINDOWS.lock().await } - pub fn new(term: Terminal) -> Self { + fn new(term: Terminal) -> Self { Self { term, - queue: vec![], + queue: BTreeMap::new(), redraw_all: true, } } - pub fn push(&mut self) { - let window = Rc::new(RefCell::new(W::default())); - self.queue.push(window); + pub async fn push(&mut self, window: W) -> SharedWnd { + self.push_dyn(window.into_shared()).await + } + + pub async fn push_new(&mut self) -> SharedWnd { + self.push(W::default()).await + } + + pub async fn push_dyn(&mut self, window: SharedWnd) -> SharedWnd { + let wid = window.lock().await.id(); + self.queue.insert(wid, WndLoop::with_wnd(window.clone())); + window } pub fn clear(&mut self) -> AResult<()> { @@ -54,41 +165,57 @@ impl WindowsHandler { Ok(()) } - pub fn draw(&mut self) -> AResult<()> { - if self.redraw_all { - for wnd in self.queue.iter() { - self.term.draw(|f| wnd.borrow_mut().draw(f))?; - } + pub async fn draw(&mut self) -> AResult<()> { + let wids_to_redraw = if self.redraw_all { + self.redraw_all = false; + let mut wids = self.queue.keys().cloned().collect::>(); + wids.sort(); + wids } else { - let wnd = self.get_last_wnd(); - self.term.draw(|f| wnd.borrow_mut().draw(f))?; + vec![*WndId::last_wid()] + }; + for wid in wids_to_redraw { + let mut wnd_locked = match self.queue.get(&wid) { + Some(w) => w.window.lock().await, + None => { + eprintln!("Can't redraw window {:?}, not found", wid); + continue; + } + }; + self.term.draw(move |f| wnd_locked.draw(f))?; } Ok(()) } - pub async fn close(&mut self) { - let wnd = self.queue.pop().unwrap(); - wnd.borrow_mut().close().await; - self.redraw_all = true; + pub async fn close(&mut self, wid: WndId, force: bool) { + let wnd = match self.queue.get(&wid) { + Some(w) => w.clone(), + None => { + eprintln!("Can't close window {:?}, not found", wid); + return; + } + }; + if wnd.window.lock().await.handle_close().await || force { + let WndLoop { updater, .. } = self.queue.remove(&wid).unwrap(); + updater.abort(); + self.redraw_all = true; + } + if self.queue.is_empty() { self.show_cursor().unwrap(); + teardown().unwrap(); exit(0); } } - pub async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()> { - if let KeyCode::Esc = k { - self.close().await; - } else { - let current_wnd = self.get_last_wnd(); - current_wnd.borrow_mut().handle_kbd(k).await?; - } - Ok(()) + pub fn send_handle_kbd(&self, k: KeyCode) { + WND_EVENT_CHANNEL.send(WndEvent::Key(k)); } pub async fn update(&mut self) -> AResult<()> { let current_wnd = self.get_last_wnd(); - current_wnd.borrow_mut().update().await?; + current_wnd.send_update(); + current_wnd.window.lock().await.handle_update().await?; Ok(()) } } diff --git a/bin/u_panel/src/tui/windows/processing.rs b/bin/u_panel/src/tui/windows/processing.rs deleted file mode 100644 index e69de29..0000000 diff --git a/integration/tests/behaviour.rs b/integration/tests/behaviour.rs index 38e88f2..fcc3c63 100644 --- a/integration/tests/behaviour.rs +++ b/integration/tests/behaviour.rs @@ -29,11 +29,11 @@ async fn test_setup_tasks() -> TestResult { let job_alias = "passwd_contents"; let cmd = format!("jobs add --alias {} 'cat /etc/passwd'", job_alias); Panel::check_status(cmd); - let cmd = format!("jobmap add {} {}", agent_uid, job_alias); + let cmd = format!("map add {} {}", agent_uid, job_alias); let assigned_uids: Vec = Panel::check_output(cmd); for _ in 0..3 { let result: Vec = - Panel::check_output(format!("jobmap list {}", assigned_uids[0])); + Panel::check_output(format!("map list {}", assigned_uids[0])); if result[0].state == JobState::Finished { return Ok(()); } else { diff --git a/integration/tests/helpers/panel.rs b/integration/tests/helpers/panel.rs index 929715a..9406d90 100644 --- a/integration/tests/helpers/panel.rs +++ b/integration/tests/helpers/panel.rs @@ -35,7 +35,8 @@ impl Panel { }) } - pub fn output(args: impl Into) -> PanelResult { + pub fn output(args: impl Into + Display) -> PanelResult { + println!("Executing 'u_panel {}'", &args); let splitted = split(args.into().as_ref()).unwrap(); Self::output_argv( splitted @@ -54,12 +55,11 @@ impl Panel { } pub fn check_status(args: impl Into + Display) { - println!("Panel: executing '{}'", &args); let result: PanelResult = Self::output(args); Self::status_is_ok(result); } - pub fn check_output(args: impl Into) -> T { + pub fn check_output(args: impl Into + Display) -> T { let result = Self::output(args); Self::status_is_ok(result) } diff --git a/lib/u_lib/src/builder.rs b/lib/u_lib/src/builder.rs index 726d74a..e6ce08f 100644 --- a/lib/u_lib/src/builder.rs +++ b/lib/u_lib/src/builder.rs @@ -12,7 +12,7 @@ impl JobBuilder { let mut prepared: Vec = vec![]; let mut result = CombinedResult::::new(); for req in job_requests { - let job_meta = JobCache::get(&req.job_id); + let job_meta = JobCache::get(req.job_id); if job_meta.is_none() { result.err(UError::NoJob(req.job_id).into_bt()); continue; @@ -22,7 +22,7 @@ impl JobBuilder { let built_req = (|| -> UResult<()> { Ok(match job_meta.exec_type { JobType::Shell => { - let meta = JobCache::get(&req.job_id).ok_or(UError::NoJob(req.job_id))?; + let meta = JobCache::get(req.job_id).ok_or(UError::NoJob(req.job_id))?; let curr_platform = guess_host_triple().unwrap_or("unknown").to_string(); //TODO: extend platform checking (partial check) if meta.platform != curr_platform { diff --git a/lib/u_lib/src/cache.rs b/lib/u_lib/src/cache.rs index 3af1724..9d173ee 100644 --- a/lib/u_lib/src/cache.rs +++ b/lib/u_lib/src/cache.rs @@ -20,11 +20,11 @@ impl JobCache { JOB_CACHE.write().unwrap().insert(job_meta.id, job_meta); } - pub fn contains(uid: &Uuid) -> bool { - JOB_CACHE.read().unwrap().contains_key(uid) + pub fn contains(uid: Uuid) -> bool { + JOB_CACHE.read().unwrap().contains_key(&uid) } - pub fn get(uid: &Uuid) -> Option { + pub fn get<'jh>(uid: Uuid) -> Option> { if !Self::contains(uid) { return None; } @@ -32,17 +32,17 @@ impl JobCache { Some(JobCacheHolder(lock, uid)) } - pub fn remove(uid: &Uuid) { - JOB_CACHE.write().unwrap().remove(uid); + pub fn remove(uid: Uuid) { + JOB_CACHE.write().unwrap().remove(&uid); } } -pub struct JobCacheHolder<'jm>(pub RwLockReadGuard<'jm, Cache>, pub &'jm Uuid); +pub struct JobCacheHolder<'jh>(pub RwLockReadGuard<'jh, Cache>, pub Uuid); -impl<'jm> Deref for JobCacheHolder<'jm> { +impl<'jh> Deref for JobCacheHolder<'jh> { type Target = JobMeta; fn deref(&self) -> &Self::Target { - self.0.get(self.1).unwrap() + self.0.get(&self.1).unwrap() } } diff --git a/lib/u_lib/src/models/jobs/assigned.rs b/lib/u_lib/src/models/jobs/assigned.rs index 3182796..8893977 100644 --- a/lib/u_lib/src/models/jobs/assigned.rs +++ b/lib/u_lib/src/models/jobs/assigned.rs @@ -80,7 +80,7 @@ impl AssignedJob { pub async fn run(mut self) -> Reportable { use tokio::process::Command; let (argv, _payload) = { - let meta = JobCache::get(&self.job_id).unwrap(); + let meta = JobCache::get(self.job_id).unwrap(); if let Some(ref payload) = meta.payload { let extracted_payload = match TempFile::write_exec(payload) { Ok(p) => p,