what the FUCK

This commit is contained in:
Michael Zhang 2021-02-12 02:12:43 -06:00
commit 16545a4dd1
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
12 changed files with 1596 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1315
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

23
Cargo.toml Normal file
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
pub enum Event {}

21
src/mailapp.rs Normal file
View 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
View 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
View 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
View 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();
}
}