From 5df2b09ceb1880414eb6699b6785376f80a3c26f Mon Sep 17 00:00:00 2001 From: plazmoid Date: Sat, 18 Dec 2021 03:42:50 +0500 Subject: [PATCH] IT WORKS --- bin/u_panel/Cargo.toml | 7 +- bin/u_panel/src/argparse.rs | 10 +- bin/u_panel/src/main.rs | 5 +- bin/u_panel/src/tui/mod.rs | 154 ++++++++++++++++++------ bin/u_panel/src/tui/retval.rs | 31 ++--- bin/u_panel/src/tui/utils.rs | 33 +++++ bin/u_panel/src/tui/windows/confirm.rs | 15 ++- bin/u_panel/src/tui/windows/main_wnd.rs | 14 ++- bin/u_panel/src/tui/windows/mod.rs | 71 +++++------ scripts/build_musl_libs.sh | 18 +-- 10 files changed, 242 insertions(+), 116 deletions(-) diff --git a/bin/u_panel/Cargo.toml b/bin/u_panel/Cargo.toml index 924ac7c..4fb5761 100644 --- a/bin/u_panel/Cargo.toml +++ b/bin/u_panel/Cargo.toml @@ -14,7 +14,7 @@ env_logger = "0.7.1" uuid = "0.6.5" serde_json = "1.0.4" serde = { version = "1.0.114", features = ["derive"] } -tokio = "1.11.0" +tokio = { version = "1.11.0", features = ["rt", "rt-multi-thread"] } # be = { version = "*", path = "./be" } u_lib = { version = "*", path = "../../lib/u_lib" } tui = { version = "0.16", default-features = false, features = ['crossterm'] } @@ -24,3 +24,8 @@ strum = { version = "0.22.0", features = ["derive"] } async-trait = "0.1.51" once_cell = "1.8.0" crossbeam = "0.8.1" +async-channel = "1.6.1" +tracing = "0.1.29" +tracing-subscriber = { version = "0.3.3", features = ["env-filter"]} +signal-hook = "0.3.12" +tracing-appender = "0.2.0" diff --git a/bin/u_panel/src/argparse.rs b/bin/u_panel/src/argparse.rs index 3ee527a..651e50d 100644 --- a/bin/u_panel/src/argparse.rs +++ b/bin/u_panel/src/argparse.rs @@ -17,7 +17,13 @@ enum Cmd { Agents(LD), Jobs(JobALD), Map(JobMapALD), - TUI, + TUI(TUIArgs), +} + +#[derive(StructOpt, Debug)] +pub struct TUIArgs { + #[structopt(long)] + pub nogui: bool, } #[derive(StructOpt, Debug)] @@ -126,7 +132,7 @@ 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::TUI => crate::tui::init_tui() + Cmd::TUI(args) => crate::tui::init_tui(&args) .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 29a1275..63e9e06 100644 --- a/bin/u_panel/src/main.rs +++ b/bin/u_panel/src/main.rs @@ -4,6 +4,9 @@ mod tui; #[macro_use] extern crate async_trait; +#[macro_use] +extern crate tracing; + use argparse::{process_cmd, Args}; use once_cell::sync::Lazy; use std::env; @@ -17,7 +20,7 @@ pub static CLIENT: Lazy = Lazy::new(|| { ClientHandler::new(None).password(token.clone()) }); -#[tokio::main] +#[tokio::main(flavor = "multi_thread")] async fn main() { init_env(); let args: Args = Args::from_args(); diff --git a/bin/u_panel/src/tui/mod.rs b/bin/u_panel/src/tui/mod.rs index 4c414f2..5cd9942 100644 --- a/bin/u_panel/src/tui/mod.rs +++ b/bin/u_panel/src/tui/mod.rs @@ -3,6 +3,7 @@ mod retval; mod utils; mod windows; +use crate::argparse::TUIArgs; use anyhow::Result as AResult; use backtrace::Backtrace; use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}; @@ -12,15 +13,17 @@ use crossterm::terminal::{ }; use once_cell::sync::Lazy; use retval::{RetVal, ReturnValue}; -use std::panic::set_hook; -use std::process::exit; use std::{ + env, io::{stdout, Stdout}, + panic::set_hook, + process::exit, + sync::atomic::{AtomicBool, Ordering}, thread, time::Duration, }; use tui::{backend::CrosstermBackend, Terminal}; -use utils::Channel; +use utils::{AsyncChannel, Channel}; use windows::{MainWnd, SharedWnd, WindowsHandler, WndId}; pub type Backend = CrosstermBackend; @@ -30,9 +33,12 @@ const EVENT_GEN_PERIOD: Duration = Duration::from_millis(120); static GENERAL_EVENT_CHANNEL: Lazy> = Lazy::new(|| Channel::new()); +static ACTIVE_LOOP: AtomicBool = AtomicBool::new(true); + enum GEvent { CreateWnd(SharedWnd), CloseWnd { wid: WndId, force: bool }, + Exit, Key(KeyCode), Tick, } @@ -42,63 +48,139 @@ fn get_terminal() -> AResult> { Ok(Terminal::new(backend)?) } -pub async fn init_tui() -> AResult<()> { - //TODO: fix this - set_hook(Box::new(|p| { - teardown().unwrap(); - get_terminal().unwrap().show_cursor().unwrap(); - eprintln!("{}\n{:?}", p, Backtrace::new()); - exit(254); - })); - if let Err(e) = init().await { - teardown()?; - return Err(e); - } - Ok(()) -} +pub async fn init_tui(args: &TUIArgs) -> AResult<()> { + init_logger(); + info!("Initializing u_panel"); -async fn init() -> AResult<()> { - enable_raw_mode()?; - execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?; + let gui = !args.nogui; + init_signal_handlers(gui); + init_panic_handler(gui); + term_setup(gui)?; + info!("Starting loop"); + + let result = init_loop(args).await; + info!("Exiting"); + term_teardown(gui)?; + result +} +async fn init_loop(args: &TUIArgs) -> AResult<()> { + let gui = !args.nogui; + if gui { + WindowsHandler::lock().await.clear()?; + } WindowsHandler::lock().await.push_new::().await; - thread::spawn(move || loop { - if event::poll(EVENT_GEN_PERIOD).unwrap() { - match event::read().unwrap() { - Event::Key(key) => GENERAL_EVENT_CHANNEL.send(GEvent::Key(key.code)), - _ => (), + thread::spawn(move || { + while is_running() { + if event::poll(EVENT_GEN_PERIOD).unwrap() { + match event::read().unwrap() { + Event::Key(key) => GENERAL_EVENT_CHANNEL.send(GEvent::Key(key.code)), + _ => (), + } + } else { + GENERAL_EVENT_CHANNEL.send(GEvent::Tick) } - } else { - GENERAL_EVENT_CHANNEL.send(GEvent::Tick) } }); - WindowsHandler::lock().await.clear()?; - - loop { + while is_running() { match GENERAL_EVENT_CHANNEL.recv() { GEvent::Tick => { let mut wh = WindowsHandler::lock().await; wh.update().await?; - wh.draw().await?; + if gui { + wh.draw().await?; + } } GEvent::CloseWnd { wid, force } => { - let mut wh = WindowsHandler::lock().await; - wh.close(wid, force).await; + WindowsHandler::lock().await.close(wid, force).await; } GEvent::Key(key) => { - WindowsHandler::lock().await.send_handle_kbd(key); + info!(?key, "pressed"); + if let KeyCode::Char('q') = key { + break_global(); + } else { + WindowsHandler::lock().await.send_handle_kbd(key).await; + } } GEvent::CreateWnd(wnd) => { WindowsHandler::lock().await.push_dyn(wnd).await; } + GEvent::Exit => { + break_global(); + } } } + Ok(()) +} + +#[inline] +fn is_running() -> bool { + ACTIVE_LOOP.load(Ordering::Relaxed) +} + +fn break_global() { + ACTIVE_LOOP.store(false, Ordering::Relaxed); } -fn teardown() -> AResult<()> { +fn init_signal_handlers(gui: bool) { + use signal_hook::{ + consts::{SIGINT, SIGTERM}, + iterator::Signals, + }; + + thread::spawn(move || { + let mut signals = Signals::new(&[SIGINT, SIGTERM]).unwrap(); + for sig in signals.forever() { + match sig { + SIGINT => break_global(), + SIGTERM => { + break_global(); + term_teardown(gui).ok(); + exit(3); + } + _ => (), + } + } + }); +} + +fn init_logger() { + use tracing_appender::rolling::{RollingFileAppender, Rotation}; + use tracing_subscriber::EnvFilter; + + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "info") + } + + tracing_subscriber::fmt::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_writer(|| RollingFileAppender::new(Rotation::NEVER, ".", "u_panel.log")) + .init(); +} + +fn init_panic_handler(gui: bool) { + set_hook(Box::new(move |panic_info| { + term_teardown(gui).ok(); + eprintln!("{}\n{:?}", panic_info, Backtrace::new()); + exit(254); + })); +} + +fn term_setup(gui: bool) -> AResult<()> { + enable_raw_mode()?; + if gui { + execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?; + } + Ok(()) +} + +fn term_teardown(gui: bool) -> AResult<()> { disable_raw_mode()?; - execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture)?; + if gui { + get_terminal()?.show_cursor()?; + execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture)?; + } Ok(()) } diff --git a/bin/u_panel/src/tui/retval.rs b/bin/u_panel/src/tui/retval.rs index 6d470b7..3874dfc 100644 --- a/bin/u_panel/src/tui/retval.rs +++ b/bin/u_panel/src/tui/retval.rs @@ -1,42 +1,25 @@ +use crate::tui::Channel; 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)); +static RETVAL: Lazy> = Lazy::new(|| Channel::new()); pub struct ReturnValue; impl ReturnValue { - pub async fn set(t: RV) { - *LAST_RETVAL.lock().await = Some(t); + pub fn set(t: RV) { + RETVAL.send(t); } - pub async fn get() -> Box { - ReturnValue - .await + pub fn get() -> Box { + RETVAL + .recv() .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 index f1a1f2c..e8e8542 100644 --- a/bin/u_panel/src/tui/utils.rs +++ b/bin/u_panel/src/tui/utils.rs @@ -1,3 +1,6 @@ +use async_channel::{ + unbounded as unbounded_async, Receiver as AsyncReceiver, Sender as AsyncSender, +}; use crossbeam::channel::{unbounded, Receiver, Sender}; pub struct Channel { @@ -29,3 +32,33 @@ impl Default for Channel { Channel::new() } } + +pub struct AsyncChannel { + pub tx: AsyncSender, + pub rx: AsyncReceiver, +} + +impl AsyncChannel { + pub fn new() -> Self { + let (tx, rx) = unbounded_async::(); + Self { tx, rx } + } + + pub async fn send(&self, msg: T) { + self.tx.send(msg).await.unwrap() + } + + pub async fn recv(&self) -> T { + self.rx.recv().await.unwrap() + } + + pub fn is_empty(&self) -> bool { + self.rx.is_empty() + } +} + +impl Default for AsyncChannel { + fn default() -> Self { + AsyncChannel::new() + } +} diff --git a/bin/u_panel/src/tui/windows/confirm.rs b/bin/u_panel/src/tui/windows/confirm.rs index 41ca76e..8272ffa 100644 --- a/bin/u_panel/src/tui/windows/confirm.rs +++ b/bin/u_panel/src/tui/windows/confirm.rs @@ -1,7 +1,10 @@ use super::{Window, WndId}; -use crate::tui::{Frame, GEvent, RetVal, ReturnValue, GENERAL_EVENT_CHANNEL}; +use crate::tui::{ + windows::WndEvent, AsyncChannel, Frame, GEvent, RetVal, ReturnValue, GENERAL_EVENT_CHANNEL, +}; use anyhow::Result as AResult; use crossterm::event::KeyCode; +use once_cell::sync::OnceCell; use std::any::Any; use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use tui::style::{Modifier, Style}; @@ -79,7 +82,6 @@ impl Window for ConfirmWnd { 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) @@ -98,12 +100,17 @@ impl Window for ConfirmWnd { List::new(options).highlight_style(Style::default().add_modifier(Modifier::BOLD)); f.render_stateful_widget(list, chunks[1], &mut self.state); } + + fn get_chan(&self) -> &'static AsyncChannel { + static EV_CHAN: OnceCell> = OnceCell::new(); + EV_CHAN.get_or_init(|| AsyncChannel::new()) + } } -pub async fn confirm_wnd(msg: impl Into) -> bool { +pub async fn confirm_wnd(msg: impl Into) -> String { let wnd = ConfirmWnd::new_yn(msg.into(), None); GENERAL_EVENT_CHANNEL.send(GEvent::CreateWnd(wnd.into_shared())); - *ReturnValue::get().await + *ReturnValue::get() } fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { diff --git a/bin/u_panel/src/tui/windows/main_wnd.rs b/bin/u_panel/src/tui/windows/main_wnd.rs index e595e1f..1a7c40a 100644 --- a/bin/u_panel/src/tui/windows/main_wnd.rs +++ b/bin/u_panel/src/tui/windows/main_wnd.rs @@ -1,7 +1,12 @@ use super::{confirm_wnd, Window}; -use crate::tui::{impls::CRUD, windows::WndId, Frame, RetVal}; +use crate::tui::{ + impls::CRUD, + windows::{WndEvent, WndId}, + AsyncChannel, Frame, RetVal, +}; use anyhow::Result as AResult; use crossterm::event::KeyCode; +use once_cell::sync::OnceCell; use std::any::Any; use std::{fmt::Display, str::FromStr}; use strum::VariantNames; @@ -256,7 +261,7 @@ impl Window for MainWnd { KeyCode::Up => self.on_up(), KeyCode::Down => self.on_down(), KeyCode::Delete => { - if confirm_wnd("Delete?").await { + if &confirm_wnd("Delete?").await == "Yes" { self.delete().await; self.update_tab(); } @@ -310,6 +315,11 @@ impl Window for MainWnd { .highlight_style(Style::default().add_modifier(Modifier::BOLD)); f.render_stateful_widget(list, chunks[1], self.tab_list_state()); } + + fn get_chan(&self) -> &'static AsyncChannel { + static EV_CHAN: OnceCell> = OnceCell::new(); + EV_CHAN.get_or_init(|| AsyncChannel::new()) + } } impl RetVal for MainWnd { diff --git a/bin/u_panel/src/tui/windows/mod.rs b/bin/u_panel/src/tui/windows/mod.rs index 87c430b..c4b18a5 100644 --- a/bin/u_panel/src/tui/windows/mod.rs +++ b/bin/u_panel/src/tui/windows/mod.rs @@ -1,18 +1,17 @@ mod confirm; mod main_wnd; +use async_channel::Sender; pub use confirm::{confirm_wnd, ConfirmWnd}; -use crossbeam::channel::Sender; pub use main_wnd::MainWnd; use crate::tui::{ - get_terminal, impls::Id, teardown, utils::Channel, Backend, Frame, GEvent, RetVal, ReturnValue, + get_terminal, impls::Id, AsyncChannel, Backend, Frame, GEvent, RetVal, ReturnValue, GENERAL_EVENT_CHANNEL, }; use anyhow::Result as AResult; use crossterm::event::KeyCode; use once_cell::sync::{Lazy, OnceCell}; use std::collections::BTreeMap; -use std::process::exit; use std::sync::Arc; use std::sync::{Mutex as StdMutex, MutexGuard as StdMutexGuard}; use tokio::sync::{Mutex, MutexGuard}; @@ -24,11 +23,10 @@ static WINDOWS: Lazy>> = static LAST_WND_ID: OnceCell> = OnceCell::new(); -static WND_EVENT_CHANNEL: Lazy> = Lazy::new(|| Channel::new()); - pub type SharedWnd = Arc>; -enum WndEvent { +#[derive(Clone, Debug)] +pub enum WndEvent { Key(KeyCode), } @@ -36,7 +34,7 @@ enum WndEvent { pub struct WndId(u64); impl WndId { - pub fn last_wid() -> StdMutexGuard<'static, WndId> { + fn last_wid() -> StdMutexGuard<'static, WndId> { LAST_WND_ID .get_or_init(|| StdMutex::new(WndId(0))) .lock() @@ -54,25 +52,25 @@ impl Default for WndId { #[async_trait] 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 handle_event(&mut self, ev: WndEvent) { + match ev { + WndEvent::Key(k) => { + self.handle_kbd(k).await.unwrap(); } } } async fn close(&mut self, force: bool) { let rv = self.retval(); - ReturnValue::set(rv).await; + ReturnValue::set(rv); GENERAL_EVENT_CHANNEL.send(GEvent::CloseWnd { wid: self.id(), force, }); } + fn get_chan(&self) -> &'static AsyncChannel; + fn into_shared(self) -> SharedWnd where Self: Sized + 'static, @@ -90,30 +88,35 @@ pub trait Window: Id + RetVal + Send { } pub struct WndLoop { - upd_tx: Sender<()>, + upd_tx: Sender, updater: JoinHandle<()>, window: SharedWnd, } impl WndLoop { - pub fn with_wnd(window: SharedWnd) -> Self { - let Channel { tx, rx } = Channel::new(); + pub async fn with_wnd(window: SharedWnd) -> Self { let wnd = window.clone(); + let AsyncChannel { tx, rx } = wnd.lock().await.get_chan(); let wnd_loop = async move { loop { - rx.recv().unwrap(); - wnd.lock().await.check_wnd_events().await; + match rx.recv().await { + Ok(ev) => wnd.lock().await.handle_event(ev).await, + Err(_) => break, + } } }; WndLoop { - upd_tx: tx, + upd_tx: tx.clone(), window, updater: task::spawn(wnd_loop), } } - pub fn send_update(&self) { - self.upd_tx.send(()).unwrap(); + pub async fn send(&self, ev: WndEvent) { + let wnd_id = self.window.lock().await.id(); + let event = ev.clone(); + debug!(?event, ?wnd_id, "sending"); + self.upd_tx.send(ev).await.expect("big zhopa"); } } @@ -151,7 +154,8 @@ impl WindowsHandler { 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())); + self.queue + .insert(wid, WndLoop::with_wnd(window.clone()).await); window } @@ -160,19 +164,12 @@ impl WindowsHandler { Ok(()) } - pub fn show_cursor(&mut self) -> AResult<()> { - self.term.show_cursor()?; - Ok(()) - } - 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 + self.queue.keys().collect::>() } else { - vec![*WndId::last_wid()] + vec![self.queue.keys().last().unwrap()] }; for wid in wids_to_redraw { let mut wnd_locked = match self.queue.get(&wid) { @@ -202,19 +199,17 @@ impl WindowsHandler { } if self.queue.is_empty() { - self.show_cursor().unwrap(); - teardown().unwrap(); - exit(0); + GENERAL_EVENT_CHANNEL.send(GEvent::Exit); } } - pub fn send_handle_kbd(&self, k: KeyCode) { - WND_EVENT_CHANNEL.send(WndEvent::Key(k)); + pub async fn send_handle_kbd(&self, k: KeyCode) { + let current_wnd = self.get_last_wnd(); + current_wnd.send(WndEvent::Key(k)).await; } pub async fn update(&mut self) -> AResult<()> { let current_wnd = self.get_last_wnd(); - current_wnd.send_update(); current_wnd.window.lock().await.handle_update().await?; Ok(()) } diff --git a/scripts/build_musl_libs.sh b/scripts/build_musl_libs.sh index b63211d..8c688a5 100755 --- a/scripts/build_musl_libs.sh +++ b/scripts/build_musl_libs.sh @@ -5,11 +5,13 @@ ARGS=$@ STATIC_LIBS=./static DOCKER_EXCHG=/musl-share IMAGE=unki/musllibs -mkdir -p $STATIC_LIBS -cd $ROOTDIR/images && docker build -t $IMAGE . -f musl-libs.Dockerfile -docker run \ - -v $ROOTDIR/$STATIC_LIBS:$DOCKER_EXCHG \ - -w /volume \ - -it \ - $IMAGE \ - bash -c "[[ \$(ls -A $DOCKER_EXCHG) ]] || cp /musl/. $DOCKER_EXCHG -r" +if [[ ! -d ./static ]]; then + mkdir $STATIC_LIBS + cd $ROOTDIR/images && docker build -t $IMAGE . -f musl-libs.Dockerfile + docker run \ + -v $ROOTDIR/$STATIC_LIBS:$DOCKER_EXCHG \ + -w /volume \ + -it \ + $IMAGE \ + bash -c "[[ \$(ls -A $DOCKER_EXCHG) ]] || cp /musl/. $DOCKER_EXCHG -r" +fi