restructure configs

This commit is contained in:
Michael Zhang 2021-02-14 17:49:54 -06:00
parent 5ad3a844e1
commit 4ed955fb93
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
4 changed files with 62 additions and 26 deletions

11
notes.md Normal file
View file

@ -0,0 +1,11 @@
imap routine
---
- basic tcp connection is opened
- if tls is "on", then immediately perform tls handshake with the server
- if tls is "starttls", check starttls capability
- if the server doesn't have starttls capability, die and report to the user
- if the server _does_ have starttls, exit the read loop and perform tls handshake over current connection
- at this point, tls should be figured out, so moving on to auth
- check if the auth type that the user specified is in the list of auth types (prob support plain and oauth2?)

View file

@ -14,11 +14,30 @@ use tokio::{sync::watch, task::JoinHandle};
use xdg::BaseDirectories; use xdg::BaseDirectories;
/// Alias for a MailConfig receiver. /// Alias for a MailConfig receiver.
pub type ConfigWatcher = watch::Receiver<Option<MailConfig>>; pub type ConfigWatcher = watch::Receiver<Option<Config>>;
/// Configuration /// Configuration
#[derive(Default, Serialize, Deserialize, Clone, Debug)] #[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct MailConfig { pub struct Config {
/// Version of the config to use
/// (potentially for migration later?)
pub version: String,
/// Mail accounts
#[serde(rename = "mail")]
pub mail_accounts: Vec<MailAccountConfig>,
}
/// Configuration for a single mail account
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct MailAccountConfig {
/// Imap
pub imap: ImapConfig,
}
/// Configuring an IMAP server
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct ImapConfig {
/// Host of the IMAP server (needs to be hostname for TLS) /// Host of the IMAP server (needs to be hostname for TLS)
pub server: String, pub server: String,
@ -48,7 +67,7 @@ fn start_watcher() -> Result<(RecommendedWatcher, Receiver<DebouncedEvent>)> {
Ok((watcher, rx)) Ok((watcher, rx))
} }
async fn read_config(path: impl AsRef<Path>) -> Result<MailConfig> { async fn read_config(path: impl AsRef<Path>) -> Result<Config> {
let mut file = File::open(path.as_ref())?; let mut file = File::open(path.as_ref())?;
let mut contents = Vec::new(); let mut contents = Vec::new();
file.read_to_end(&mut contents)?; file.read_to_end(&mut contents)?;
@ -63,20 +82,29 @@ async fn read_config(path: impl AsRef<Path>) -> Result<MailConfig> {
/// This exists so all errors are able to be caught in one go. /// This exists so all errors are able to be caught in one go.
async fn watcher_loop( async fn watcher_loop(
fs_events: Receiver<DebouncedEvent>, fs_events: Receiver<DebouncedEvent>,
config_tx: watch::Sender<Option<MailConfig>>, config_tx: watch::Sender<Option<Config>>,
) -> Result<()> { ) -> Result<()> {
// first try opening the config file directly on load // first try opening the config file directly when the program is opened
// (so the config isn't blank until the user touches the config file) // (so the config isn't blank until the user touches the config file)
let xdg = BaseDirectories::new()?; let xdg = BaseDirectories::new()?;
if let Some(config_path) = xdg.find_config_file("panorama/panorama.toml") { if let Some(config_path) = xdg.find_config_file("panorama/panorama.toml") {
debug!("found config at {:?}", config_path); debug!("found config at {:?}", config_path);
let config = read_config(config_path).await?; let config = read_config(config_path).await?;
debug!("read config: {:?}, sending to output", config);
config_tx.send(Some(config))?; config_tx.send(Some(config))?;
} }
// start listening for events from the notify::Watcher
for event in fs_events { for event in fs_events {
debug!("new event: {:?}", event); debug!("new event: {:?}", event);
// config_tx.send(Some(config))?; use notify::DebouncedEvent::*;
match event {
NoticeWrite(path) | Write(path) => {
let config = read_config(path).await?;
config_tx.send(Some(config))?;
}
_ => {}
}
} }
Ok(()) Ok(())
@ -93,7 +121,7 @@ pub fn spawn_config_watcher() -> Result<(JoinHandle<()>, ConfigWatcher)> {
match watcher_loop(config_rx, config_tx).await { match watcher_loop(config_rx, config_tx).await {
Ok(_) => {} Ok(_) => {}
Err(err) => { Err(err) => {
debug!("config watcher died: {:?}", err); debug!("config watcher bugged: {:?}", err);
} }
} }
}); });

View file

@ -25,7 +25,7 @@ use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
use tokio_stream::wrappers::WatchStream; use tokio_stream::wrappers::WatchStream;
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError}; use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
use crate::config::{ConfigWatcher, MailConfig}; use crate::config::{Config, ConfigWatcher, ImapConfig};
/// Command sent to the mail thread by something else (i.e. UI) /// Command sent to the mail thread by something else (i.e. UI)
pub enum MailCommand { pub enum MailCommand {
@ -45,7 +45,10 @@ pub async fn run_mail(
let mut config_watcher = WatchStream::new(config_watcher); let mut config_watcher = WatchStream::new(config_watcher);
loop { loop {
let config: MailConfig = match config_watcher.next().await { debug!("listening for configs");
let a = config_watcher.next().await;
debug!("got config {:?}", a);
let config: Config = match a {
Some(Some(v)) => v, Some(Some(v)) => v,
_ => break, _ => break,
}; };
@ -57,14 +60,18 @@ pub async fn run_mail(
curr_conn.abort(); curr_conn.abort();
} }
let handle = tokio::spawn(open_imap_connection(config)); let handle = tokio::spawn(async {
for acct in config.mail_accounts.into_iter() {
open_imap_connection(acct.imap);
}
});
curr_conn = Some(handle); curr_conn = Some(handle);
} }
Ok(()) Ok(())
} }
async fn open_imap_connection(config: MailConfig) -> Result<()> { async fn open_imap_connection(config: ImapConfig) -> Result<()> {
debug!( debug!(
"Opening imap connection to {}:{}", "Opening imap connection to {}:{}",
config.server, config.port config.server, config.port
@ -115,7 +122,7 @@ enum LoopExit<S, S2> {
} }
async fn listen_loop<S, S2>( async fn listen_loop<S, S2>(
config: MailConfig, config: ImapConfig,
st: &mut State, st: &mut State,
sink: S2, sink: S2,
mut stream: S, mut stream: S,

View file

@ -8,7 +8,7 @@ use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use futures::future::TryFutureExt; use futures::future::TryFutureExt;
use panorama::{ use panorama::{
config::{spawn_config_watcher, MailConfig}, config::{spawn_config_watcher, Config},
mail, ui, mail, ui,
}; };
use structopt::StructOpt; use structopt::StructOpt;
@ -38,26 +38,16 @@ async fn main() -> Result<()> {
let xdg = BaseDirectories::new()?; let xdg = BaseDirectories::new()?;
let (config_thread, config_update) = spawn_config_watcher()?; let (config_thread, config_update) = spawn_config_watcher()?;
// let config: MailConfig = {
// let config_path = opt
// .config_path
// .clone()
// .unwrap_or_else(|| "config.toml".into());
// let mut config_file = File::open(config_path)?;
// let mut contents = Vec::new();
// config_file.read_to_end(&mut contents)?;
// toml::from_slice(&contents)?
// };
// used to notify the runtime that the process should exit // used to notify the runtime that the process should exit
let (exit_tx, mut exit_rx) = mpsc::channel::<()>(1); let (exit_tx, mut exit_rx) = mpsc::channel::<()>(1);
// used to send commands to the mail service // used to send commands to the mail service
let (mail_tx, mail_rx) = mpsc::unbounded_channel(); let (mail_tx, mail_rx) = mpsc::unbounded_channel();
let mail_thread = tokio::spawn(mail::run_mail(config_update.clone(), mail_rx).unwrap_or_else(report_err)); tokio::spawn(mail::run_mail(config_update.clone(), mail_rx).unwrap_or_else(report_err));
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let ui_thread = tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err)); tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
exit_rx.recv().await; exit_rx.recv().await;