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
|
||||
run: |
|
||||
cargo doc --workspace --no-deps
|
||||
cargo doc --workspace --no-deps --document-private-items
|
||||
cp -r target/doc public/api
|
||||
|
||||
- name: deploy
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
|||
/.env
|
||||
/output.log
|
||||
/config.toml
|
||||
/public
|
||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -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",
|
||||
|
|
14
Cargo.toml
14
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 = []
|
||||
|
|
3
Justfile
3
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'
|
||||
|
|
|
@ -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<Path>) -> Result<Config> {
|
|||
|
||||
async fn start_inotify_stream(
|
||||
mut inotify: Inotify,
|
||||
config_home: impl AsRef<Path>,
|
||||
config_tx: watch::Sender<Config>,
|
||||
) -> 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))
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<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(())
|
||||
}
|
||||
|
|
43
src/main.rs
43
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<PathBuf>,
|
||||
|
||||
/// The path to the log file. By default, does not log.
|
||||
#[structopt(long = "log-file")]
|
||||
log_file: Option<PathBuf>,
|
||||
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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()?;
|
||||
|
||||
|
|
Loading…
Reference in a new issue