Osuation
This commit is contained in:
parent
16545a4dd1
commit
b232bebed5
7 changed files with 38 additions and 202 deletions
29
src/app.rs
29
src/app.rs
|
@ -1,29 +0,0 @@
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::config::ConfigWatcher;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait AnyApp {
|
|
||||||
fn say_hello(&self) {
|
|
||||||
debug!("hello from app");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An App is usually associated with a particular separate service or piece of functionality.
|
|
||||||
pub struct App<A: AppI> {
|
|
||||||
inner: A,
|
|
||||||
// config_watcher: ConfigWatcher<A::Config>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: AppI> App<A> {
|
|
||||||
pub fn new(app: A) -> Self {
|
|
||||||
App { inner: app }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The interface that anything that wants to become an App must implement
|
|
||||||
pub trait AppI: Sized {
|
|
||||||
type Config;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: AppI> AnyApp for App<A> {}
|
|
|
@ -1,25 +0,0 @@
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use notify::{DebouncedEvent, RecursiveMode, Watcher};
|
|
||||||
use xdg::BaseDirectories;
|
|
||||||
|
|
||||||
pub struct ConfigWatcher<C> {
|
|
||||||
_ty: PhantomData<C>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn watch_config() -> Result<()> {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
|
|
||||||
let xdg = BaseDirectories::new()?;
|
|
||||||
let config_home = xdg.get_config_home();
|
|
||||||
let mut watcher = notify::watcher(tx, Duration::from_secs(5))?;
|
|
||||||
watcher.watch(config_home, RecursiveMode::Recursive)?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let evt = rx.recv()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
pub enum Event {}
|
|
|
@ -1,21 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use lettre::SmtpClient;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::app::AppI;
|
|
||||||
|
|
||||||
pub struct MailApp {
|
|
||||||
client: SmtpClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppI for MailApp {
|
|
||||||
type Config = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MailApp {
|
|
||||||
pub fn new(domain: impl AsRef<str>) -> Result<Self> {
|
|
||||||
let client = SmtpClient::new_simple(domain.as_ref())?;
|
|
||||||
Ok(MailApp { client })
|
|
||||||
}
|
|
||||||
}
|
|
62
src/main.rs
62
src/main.rs
|
@ -1,65 +1,23 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate async_trait;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate cfg_if;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate crossterm;
|
extern crate crossterm;
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate pin_project;
|
|
||||||
|
|
||||||
mod app;
|
|
||||||
mod config;
|
|
||||||
mod event;
|
|
||||||
mod mailapp;
|
|
||||||
mod panorama;
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::runtime::Runtime;
|
use lettre::SmtpClient;
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use crate::panorama::Panorama;
|
type ExitSender = oneshot::Sender<()>;
|
||||||
use crate::config::watch_config;
|
|
||||||
use crate::ui::Ui;
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
#[tokio::main]
|
||||||
fern::Dispatch::new()
|
async fn main() -> Result<()> {
|
||||||
.format(|out, message, record| {
|
SmtpClient::new_simple("");
|
||||||
out.finish(format_args!(
|
|
||||||
"{}[{}][{}] {}",
|
|
||||||
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
|
||||||
record.target(),
|
|
||||||
record.level(),
|
|
||||||
message
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.level(log::LevelFilter::Debug)
|
|
||||||
.chain(fern::log_file("output.log")?)
|
|
||||||
.apply()?;
|
|
||||||
|
|
||||||
let runtime = Runtime::new()?;
|
let (exit_tx, exit_rx) = oneshot::channel::<()>();
|
||||||
thread::spawn(move || {
|
|
||||||
let panorama = Panorama::new().unwrap();
|
|
||||||
runtime.block_on(panorama.run());
|
|
||||||
});
|
|
||||||
|
|
||||||
let stdout = io::stdout();
|
let stdout = std::io::stdout();
|
||||||
let (evts_tx, evts_rx) = channel();
|
tokio::spawn(ui::run_ui(stdout, exit_tx));
|
||||||
|
|
||||||
// spawn a thread for listening to configuration changes
|
|
||||||
thread::spawn(move || {
|
|
||||||
watch_config();
|
|
||||||
});
|
|
||||||
info!("poggers");
|
|
||||||
|
|
||||||
// run the ui on the main thread
|
|
||||||
let ui = Ui::init(stdout, evts_rx)?;
|
|
||||||
ui.run()?;
|
|
||||||
|
|
||||||
|
exit_rx.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::mailapp::MailApp;
|
|
||||||
use crate::app::{AnyApp, App};
|
|
||||||
|
|
||||||
pub struct Panorama {
|
|
||||||
apps: Vec<Box<dyn AnyApp>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panorama {
|
|
||||||
pub fn new() -> Result<Panorama> {
|
|
||||||
let mut apps = Vec::new();
|
|
||||||
|
|
||||||
let mail = MailApp::new("mzhang.io")?;
|
|
||||||
let mail = Box::new(App::new(mail)) as Box<dyn AnyApp>;
|
|
||||||
apps.push(mail);
|
|
||||||
|
|
||||||
Ok(Panorama {
|
|
||||||
apps,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&self) -> Result<()> {
|
|
||||||
debug!("starting all apps...");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
self.apps.iter().map(|app| {
|
|
||||||
app.say_hello();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
67
src/ui.rs
67
src/ui.rs
|
@ -1,60 +1,51 @@
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use chrono::Local;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event,
|
cursor,
|
||||||
terminal::{self, EnterAlternateScreen},
|
event::{self, Event, KeyCode, KeyEvent},
|
||||||
|
style, terminal,
|
||||||
};
|
};
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
use crate::event::Event;
|
use crate::ExitSender;
|
||||||
|
|
||||||
pub struct Ui<S: Write> {
|
const FRAME: Duration = Duration::from_millis(16);
|
||||||
screen: S,
|
|
||||||
evts: Receiver<Event>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Write> Ui<S> {
|
pub async fn run_ui(mut w: impl Write, exit: ExitSender) -> Result<()> {
|
||||||
pub fn init(mut screen: S, evts: Receiver<Event>) -> Result<Self> {
|
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
|
||||||
execute!(screen, EnterAlternateScreen)?;
|
|
||||||
terminal::enable_raw_mode()?;
|
terminal::enable_raw_mode()?;
|
||||||
|
|
||||||
Ok(Ui { screen, evts })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(mut self) -> Result<()> {
|
|
||||||
use crossterm::event::{Event, KeyCode, KeyEvent};
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// check for new events
|
execute!(w, cursor::MoveTo(0, 0))?;
|
||||||
use std::sync::mpsc::TryRecvError;
|
|
||||||
match self.evts.try_recv() {
|
|
||||||
Ok(evt) => {}
|
|
||||||
Err(TryRecvError::Empty) => {} // skip
|
|
||||||
Err(TryRecvError::Disconnected) => todo!("impossible?"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// read events from the terminal
|
let now = Local::now();
|
||||||
|
println!("shiet {}", now);
|
||||||
|
|
||||||
|
// approx 60fps
|
||||||
|
time::sleep(FRAME).await;
|
||||||
|
|
||||||
|
if event::poll(FRAME)? {
|
||||||
match event::read()? {
|
match event::read()? {
|
||||||
Event::Key(KeyEvent {
|
Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char('q'),
|
code: KeyCode::Char('q'),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => break,
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execute!(
|
||||||
|
w,
|
||||||
|
style::ResetColor,
|
||||||
|
cursor::Show,
|
||||||
|
terminal::LeaveAlternateScreen
|
||||||
|
)?;
|
||||||
|
terminal::disable_raw_mode()?;
|
||||||
|
|
||||||
|
exit.send(()).expect("fake news?");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Write> Drop for Ui<S> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
use crossterm::{cursor::Show, style::ResetColor, terminal::LeaveAlternateScreen};
|
|
||||||
|
|
||||||
execute!(self.screen, ResetColor, Show, LeaveAlternateScreen,).unwrap();
|
|
||||||
terminal::disable_raw_mode().unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue