From 9116898a21375bbcc076146c16dd84a95e1cfed4 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Tue, 16 Feb 2021 06:34:48 -0600 Subject: [PATCH] start 2nd (cleaner) imap implementation --- .github/workflows/doc.yml | 2 +- .gitignore | 1 + Cargo.lock | 11 --------- Cargo.toml | 14 +++++------ Justfile | 3 +++ src/config.rs | 46 +++++++++++++++++++++++++++++------ src/lib.rs | 4 +-- src/mail/imap2.rs | 51 +++++++++++++++++++++++++++++++++++++-- src/main.rs | 43 ++++++++++++++------------------- src/ui/mod.rs | 5 ---- 10 files changed, 118 insertions(+), 62 deletions(-) diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 79a05ea..690f454 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -26,7 +26,7 @@ jobs: - name: build api docs run: | - cargo doc --workspace --no-deps + cargo doc --workspace --no-deps --document-private-items cp -r target/doc public/api - name: deploy diff --git a/.gitignore b/.gitignore index 7ae2572..ab94559 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.env /output.log /config.toml +/public diff --git a/Cargo.lock b/Cargo.lock index 809e2e6..2092ed2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,15 +232,6 @@ dependencies = [ "ascii_utils", ] -[[package]] -name = "fern" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" -dependencies = [ - "log", -] - [[package]] name = "foreign-types" version = "0.3.2" @@ -654,11 +645,9 @@ dependencies = [ "cfg-if", "chrono", "crossterm", - "fern", "futures", "inotify", "lettre", - "log", "panorama-imap", "pin-project", "rustls-connector", diff --git a/Cargo.toml b/Cargo.toml index ad7a907..0a58e47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,16 +12,17 @@ license = "GPL-3.0-or-later" members = ["imap"] [dependencies] +# log = "0.4.14" +# fern = "0.6.0" 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" -panorama-imap = { path = "imap", version = "0" } +inotify = { version = "0.9.2", features = ["stream"] } lettre = "0.9.5" -log = "0.4.14" +panorama-imap = { path = "imap", version = "0" } pin-project = "1.0.4" rustls-connector = "0.13.1" serde = { version = "1.0.123", features = ["derive"] } @@ -31,12 +32,11 @@ tokio-rustls = "0.22.0" tokio-stream = { version = "0.1.3", features = ["sync"] } tokio-util = { version = "0.6.3", features = ["full"] } toml = "0.5.8" +tracing = "0.1.23" +tracing-appender = "0.1.2" +tracing-subscriber = "0.2.15" webpki-roots = "0.21.0" xdg = "2.2.0" -tracing = "0.1.23" -tracing-subscriber = "0.2.15" -inotify = { version = "0.9.2", features = ["stream"] } -tracing-appender = "0.1.2" [features] clippy = [] diff --git a/Justfile b/Justfile index ae48845..5698236 100644 --- a/Justfile +++ b/Justfile @@ -1,5 +1,8 @@ doc: cargo doc --document-private-items +doc-open: + cargo doc --document-private-items --open + watch: cargo watch -x 'clippy --all --all-features' diff --git a/src/config.rs b/src/config.rs index c520dd9..5089b9c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,14 +2,15 @@ //! //! One of the primary goals of panorama is to be able to always hot-reload configuration files. -use std::fs::File; +use std::fs::{self, File}; use std::io::Read; use std::path::{Path, PathBuf}; -use anyhow::Result; +use anyhow::{Context, Result}; use futures::{future::TryFutureExt, stream::StreamExt}; use inotify::{Inotify, WatchMask}; use tokio::{sync::watch, task::JoinHandle}; +use xdg::BaseDirectories; use crate::report_err; @@ -81,19 +82,39 @@ async fn read_config(path: impl AsRef) -> Result { async fn start_inotify_stream( mut inotify: Inotify, + config_home: impl AsRef, config_tx: watch::Sender, ) -> Result<()> { let mut buffer = vec![0; 1024]; let mut event_stream = inotify.event_stream(&mut buffer)?; + let config_home = config_home.as_ref().to_path_buf(); + let config_path = config_home.join("panorama.toml"); while let Some(v) = event_stream.next().await { - let event = v?; - - debug!("event: {:?}", event); + let event = v.context("event")?; if let Some(name) = event.name { let path = PathBuf::from(name); - let config = read_config(path).await?; + let path_c = config_home + .clone() + .join(path.clone()) + .canonicalize() + .context("osu")?; + if !path_c.exists() { + debug!("path {:?} doesn't exist", path_c); + continue; + } + + // TODO: any better way to do this? + let config_path_c = config_path.canonicalize().context("cfg_path")?; + if config_path_c != path_c { + debug!("did not match {:?} {:?}", config_path_c, path_c); + continue; + } + + debug!("reading config from {:?}", path_c); + let config = read_config(path_c).await.context("read")?; + debug!("sending config {:?}", config); config_tx.send(config)?; } } @@ -105,9 +126,18 @@ async fn start_inotify_stream( /// which is a cloneable receiver of config update events. pub fn spawn_config_watcher_system() -> Result<(JoinHandle<()>, ConfigWatcher)> { let mut inotify = Inotify::init()?; - inotify.add_watch(".", WatchMask::all())?; + + let xdg = BaseDirectories::new()?; + let config_home = xdg.get_config_home().join("panorama"); + if !config_home.exists() { + fs::create_dir_all(&config_home)?; + } + inotify.add_watch(&config_home, WatchMask::all())?; + debug!("watching {:?}", config_home); let (config_tx, config_update) = watch::channel(Config::default()); - let handle = tokio::spawn(start_inotify_stream(inotify, config_tx).unwrap_or_else(report_err)); + let handle = tokio::spawn( + start_inotify_stream(inotify, config_home, config_tx).unwrap_or_else(report_err), + ); Ok((handle, config_update)) } diff --git a/src/lib.rs b/src/lib.rs index 05cb64a..dfe41a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,6 @@ extern crate anyhow; #[macro_use] extern crate crossterm; #[macro_use] -extern crate log; -#[macro_use] extern crate serde; #[macro_use] extern crate tracing; @@ -23,5 +21,5 @@ pub type ExitSender = tokio::sync::mpsc::Sender<()>; /// Consumes any error and dumps it to the logger. pub fn report_err(err: anyhow::Error) { - error!("error: {:?}", err); + error!("error: {}", err); } diff --git a/src/mail/imap2.rs b/src/mail/imap2.rs index 11f882a..49140c4 100644 --- a/src/mail/imap2.rs +++ b/src/mail/imap2.rs @@ -1,9 +1,56 @@ // let's try this again -use anyhow::Result; +use std::sync::Arc; -use crate::config::ImapConfig; +use anyhow::Result; +use futures::stream::Stream; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::TcpStream, +}; +use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector}; + +use crate::config::{ImapConfig, TlsMethod}; pub async fn open_imap_connection(config: ImapConfig) -> Result<()> { + let server = config.server.as_str(); + let port = config.port; + + let stream = TcpStream::connect((server, port)).await?; + + match config.tls { + TlsMethod::Off => begin_authentication(config, stream).await, + TlsMethod::On => { + let stream = perform_tls_negotiation(server.to_owned(), stream).await?; + begin_authentication(config, stream).await + } + TlsMethod::Starttls => { + todo!() + } + } +} + +/// Performs TLS negotiation, using the webpki_roots and verifying the server name +async fn perform_tls_negotiation( + server_name: impl AsRef, + stream: impl AsyncRead + AsyncWrite + Unpin, +) -> Result { + let server_name = server_name.as_ref(); + + let mut tls_config = ClientConfig::new(); + tls_config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + let tls_config = TlsConnector::from(Arc::new(tls_config)); + let dnsname = DNSNameRef::try_from_ascii_str(server_name).unwrap(); + let stream = tls_config.connect(dnsname, stream).await?; + + Ok(stream) +} + +async fn begin_authentication( + config: ImapConfig, + stream: impl AsyncRead + AsyncWrite, +) -> Result<()> { Ok(()) } diff --git a/src/main.rs b/src/main.rs index ce150d3..1752dd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #[macro_use] -extern crate log; +extern crate tracing; +use std::fs::{File, OpenOptions}; use std::path::PathBuf; use anyhow::Result; @@ -13,10 +14,6 @@ use xdg::BaseDirectories; #[derive(Debug, StructOpt)] #[structopt(author, about)] struct Opt { - /// Config file - #[structopt(long = "config-file", short = "c")] - config_path: Option, - /// The path to the log file. By default, does not log. #[structopt(long = "log-file")] log_file: Option, @@ -28,7 +25,21 @@ async fn main() -> Result<()> { let opt = Opt::from_args(); // print logs to file as directed by command line options - setup_logger(&opt)?; + use tracing_subscriber::filter::LevelFilter; + + let file = tracing_appender::rolling::daily("public", "lol"); + let (non_blocking, _guard) = tracing_appender::non_blocking(file); + + tracing_subscriber::fmt() + .with_max_level(LevelFilter::TRACE) + .with_writer(non_blocking) + .with_thread_ids(true) + .init(); + debug!("shiet"); + + // TODO: debug + let x = span!(tracing::Level::WARN, "ouais"); + let _y = x.enter(); let _xdg = BaseDirectories::new()?; let (_config_thread, config_update) = spawn_config_watcher_system()?; @@ -59,24 +70,6 @@ async fn main() -> Result<()> { } fn setup_logger(opt: &Opt) -> Result<()> { - let mut fern = 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); - - if let Some(path) = &opt.log_file { - fern = fern.chain(fern::log_file(path)?); - } - - // fern.apply()?; - tracing_subscriber::fmt::init(); - + debug!("logging set up."); Ok(()) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 594f462..ac290a2 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -29,11 +29,6 @@ pub struct Rect(u16, u16, u16, u16); /// UI entrypoint. #[instrument] pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> { - loop { - tokio::time::sleep(Duration::from_secs(5)).await; - debug!("1"); - } - execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?; terminal::enable_raw_mode()?;