start 2nd (cleaner) imap implementation
This commit is contained in:
parent
e089d76fc8
commit
9116898a21
10 changed files with 118 additions and 62 deletions
2
.github/workflows/doc.yml
vendored
2
.github/workflows/doc.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
||||||
|
|
||||||
- name: build api docs
|
- name: build api docs
|
||||||
run: |
|
run: |
|
||||||
cargo doc --workspace --no-deps
|
cargo doc --workspace --no-deps --document-private-items
|
||||||
cp -r target/doc public/api
|
cp -r target/doc public/api
|
||||||
|
|
||||||
- name: deploy
|
- name: deploy
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
/.env
|
/.env
|
||||||
/output.log
|
/output.log
|
||||||
/config.toml
|
/config.toml
|
||||||
|
/public
|
||||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -232,15 +232,6 @@ dependencies = [
|
||||||
"ascii_utils",
|
"ascii_utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fern"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -654,11 +645,9 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"fern",
|
|
||||||
"futures",
|
"futures",
|
||||||
"inotify",
|
"inotify",
|
||||||
"lettre",
|
"lettre",
|
||||||
"log",
|
|
||||||
"panorama-imap",
|
"panorama-imap",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"rustls-connector",
|
"rustls-connector",
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -12,16 +12,17 @@ license = "GPL-3.0-or-later"
|
||||||
members = ["imap"]
|
members = ["imap"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# log = "0.4.14"
|
||||||
|
# fern = "0.6.0"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-trait = "0.1.42"
|
async-trait = "0.1.42"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
crossterm = "0.19.0"
|
crossterm = "0.19.0"
|
||||||
fern = "0.6.0"
|
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
panorama-imap = { path = "imap", version = "0" }
|
inotify = { version = "0.9.2", features = ["stream"] }
|
||||||
lettre = "0.9.5"
|
lettre = "0.9.5"
|
||||||
log = "0.4.14"
|
panorama-imap = { path = "imap", version = "0" }
|
||||||
pin-project = "1.0.4"
|
pin-project = "1.0.4"
|
||||||
rustls-connector = "0.13.1"
|
rustls-connector = "0.13.1"
|
||||||
serde = { version = "1.0.123", features = ["derive"] }
|
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-stream = { version = "0.1.3", features = ["sync"] }
|
||||||
tokio-util = { version = "0.6.3", features = ["full"] }
|
tokio-util = { version = "0.6.3", features = ["full"] }
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
|
tracing = "0.1.23"
|
||||||
|
tracing-appender = "0.1.2"
|
||||||
|
tracing-subscriber = "0.2.15"
|
||||||
webpki-roots = "0.21.0"
|
webpki-roots = "0.21.0"
|
||||||
xdg = "2.2.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]
|
[features]
|
||||||
clippy = []
|
clippy = []
|
||||||
|
|
3
Justfile
3
Justfile
|
@ -1,5 +1,8 @@
|
||||||
doc:
|
doc:
|
||||||
cargo doc --document-private-items
|
cargo doc --document-private-items
|
||||||
|
|
||||||
|
doc-open:
|
||||||
|
cargo doc --document-private-items --open
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
cargo watch -x 'clippy --all --all-features'
|
cargo watch -x 'clippy --all --all-features'
|
||||||
|
|
|
@ -2,14 +2,15 @@
|
||||||
//!
|
//!
|
||||||
//! One of the primary goals of panorama is to be able to always hot-reload configuration files.
|
//! 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::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use futures::{future::TryFutureExt, stream::StreamExt};
|
use futures::{future::TryFutureExt, stream::StreamExt};
|
||||||
use inotify::{Inotify, WatchMask};
|
use inotify::{Inotify, WatchMask};
|
||||||
use tokio::{sync::watch, task::JoinHandle};
|
use tokio::{sync::watch, task::JoinHandle};
|
||||||
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
use crate::report_err;
|
use crate::report_err;
|
||||||
|
|
||||||
|
@ -81,19 +82,39 @@ async fn read_config(path: impl AsRef<Path>) -> Result<Config> {
|
||||||
|
|
||||||
async fn start_inotify_stream(
|
async fn start_inotify_stream(
|
||||||
mut inotify: Inotify,
|
mut inotify: Inotify,
|
||||||
|
config_home: impl AsRef<Path>,
|
||||||
config_tx: watch::Sender<Config>,
|
config_tx: watch::Sender<Config>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut buffer = vec![0; 1024];
|
let mut buffer = vec![0; 1024];
|
||||||
let mut event_stream = inotify.event_stream(&mut buffer)?;
|
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 {
|
while let Some(v) = event_stream.next().await {
|
||||||
let event = v?;
|
let event = v.context("event")?;
|
||||||
|
|
||||||
debug!("event: {:?}", event);
|
|
||||||
|
|
||||||
if let Some(name) = event.name {
|
if let Some(name) = event.name {
|
||||||
let path = PathBuf::from(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)?;
|
config_tx.send(config)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,9 +126,18 @@ async fn start_inotify_stream(
|
||||||
/// which is a cloneable receiver of config update events.
|
/// which is a cloneable receiver of config update events.
|
||||||
pub fn spawn_config_watcher_system() -> Result<(JoinHandle<()>, ConfigWatcher)> {
|
pub fn spawn_config_watcher_system() -> Result<(JoinHandle<()>, ConfigWatcher)> {
|
||||||
let mut inotify = Inotify::init()?;
|
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 (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))
|
Ok((handle, config_update))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate crossterm;
|
extern crate crossterm;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
@ -23,5 +21,5 @@ pub type ExitSender = tokio::sync::mpsc::Sender<()>;
|
||||||
|
|
||||||
/// Consumes any error and dumps it to the logger.
|
/// Consumes any error and dumps it to the logger.
|
||||||
pub fn report_err(err: anyhow::Error) {
|
pub fn report_err(err: anyhow::Error) {
|
||||||
error!("error: {:?}", err);
|
error!("error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,56 @@
|
||||||
// let's try this again
|
// 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<()> {
|
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<str>,
|
||||||
|
stream: impl AsyncRead + AsyncWrite + Unpin,
|
||||||
|
) -> Result<impl AsyncRead + AsyncWrite> {
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -1,6 +1,7 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate tracing;
|
||||||
|
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -13,10 +14,6 @@ use xdg::BaseDirectories;
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(author, about)]
|
#[structopt(author, about)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
/// Config file
|
|
||||||
#[structopt(long = "config-file", short = "c")]
|
|
||||||
config_path: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// The path to the log file. By default, does not log.
|
/// The path to the log file. By default, does not log.
|
||||||
#[structopt(long = "log-file")]
|
#[structopt(long = "log-file")]
|
||||||
log_file: Option<PathBuf>,
|
log_file: Option<PathBuf>,
|
||||||
|
@ -28,7 +25,21 @@ async fn main() -> Result<()> {
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
// print logs to file as directed by command line options
|
// 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 _xdg = BaseDirectories::new()?;
|
||||||
let (_config_thread, config_update) = spawn_config_watcher_system()?;
|
let (_config_thread, config_update) = spawn_config_watcher_system()?;
|
||||||
|
@ -59,24 +70,6 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_logger(opt: &Opt) -> Result<()> {
|
fn setup_logger(opt: &Opt) -> Result<()> {
|
||||||
let mut fern = fern::Dispatch::new()
|
debug!("logging set up.");
|
||||||
.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();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,6 @@ pub struct Rect(u16, u16, u16, u16);
|
||||||
/// UI entrypoint.
|
/// UI entrypoint.
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> {
|
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)?;
|
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
|
||||||
terminal::enable_raw_mode()?;
|
terminal::enable_raw_mode()?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue