From 14b07adf661c375ea6d4a8ec3e873bc5c33adedb Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Thu, 25 Mar 2021 09:12:28 -0500 Subject: [PATCH] add mail dir and db path to the config --- Cargo.lock | 43 +++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + docs/src/config.md | 3 +++ src/config.rs | 6 ++++++ src/mail/client.rs | 1 + src/mail/mod.rs | 6 ++++-- src/mail/store.rs | 49 +++++++++++++++++++++++++++++++--------------- src/main.rs | 12 ++++++++---- src/ui/mod.rs | 4 ++++ 9 files changed, 102 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70740d8..c2e54ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -769,7 +769,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", - "redox_users", + "redox_users 0.3.5", + "winapi", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.0", "winapi", ] @@ -1964,6 +1985,7 @@ dependencies = [ "quoted_printable", "serde", "sha2", + "shellexpand", "sqlx", "structopt", "tokio 1.3.0", @@ -2376,6 +2398,16 @@ dependencies = [ "rust-argon2", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.2", + "redox_syscall 0.2.5", +] + [[package]] name = "regex" version = "1.4.5" @@ -2621,6 +2653,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" +[[package]] +name = "shellexpand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +dependencies = [ + "dirs-next", +] + [[package]] name = "signal-hook" version = "0.1.17" diff --git a/Cargo.toml b/Cargo.toml index 92a5c09..0cba6ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ quoted_printable = "0.4.2" sqlx = { version = "0.5.1", features = ["runtime-tokio-rustls", "sqlite"] } sha2 = "0.9.3" hex = "0.4.3" +shellexpand = "2.1.0" [dependencies.panorama-imap] path = "imap" diff --git a/docs/src/config.md b/docs/src/config.md index 1b587e4..ab6af00 100644 --- a/docs/src/config.md +++ b/docs/src/config.md @@ -10,6 +10,9 @@ Example configuration: ```toml version = "0.1" +mail_dir = "~/.local/share/panorama/mail" +db_path = "~/.local/share/panorama/panorama.db" + [[mail]] imap.server = "mail.example.com" imap.port = 143 diff --git a/src/config.rs b/src/config.rs index 15f94e3..1c13a53 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,6 +25,12 @@ pub struct Config { /// (potentially for migration later?) pub version: String, + /// Directory to store mail in + pub mail_dir: PathBuf, + + /// SQLite database path + pub db_path: PathBuf, + /// Mail accounts #[serde(rename = "mail")] pub mail_accounts: HashMap, diff --git a/src/mail/client.rs b/src/mail/client.rs index 6a9869e..2d3fc39 100644 --- a/src/mail/client.rs +++ b/src/mail/client.rs @@ -23,6 +23,7 @@ use super::{MailCommand, MailEvent, MailStore}; /// The main function for the IMAP syncing thread pub async fn sync_main( + config: Config, acct_name: impl AsRef, acct: MailAccountConfig, mail2ui_tx: UnboundedSender, diff --git a/src/mail/mod.rs b/src/mail/mod.rs index 1dbd8e8..0eec232 100644 --- a/src/mail/mod.rs +++ b/src/mail/mod.rs @@ -67,16 +67,18 @@ pub async fn run_mail( conn.abort(); } - let mail_store = MailStore::new().await?; - for (acct_name, acct) in config.mail_accounts.into_iter() { + let mail_store = MailStore::new(config.clone()).await?; + for (acct_name, acct) in config.mail_accounts.clone().into_iter() { let mail2ui_tx = mail2ui_tx.clone(); let mail_store = mail_store.clone(); + let config2 = config.clone(); let handle = tokio::spawn(async move { // debug!("opening imap connection for {:?}", acct); // this loop is to make sure accounts are restarted on error loop { match client::sync_main( + config2.clone(), &acct_name, acct.clone(), mail2ui_tx.clone(), diff --git a/src/mail/store.rs b/src/mail/store.rs index b614db5..f8a3121 100644 --- a/src/mail/store.rs +++ b/src/mail/store.rs @@ -1,10 +1,10 @@ //! Package for managing the offline storage of emails -use std::path::PathBuf; +use std::path::{PathBuf, Path}; use anyhow::Result; use panorama_imap::response::AttributeValue; -use sha2::{Digest, Sha256, Sha512}; +use sha2::{Digest, Sha256}; use sqlx::{ migrate::{MigrateDatabase, Migrator}, sqlite::{Sqlite, SqlitePool}, @@ -12,6 +12,8 @@ use sqlx::{ }; use tokio::fs; +use crate::config::Config; + static MIGRATOR: Migrator = sqlx::migrate!(); /// Manages email storage on disk, for both database and caches @@ -19,30 +21,45 @@ static MIGRATOR: Migrator = sqlx::migrate!(); /// This struct is clone-safe: cloning it will just return a reference to the same data structure #[derive(Clone)] pub struct MailStore { + config: Config, mail_dir: PathBuf, pool: SqlitePool, } impl MailStore { /// Creates a new MailStore - pub async fn new() -> Result { - let db_path = "sqlite:hellosu.db"; - - // create the database file if it doesn't already exist -_ - - if !Sqlite::database_exists(db_path).await? { - Sqlite::create_database(db_path).await?; - } - - let pool = SqlitePool::connect(db_path).await?; - MIGRATOR.run(&pool).await?; - debug!("run migrations : {:?}", MIGRATOR); - - let mail_dir = PathBuf::from("hellosu/"); + pub async fn new(config: Config) -> Result { + let mail_dir = config.mail_dir.to_string_lossy(); + let mail_dir_str = shellexpand::tilde(mail_dir.as_ref()); + let mail_dir = PathBuf::from(mail_dir_str.as_ref()); if !mail_dir.exists() { fs::create_dir_all(&mail_dir).await?; } + info!("using mail dir: {:?}", mail_dir); - Ok(MailStore { mail_dir, pool }) + // create database parent + let db_path = config.db_path.to_string_lossy(); + let db_path_str = shellexpand::tilde(db_path.as_ref()); + + let db_path = PathBuf::from(db_path_str.as_ref()); + let db_parent = db_path.parent(); + if let Some(path) = db_parent { + fs::create_dir_all(path).await?; + } + + let db_path = format!("sqlite:{}", db_path_str); + info!("using database path: {}", db_path_str); + + // create the database file if it doesn't already exist -_ - + if !Sqlite::database_exists(&db_path_str).await? { + Sqlite::create_database(&db_path_str).await?; + } + + let pool = SqlitePool::connect(&db_path_str).await?; + MIGRATOR.run(&pool).await?; + debug!("run migrations : {:?}", MIGRATOR); + + Ok(MailStore { config, mail_dir, pool }) } /// Gets the list of all the UIDs in the given folder that need to be updated diff --git a/src/main.rs b/src/main.rs index 082db4b..58cc5b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use anyhow::Result; use fern::colors::{Color, ColoredLevelConfig}; use futures::future::TryFutureExt; use panorama::{ - config::spawn_config_watcher_system, + config::{spawn_config_watcher_system, ConfigWatcher}, mail::{self, MailEvent}, report_err, ui::{self, UiParams}, @@ -63,15 +63,16 @@ async fn run(opt: Opt) -> Result<()> { // send messages from the UI thread to the vm thread let (ui2vm_tx, _ui2vm_rx) = mpsc::unbounded_channel(); + let config_update2 = config_update.clone(); tokio::spawn(async move { - let config_update = config_update.clone(); - mail::run_mail(config_update, ui2mail_rx, mail2ui_tx) + mail::run_mail(config_update2, ui2mail_rx, mail2ui_tx) .unwrap_or_else(report_err) .await; }); if !opt.headless { - run_ui(exit_tx, mail2ui_rx, ui2vm_tx); + let config_update2 = config_update.clone(); + run_ui(config_update2, exit_tx, mail2ui_rx, ui2vm_tx); } exit_rx.recv().await; @@ -85,6 +86,7 @@ async fn run(opt: Opt) -> Result<()> { // Spawns the entire UI in a different thread, since it must be thread-local fn run_ui( + config_update: ConfigWatcher, exit_tx: mpsc::Sender<()>, mail2ui_rx: mpsc::UnboundedReceiver, _ui2vm_tx: mpsc::UnboundedSender<()>, @@ -99,6 +101,7 @@ fn run_ui( thread::spawn(move || { let localset = LocalSet::new(); let params = UiParams { + config_update, stdout, exit_tx, mail2ui_rx, @@ -123,6 +126,7 @@ fn setup_logger(log_file: Option>) -> Result<()> { .filter(|meta| { meta.target() != "tokio_util::codec::framed_impl" && !meta.target().starts_with("rustls::client") + && !meta.target().starts_with("sqlx::query") }) .format(move |out, message, record| { out.finish(format_args!( diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b86cbfa..f753b06 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -38,6 +38,7 @@ use tui::{ Frame, Terminal, }; +use crate::config::ConfigWatcher; use crate::mail::{EmailMetadata, MailEvent}; use self::colon_prompt::ColonPrompt; @@ -52,6 +53,9 @@ pub(crate) type TermType<'a, 'b> = &'b mut Terminal