add mail dir and db path to the config

This commit is contained in:
Michael Zhang 2021-03-25 09:12:28 -05:00
parent 0690c46f66
commit 14b07adf66
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
9 changed files with 102 additions and 23 deletions

43
Cargo.lock generated
View file

@ -769,7 +769,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
dependencies = [ dependencies = [
"libc", "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", "winapi",
] ]
@ -1964,6 +1985,7 @@ dependencies = [
"quoted_printable", "quoted_printable",
"serde", "serde",
"sha2", "sha2",
"shellexpand",
"sqlx", "sqlx",
"structopt", "structopt",
"tokio 1.3.0", "tokio 1.3.0",
@ -2376,6 +2398,16 @@ dependencies = [
"rust-argon2", "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]] [[package]]
name = "regex" name = "regex"
version = "1.4.5" version = "1.4.5"
@ -2621,6 +2653,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" 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]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.1.17" version = "0.1.17"

View file

@ -44,6 +44,7 @@ quoted_printable = "0.4.2"
sqlx = { version = "0.5.1", features = ["runtime-tokio-rustls", "sqlite"] } sqlx = { version = "0.5.1", features = ["runtime-tokio-rustls", "sqlite"] }
sha2 = "0.9.3" sha2 = "0.9.3"
hex = "0.4.3" hex = "0.4.3"
shellexpand = "2.1.0"
[dependencies.panorama-imap] [dependencies.panorama-imap]
path = "imap" path = "imap"

View file

@ -10,6 +10,9 @@ Example configuration:
```toml ```toml
version = "0.1" version = "0.1"
mail_dir = "~/.local/share/panorama/mail"
db_path = "~/.local/share/panorama/panorama.db"
[[mail]] [[mail]]
imap.server = "mail.example.com" imap.server = "mail.example.com"
imap.port = 143 imap.port = 143

View file

@ -25,6 +25,12 @@ pub struct Config {
/// (potentially for migration later?) /// (potentially for migration later?)
pub version: String, pub version: String,
/// Directory to store mail in
pub mail_dir: PathBuf,
/// SQLite database path
pub db_path: PathBuf,
/// Mail accounts /// Mail accounts
#[serde(rename = "mail")] #[serde(rename = "mail")]
pub mail_accounts: HashMap<String, MailAccountConfig>, pub mail_accounts: HashMap<String, MailAccountConfig>,

View file

@ -23,6 +23,7 @@ use super::{MailCommand, MailEvent, MailStore};
/// The main function for the IMAP syncing thread /// The main function for the IMAP syncing thread
pub async fn sync_main( pub async fn sync_main(
config: Config,
acct_name: impl AsRef<str>, acct_name: impl AsRef<str>,
acct: MailAccountConfig, acct: MailAccountConfig,
mail2ui_tx: UnboundedSender<MailEvent>, mail2ui_tx: UnboundedSender<MailEvent>,

View file

@ -67,16 +67,18 @@ pub async fn run_mail(
conn.abort(); conn.abort();
} }
let mail_store = MailStore::new().await?; let mail_store = MailStore::new(config.clone()).await?;
for (acct_name, acct) in config.mail_accounts.into_iter() { for (acct_name, acct) in config.mail_accounts.clone().into_iter() {
let mail2ui_tx = mail2ui_tx.clone(); let mail2ui_tx = mail2ui_tx.clone();
let mail_store = mail_store.clone(); let mail_store = mail_store.clone();
let config2 = config.clone();
let handle = tokio::spawn(async move { let handle = tokio::spawn(async move {
// debug!("opening imap connection for {:?}", acct); // debug!("opening imap connection for {:?}", acct);
// this loop is to make sure accounts are restarted on error // this loop is to make sure accounts are restarted on error
loop { loop {
match client::sync_main( match client::sync_main(
config2.clone(),
&acct_name, &acct_name,
acct.clone(), acct.clone(),
mail2ui_tx.clone(), mail2ui_tx.clone(),

View file

@ -1,10 +1,10 @@
//! Package for managing the offline storage of emails //! Package for managing the offline storage of emails
use std::path::PathBuf; use std::path::{PathBuf, Path};
use anyhow::Result; use anyhow::Result;
use panorama_imap::response::AttributeValue; use panorama_imap::response::AttributeValue;
use sha2::{Digest, Sha256, Sha512}; use sha2::{Digest, Sha256};
use sqlx::{ use sqlx::{
migrate::{MigrateDatabase, Migrator}, migrate::{MigrateDatabase, Migrator},
sqlite::{Sqlite, SqlitePool}, sqlite::{Sqlite, SqlitePool},
@ -12,6 +12,8 @@ use sqlx::{
}; };
use tokio::fs; use tokio::fs;
use crate::config::Config;
static MIGRATOR: Migrator = sqlx::migrate!(); static MIGRATOR: Migrator = sqlx::migrate!();
/// Manages email storage on disk, for both database and caches /// 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 /// This struct is clone-safe: cloning it will just return a reference to the same data structure
#[derive(Clone)] #[derive(Clone)]
pub struct MailStore { pub struct MailStore {
config: Config,
mail_dir: PathBuf, mail_dir: PathBuf,
pool: SqlitePool, pool: SqlitePool,
} }
impl MailStore { impl MailStore {
/// Creates a new MailStore /// Creates a new MailStore
pub async fn new() -> Result<Self> { pub async fn new(config: Config) -> Result<Self> {
let db_path = "sqlite:hellosu.db"; let mail_dir = config.mail_dir.to_string_lossy();
let mail_dir_str = shellexpand::tilde(mail_dir.as_ref());
// create the database file if it doesn't already exist -_ - let mail_dir = PathBuf::from(mail_dir_str.as_ref());
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/");
if !mail_dir.exists() { if !mail_dir.exists() {
fs::create_dir_all(&mail_dir).await?; 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 /// Gets the list of all the UIDs in the given folder that need to be updated

View file

@ -5,7 +5,7 @@ use anyhow::Result;
use fern::colors::{Color, ColoredLevelConfig}; use fern::colors::{Color, ColoredLevelConfig};
use futures::future::TryFutureExt; use futures::future::TryFutureExt;
use panorama::{ use panorama::{
config::spawn_config_watcher_system, config::{spawn_config_watcher_system, ConfigWatcher},
mail::{self, MailEvent}, mail::{self, MailEvent},
report_err, report_err,
ui::{self, UiParams}, ui::{self, UiParams},
@ -63,15 +63,16 @@ async fn run(opt: Opt) -> Result<()> {
// send messages from the UI thread to the vm thread // send messages from the UI thread to the vm thread
let (ui2vm_tx, _ui2vm_rx) = mpsc::unbounded_channel(); let (ui2vm_tx, _ui2vm_rx) = mpsc::unbounded_channel();
let config_update2 = config_update.clone();
tokio::spawn(async move { tokio::spawn(async move {
let config_update = config_update.clone(); mail::run_mail(config_update2, ui2mail_rx, mail2ui_tx)
mail::run_mail(config_update, ui2mail_rx, mail2ui_tx)
.unwrap_or_else(report_err) .unwrap_or_else(report_err)
.await; .await;
}); });
if !opt.headless { 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; 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 // Spawns the entire UI in a different thread, since it must be thread-local
fn run_ui( fn run_ui(
config_update: ConfigWatcher,
exit_tx: mpsc::Sender<()>, exit_tx: mpsc::Sender<()>,
mail2ui_rx: mpsc::UnboundedReceiver<MailEvent>, mail2ui_rx: mpsc::UnboundedReceiver<MailEvent>,
_ui2vm_tx: mpsc::UnboundedSender<()>, _ui2vm_tx: mpsc::UnboundedSender<()>,
@ -99,6 +101,7 @@ fn run_ui(
thread::spawn(move || { thread::spawn(move || {
let localset = LocalSet::new(); let localset = LocalSet::new();
let params = UiParams { let params = UiParams {
config_update,
stdout, stdout,
exit_tx, exit_tx,
mail2ui_rx, mail2ui_rx,
@ -123,6 +126,7 @@ fn setup_logger(log_file: Option<impl AsRef<Path>>) -> Result<()> {
.filter(|meta| { .filter(|meta| {
meta.target() != "tokio_util::codec::framed_impl" meta.target() != "tokio_util::codec::framed_impl"
&& !meta.target().starts_with("rustls::client") && !meta.target().starts_with("rustls::client")
&& !meta.target().starts_with("sqlx::query")
}) })
.format(move |out, message, record| { .format(move |out, message, record| {
out.finish(format_args!( out.finish(format_args!(

View file

@ -38,6 +38,7 @@ use tui::{
Frame, Terminal, Frame, Terminal,
}; };
use crate::config::ConfigWatcher;
use crate::mail::{EmailMetadata, MailEvent}; use crate::mail::{EmailMetadata, MailEvent};
use self::colon_prompt::ColonPrompt; use self::colon_prompt::ColonPrompt;
@ -52,6 +53,9 @@ pub(crate) type TermType<'a, 'b> = &'b mut Terminal<CrosstermBackend<&'a mut Std
/// Parameters for passing to the UI thread /// Parameters for passing to the UI thread
pub struct UiParams { pub struct UiParams {
/// Config updates
pub config_update: ConfigWatcher,
/// Handle to the screen /// Handle to the screen
pub stdout: Stdout, pub stdout: Stdout,