use std::mem; use std::pin::Pin; use std::task::{Context, Poll, Waker}; use std::sync::{Arc, Mutex, MutexGuard}; use std::borrow::BorrowMut; use futures::prelude::*; use futures_signals::signal::{Mutable, Signal, MutableSignalCloned, MutableLockMut}; use termion::event::Key; use crate::input::Inputs; use crate::session::Session; use crate::commands::{CommandParser, Commands}; use smol::future::FutureExt; use slog::{ Logger, Drain, Level, Record, OwnedKVList, }; use tui::text::{Span, Spans}; use tui::style::{Style, Color}; #[derive(PartialEq, Eq, Debug, Clone)] pub enum Window { Main, Help, } /// Application state struct pub struct Sute<'a, S> { // TODO: BE SMART. Inputs change the state, resize signals change the state, futures completing // change the state. state: Mutable, signal: S, inputs: Inputs, session: Option, log: Logger, cmds: CommandParser<'a>, drain: Arc>, future: Option>>>, } impl<'a, S: Unpin> Sute<'a, S> { pub fn new(signal: S, log: Logger, drain: Arc>, session: Option) -> Self { let inputs = Inputs::new(); let state = Mutable::new(SuteState::new()); let cmds = CommandParser::new(); let future = None; Self { state, signal, inputs, session, log, cmds, drain, future } } fn run_cmd(&mut self, cmdline: String) { let words = cmdline.split_ascii_whitespace().map(|s| s.to_string()); match self.cmds.parse(words) { Ok(matches) => { match matches.subcommand() { Some(("quit", _)) => self.state.lock_mut().running = false, Some(("authenticate", m)) => { let u = m.value_of("user"); let p = m.value_of("pass"); if u.is_none() || p.is_none() { error!(self.log, "authenticate "); } else { if let Some(mut api) = self.session.as_ref().map(|s| s.bootstrap.clone()) { let log2 = self.log.clone(); info!(self.log, "Going for an auth trip"); let u = u.unwrap().to_string(); let p = p.unwrap().to_string(); let f = api.authentication().then(move |mut auth| { auth.authenticate(u, p).then(move |res| { info!(log2, "Authentication result: {}", res); futures::future::ready(()) }) }); let old_f = self.future.replace(Box::pin(f)); if !old_f.is_none() { warn!(self.log, "Dropping a future"); } } else { error!(self.log, "No connection, can't authenticate!"); } } } Some(("minfo", m)) => { if let Some(uid) = m.value_of("uid") { let uid = uid.to_string(); if let Some(mut api) = self.session.as_ref().map(|s| s.bootstrap.clone()) { let log = self.log.clone(); let machines = api.machines(); let machine = machines.then(|mut machines| { machines.get_machine(uid) }); let f = machine.then(move |res| { res.as_ref().unwrap().get_info().map(move |info| { info!(log, "Yay, stuff! {:?}", info); }) }); let old_f = self.future.replace(Box::pin(f.map(|_| ()))); if !old_f.is_none() { warn!(self.log, "Dropping a future"); } } } else { error!(self.log, "minfo ") } } Some(("list", _m)) => { if let Some(mut machines) = self.session.as_mut().map(|s| s.bootstrap.machines()) { let log = self.log.clone(); let f = async move { let machlist = machines.await.list_machines().await; info!(log, "Listing all machines"); for m in machlist { m.get_info().map(|info| { info!(log, "A machine: {:?}", info); }).await; } info!(log, "Listed all machines"); }; let old_f = self.future.replace(Box::pin(f.map(|_| ()))); if !old_f.is_none() { warn!(self.log, "Dropping a future"); } } }, Some(("use", m)) => { if let Some(uid) = m.value_of("uid") { let uid = uid.to_string(); if let Some(mut api) = self.session.as_ref().map(|s| s.bootstrap.clone()) { let log = self.log.clone(); let machines = api.machines(); let machine = machines.then(|mut machines| { machines.get_machine(uid) }); let f = async move { let m = machine.await; if let Some(gb) = m.as_ref().unwrap().use_().await { info!(log, "Was allowed to use the machine, giving it back meow"); gb.give_back().await; } else { info!(log, "Wasn't allowed to use the machine :("); } }; let old_f = self.future.replace(Box::pin(f.map(|_| ()))); if !old_f.is_none() { warn!(self.log, "Dropping a future"); } } } } Some((s, m)) => info!(self.log, "Got Command {} with params {:?}", s, m), None => error!(self.log, "No command provided."), } }, Err(e) => { error!(self.log, "{}", e); } } } fn connect(&mut self, params: String) { info!(self.log, "Called connect with {}", params); } fn handle_resize(&mut self, new_size: (u16,u16)) { trace!(self.log, "Locking in handle_resize"); self.state.lock_mut().size = new_size; } pub fn get_state(&self) -> SuteState { self.state.get_cloned() } // We can make this more efficient by not cloning. But who cares? pub fn signal(&self) -> impl Signal { self.state.signal_cloned() } pub fn handle_key(&mut self, key: Key) { trace!(self.log, "Locking in handle_key"); match key { Key::Char('\n') => { let mut state = self.state.lock_mut(); let cmd = mem::replace(&mut state.cmd_line, String::new()); // drop the mutably borrowed state here so we can mutably re-borrow self afterwards mem::drop(state); self.run_cmd(cmd); }, Key::Char(c) => { self.state.lock_mut().cmd_line.push(c); }, Key::Backspace => { self.state.lock_mut().cmd_line.pop(); }, _ => { // FIXME: Currently we *must* modify state here otherwise the signal is parked // indefinitely and the system will never update. let mut state = self.state.lock_mut(); state.tick += 1; } } } } impl <'a, S: Unpin + Signal> Future for Sute<'a, S> { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { // Return early if we're not running anymore if ! self.state.lock_ref().running { return Poll::Ready(()); } if let Some(mut f) = self.future.take() { match f.poll(cx) { Poll::Pending => { self.future.replace(Box::pin(f)); }, _ => {}, } } if let Poll::Ready(Some(size)) = Pin::new(&mut self.signal).poll_change(cx) { self.handle_resize(size); } match Pin::new(&mut self.inputs).poll_next(cx) { Poll::Ready(Some(key)) => { self.handle_key(key); }, // If the input closes stop the program Poll::Ready(None) => { trace!(self.log, "Locking in impl Future for Sute"); self.state.lock_mut().running = false; return Poll::Ready(()); }, Poll::Pending => {} } Poll::Pending } } #[derive(Debug, Clone)] pub struct SuteState { pub active_win: Window, pub size: (u16,u16), pub running: bool, pub tick: usize, pub server: Option, pub cmd_line: String, pub tick_c: char, // TODO: Figure out how to put log lines here signaled } impl SuteState { pub fn new() -> Self { Self { active_win: Window::Main, size: (80,20), running: true, tick: 0, server: None, cmd_line: String::new(), tick_c: '|', } } } #[derive(Debug)] pub struct LogDrain<'a> { inner: Mutex>>, } impl<'a> LogDrain<'a> { pub fn new() -> Self { Self { inner: Mutex::new(Vec::new()) } } pub fn get_inner(&self) -> MutexGuard>> { self.inner.lock().unwrap() } } impl<'a> Drain for LogDrain<'a> { type Ok = (); type Err = (); fn log(&self, record: &Record, values: &OwnedKVList) -> Result { let critical_style: Style = Style::default().fg(Color::Magenta); let error_style: Style = Style::default().fg(Color::Red); let warning_style: Style = Style::default().fg(Color::Yellow); let info_style: Style = Style::default().fg(Color::Cyan); let debug_style: Style = Style::default().fg(Color::White); let trace_style: Style = Style::default().fg(Color::Green); let lvlspan = match record.level() { Level::Critical => Span::styled("CRIT", critical_style), Level::Error => Span::styled("ERRO", error_style), Level::Warning => Span::styled("WARN", warning_style), Level::Info => Span::styled("INFO", info_style), Level::Debug => Span::styled("DEBG", debug_style), Level::Trace => Span::styled("TRCE", trace_style), }; let contentspan = Span::from(record.msg().to_string()); { let mut v = self.inner.lock().unwrap(); v.push(Spans::from(vec![ Span::from("["), lvlspan, Span::from("]: "), contentspan, ])); } Ok(()) } }