diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 27d51f02..238bb4b3 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -58,7 +58,12 @@ pub type BackendCreator = Box< /// A hashmap containing all available mail backends. /// An abstraction over any available backends. pub struct Backends { - map: FnvHashMap BackendCreator>>, + map: FnvHashMap, +} + +pub struct Backend { + pub create_fn: Box BackendCreator>, + pub validate_conf_fn: Box Result<()>>, } impl Default for Backends { @@ -76,28 +81,40 @@ impl Backends { { b.register( "maildir".to_string(), - Box::new(|| Box::new(|f, i| MaildirType::new(f, i))), + Backend { + create_fn: Box::new(|| Box::new(|f, i| MaildirType::new(f, i))), + validate_conf_fn: Box::new(MaildirType::validate_config), + }, ); } #[cfg(feature = "mbox_backend")] { b.register( "mbox".to_string(), - Box::new(|| Box::new(|f, i| MboxType::new(f, i))), + Backend { + create_fn: Box::new(|| Box::new(|f, i| MboxType::new(f, i))), + validate_conf_fn: Box::new(MboxType::validate_config), + }, ); } #[cfg(feature = "imap_backend")] { b.register( "imap".to_string(), - Box::new(|| Box::new(|f, i| ImapType::new(f, i))), + Backend { + create_fn: Box::new(|| Box::new(|f, i| ImapType::new(f, i))), + validate_conf_fn: Box::new(ImapType::validate_config), + }, ); } #[cfg(feature = "notmuch_backend")] { b.register( "notmuch".to_string(), - Box::new(|| Box::new(|f, i| NotmuchDb::new(f, i))), + Backend { + create_fn: Box::new(|| Box::new(|f, i| NotmuchDb::new(f, i))), + validate_conf_fn: Box::new(NotmuchDb::validate_config), + }, ); } b @@ -107,15 +124,23 @@ impl Backends { if !self.map.contains_key(key) { panic!("{} is not a valid mail backend", key); } - self.map[key]() + (self.map[key].create_fn)() } - pub fn register(&mut self, key: String, backend: Box BackendCreator>) { + pub fn register(&mut self, key: String, backend: Backend) { if self.map.contains_key(&key) { panic!("{} is an already registered backend", key); } self.map.insert(key, backend); } + + pub fn validate_config(&self, key: &str, s: &AccountSettings) -> Result<()> { + (self + .map + .get(key) + .ok_or_else(|| MeliError::new(format!("{} is not a valid mail backend", key)))? + .validate_conf_fn)(s) + } } #[derive(Debug)] diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index d6bd6169..a68ca3ae 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -84,6 +84,34 @@ impl std::ops::Deref for IsSubscribedFn { } type Capabilities = FnvHashSet>; +macro_rules! get_conf_val { + ($s:ident[$var:literal]) => { + $s.extra.get($var).ok_or_else(|| { + MeliError::new(format!( + "Configuration error ({}): IMAP connection requires the field `{}` set", + $s.name.as_str(), + $var + )) + }) + }; + ($s:ident[$var:literal], $default:expr) => { + $s.extra + .get($var) + .map(|v| { + <_>::from_str(v).map_err(|e| { + MeliError::new(format!( + "Configuration error ({}): Invalid value for field `{}`: {}\n{}", + $s.name.as_str(), + $var, + v, + e + )) + }) + }) + .unwrap_or_else(|| Ok($default)) + }; +} + #[derive(Debug)] pub struct UIDStore { uidvalidity: Arc>>, @@ -313,7 +341,9 @@ impl MailBackend for ImapType { let path = { let folders = self.folders.read().unwrap(); - let f_result = folders.values().find(|v| v.name == folder); + let f_result = folders + .values() + .find(|v| v.path == folder || v.name == folder); if f_result .map(|f| !f.permissions.lock().unwrap().create_messages) .unwrap_or(false) @@ -417,34 +447,6 @@ impl MailBackend for ImapType { } } -macro_rules! get_conf_val { - ($s:ident[$var:literal]) => { - $s.extra.get($var).ok_or_else(|| { - MeliError::new(format!( - "Configuration error ({}): IMAP connection requires the field `{}` set", - $s.name.as_str(), - $var - )) - }) - }; - ($s:ident[$var:literal], $default:expr) => { - $s.extra - .get($var) - .map(|v| { - <_>::from_str(v).map_err(|e| { - MeliError::new(format!( - "Configuration error ({}): Invalid value for field `{}`: {}\n{}", - $s.name.as_str(), - $var, - v, - e - )) - }) - }) - .unwrap_or_else(|| Ok($default)) - }; -} - impl ImapType { pub fn new( s: &AccountSettings, @@ -609,4 +611,14 @@ impl ImapType { } Err(MeliError::new(response)) } + + pub fn validate_config(s: &AccountSettings) -> Result<()> { + get_conf_val!(s["server_hostname"])?; + get_conf_val!(s["server_username"])?; + get_conf_val!(s["server_password"])?; + get_conf_val!(s["server_port"], 143)?; + get_conf_val!(s["use_starttls"], false)?; + get_conf_val!(s["danger_accept_invalid_certs"], false)?; + Ok(()) + } } diff --git a/melib/src/backends/maildir/backend.rs b/melib/src/backends/maildir/backend.rs index 519b0fa2..e26ff9e6 100644 --- a/melib/src/backends/maildir/backend.rs +++ b/melib/src/backends/maildir/backend.rs @@ -877,6 +877,25 @@ impl MaildirType { writer.write_all(bytes).unwrap(); return Ok(()); } + + pub fn validate_config(s: &AccountSettings) -> Result<()> { + let root_path = PathBuf::from(s.root_folder()).expand(); + if !root_path.exists() { + return Err(MeliError::new(format!( + "Configuration error ({}): root_path `{}` is not a valid directory.", + s.name(), + s.root_folder.as_str() + ))); + } else if !root_path.is_dir() { + return Err(MeliError::new(format!( + "Configuration error ({}): root_path `{}` is not a directory.", + s.name(), + s.root_folder.as_str() + ))); + } + + Ok(()) + } } fn add_path_to_index( diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index 03ed35d0..77c7b45f 100644 --- a/melib/src/backends/mbox.rs +++ b/melib/src/backends/mbox.rs @@ -432,9 +432,14 @@ impl MailBackend for MboxType { work_context: WorkContext, ) -> Result { let (tx, rx) = channel(); - let mut watcher = watcher(tx, std::time::Duration::from_secs(10)).unwrap(); + let mut watcher = watcher(tx, std::time::Duration::from_secs(10)) + .map_err(|e| e.to_string()) + .map_err(MeliError::new)?; for f in self.folders.lock().unwrap().values() { - watcher.watch(&f.path, RecursiveMode::Recursive).unwrap(); + watcher + .watch(&f.path, RecursiveMode::Recursive) + .map_err(|e| e.to_string()) + .map_err(MeliError::new)?; debug!("watching {:?}", f.path.as_path()); } let index = self.index.clone(); @@ -646,4 +651,16 @@ impl MboxType { */ Ok(Box::new(ret)) } + + pub fn validate_config(s: &AccountSettings) -> Result<()> { + let path = Path::new(s.root_folder.as_str()).expand(); + if !path.exists() { + return Err(MeliError::new(format!( + "\"root_folder\" {} for account {} is not a valid path.", + s.root_folder.as_str(), + s.name() + ))); + } + Ok(()) + } } diff --git a/melib/src/backends/notmuch.rs b/melib/src/backends/notmuch.rs index eb6c5157..52a59dab 100644 --- a/melib/src/backends/notmuch.rs +++ b/melib/src/backends/notmuch.rs @@ -111,11 +111,11 @@ impl NotmuchDb { let mut database: *mut notmuch_database_t = std::ptr::null_mut(); let path = Path::new(s.root_folder.as_str()).expand().to_path_buf(); if !path.exists() { - panic!( + return Err(MeliError::new(format!( "\"root_folder\" {} for account {} is not a valid path.", s.root_folder.as_str(), s.name() - ); + ))); } let path_c = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); @@ -170,6 +170,26 @@ impl NotmuchDb { save_messages_to: None, })) } + + pub fn validate_config( s: &AccountSettings) -> Result<()> { + let path = Path::new(s.root_folder.as_str()).expand().to_path_buf(); + if !path.exists() { + return Err(MeliError::new(format!( + "\"root_folder\" {} for account {} is not a valid path.", + s.root_folder.as_str(), + s.name() + ))); + } + for (k, f) in s.folders.iter() { + if f.extra.get("query").is_none() { + return Err(MeliError::new(format!( + "notmuch folder configuration entry \"{}\" should have a \"query\" value set.", + k + ))); + } + } + Ok(()) + } } impl MailBackend for NotmuchDb { diff --git a/ui/src/conf.rs b/ui/src/conf.rs index 622e3131..2aec8de5 100644 --- a/ui/src/conf.rs +++ b/ui/src/conf.rs @@ -328,12 +328,40 @@ impl FileSettings { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; - let s: std::result::Result = toml::from_str(&contents); - if let Err(e) = s { - return Err(MeliError::new(format!( - "Config file contains errors: {}", - e.to_string() - ))); + let s: FileSettings = toml::from_str(&contents).map_err(|e| { + MeliError::new(format!("Config file contains errors: {}", e.to_string())) + })?; + let backends = melib::backends::Backends::new(); + for (name, acc) in s.accounts { + let FileAccount { + root_folder, + format, + identity, + read_only, + display_name, + subscribed_folders, + folders, + extra, + index_style: _, + cache_type: _, + } = acc; + + let lowercase_format = format.to_lowercase(); + let s = AccountSettings { + name, + root_folder, + format: format.clone(), + identity, + read_only, + display_name, + subscribed_folders, + folders: folders + .into_iter() + .map(|(k, v)| (k, v.folder_conf)) + .collect(), + extra, + }; + backends.validate_config(&lowercase_format, &s)?; } Ok(()) diff --git a/ui/src/conf/shortcuts.rs b/ui/src/conf/shortcuts.rs index e711137b..be87e1c0 100644 --- a/ui/src/conf/shortcuts.rs +++ b/ui/src/conf/shortcuts.rs @@ -10,13 +10,13 @@ pub struct Shortcuts { pub listing: ListingShortcuts, #[serde(default)] pub composing: ComposingShortcuts, - #[serde(default)] + #[serde(default, alias = "compact-listing")] pub compact_listing: CompactListingShortcuts, - #[serde(default)] + #[serde(default, alias = "contact-list")] pub contact_list: ContactListShortcuts, - #[serde(default)] + #[serde(default, alias = "envelope-view")] pub envelope_view: EnvelopeViewShortcuts, - #[serde(default)] + #[serde(default, alias = "thread-view")] pub thread_view: ThreadViewShortcuts, #[serde(default)] pub pager: PagerShortcuts,