what the FUCK
This commit is contained in:
commit
16545a4dd1
12 changed files with 1596 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
1315
Cargo.lock
generated
Normal file
1315
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
23
Cargo.toml
Normal file
23
Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "panorama"
|
||||
version = "0.1.0"
|
||||
authors = ["Michael Zhang <mail@mzhang.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.38"
|
||||
async-trait = "0.1.42"
|
||||
cfg-if = "1.0.0"
|
||||
chrono = "0.4.19"
|
||||
crossterm = "0.19.0"
|
||||
fern = "0.6.0"
|
||||
futures = "0.3.12"
|
||||
lettre = "0.9.5"
|
||||
log = "0.4.14"
|
||||
notify = "4.0.15"
|
||||
pin-project = "1.0.4"
|
||||
tokio = { version = "1.1.1", features = ["full"] }
|
||||
tokio-rustls = "0.22.0"
|
||||
tokio-util = { version = "0.6.3", features = ["full"] }
|
||||
webpki-roots = "0.21.0"
|
||||
xdg = "2.2.0"
|
12
README.md
Normal file
12
README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
panorama
|
||||
========
|
||||
|
||||
Panorama is a terminal Personal Information Manager (PIM).
|
||||
|
||||
Goals:
|
||||
|
||||
- Handles email, calendar, and address books using open standards.
|
||||
- Unified "feed" that any app can submit to.
|
||||
- Hot-reload on-disk config.
|
||||
- Submit notifications to gotify-shaped notification servers.
|
||||
- Never have to actually close the application.
|
7
output.log
Normal file
7
output.log
Normal file
|
@ -0,0 +1,7 @@
|
|||
[2021-02-12][01:42:31][panorama][INFO] poggers
|
||||
[2021-02-12][01:42:36][panorama][INFO] poggers
|
||||
[2021-02-12][01:56:24][panorama][INFO] poggers
|
||||
[2021-02-12][01:56:50][panorama][INFO] poggers
|
||||
[2021-02-12][01:56:50][panorama::panorama][DEBUG] starting all apps...
|
||||
[2021-02-12][02:04:53][panorama][INFO] poggers
|
||||
[2021-02-12][02:04:53][panorama::panorama][DEBUG] starting all apps...
|
29
src/app.rs
Normal file
29
src/app.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
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> {}
|
25
src/config.rs
Normal file
25
src/config.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
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
src/event.rs
Normal file
1
src/event.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub enum Event {}
|
21
src/mailapp.rs
Normal file
21
src/mailapp.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
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 })
|
||||
}
|
||||
}
|
65
src/main.rs
Normal file
65
src/main.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
#[macro_use]
|
||||
extern crate async_trait;
|
||||
#[macro_use]
|
||||
extern crate cfg_if;
|
||||
#[macro_use]
|
||||
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;
|
||||
|
||||
use std::io;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::panorama::Panorama;
|
||||
use crate::config::watch_config;
|
||||
use crate::ui::Ui;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
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()?;
|
||||
thread::spawn(move || {
|
||||
let panorama = Panorama::new().unwrap();
|
||||
runtime.block_on(panorama.run());
|
||||
});
|
||||
|
||||
let stdout = io::stdout();
|
||||
let (evts_tx, evts_rx) = channel();
|
||||
|
||||
// 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()?;
|
||||
|
||||
Ok(())
|
||||
}
|
37
src/panorama.rs
Normal file
37
src/panorama.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
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(())
|
||||
}
|
||||
}
|
60
src/ui.rs
Normal file
60
src/ui.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use std::io::Write;
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{
|
||||
event,
|
||||
terminal::{self, EnterAlternateScreen},
|
||||
};
|
||||
|
||||
use crate::event::Event;
|
||||
|
||||
pub struct Ui<S: Write> {
|
||||
screen: S,
|
||||
evts: Receiver<Event>,
|
||||
}
|
||||
|
||||
impl<S: Write> Ui<S> {
|
||||
pub fn init(mut screen: S, evts: Receiver<Event>) -> Result<Self> {
|
||||
execute!(screen, EnterAlternateScreen)?;
|
||||
terminal::enable_raw_mode()?;
|
||||
|
||||
Ok(Ui { screen, evts })
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> Result<()> {
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent};
|
||||
|
||||
loop {
|
||||
// check for new events
|
||||
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
|
||||
match event::read()? {
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('q'),
|
||||
..
|
||||
}) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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