From f6cdc7b49855f82bf8f3c3e7aa5e8269506f7f58 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 29 Oct 2019 22:17:26 +0100 Subject: [PATCH] Introduce enum API for configuration This introduces a new Context::get_config_item() and Context::set_config_item() API which operates on two enums: ConfigKey and ConfigItem. The latter has variats which hold the actual configuration as data. This allows to introduce stronger typing of the configuration items. This commit however does not yet do this, it adapts the existing APIs to use the new API leaving the stronger typing for future commits. This should make the changes more digestible and reviewable. --- deltachat-ffi/src/lib.rs | 10 +- src/config.rs | 676 ++++++++++++++++++++++++++++++++------- 2 files changed, 560 insertions(+), 126 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 2391ce6d9..5163e1776 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -337,12 +337,16 @@ pub unsafe extern "C" fn dc_get_config( return "".strdup(); } let ffi_context = &*context; - match config::Config::from_str(&to_string_lossy(key)) { + match config::Config::from_key_str(&to_string_lossy(key)) { Ok(key) => ffi_context .with_inner(|ctx| ctx.get_config(key).unwrap_or_default().strdup()) .unwrap_or_else(|_| "".strdup()), - Err(_) => { - ffi_context.error("dc_get_config(): invalid key"); + Err(err) => { + ffi_context.error(&format!( + "dc_get_config(): invalid key {} ({})", + &to_string_lossy(key), + err + )); "".strdup() } } diff --git a/src/config.rs b/src/config.rs index 759dc354a..71a967be5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,162 +1,523 @@ //! # Key-value configuration management -use strum::{EnumProperty, IntoEnumIterator}; -use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString}; +use std::str::FromStr; + +use strum::IntoEnumIterator; +use strum_macros::{AsRefStr, Display, EnumDiscriminants, EnumIter, EnumString}; use crate::blob::BlobObject; use crate::constants::DC_VERSION_STR; use crate::context::Context; -use crate::dc_tools::*; use crate::error::Error; use crate::job::*; use crate::stock::StockMessage; -/// The available configuration keys. +/// The available configuration items. +/// +/// There is also an enum called `ConfigKey` which has the same enum +/// variants but does not carry any data. #[derive( - Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty, + Debug, + Clone, + Display, + PartialEq, + Eq, + EnumDiscriminants, + EnumProperty, + EnumString, + EnumIter, + AsRefStr, )] #[strum(serialize_all = "snake_case")] -pub enum Config { - Addr, - MailServer, - MailUser, - MailPw, - MailPort, - ImapCertificateChecks, - SendServer, - SendUser, - SendPw, - SendPort, - SmtpCertificateChecks, - ServerFlags, - #[strum(props(default = "INBOX"))] - ImapFolder, - Displayname, - Selfstatus, - Selfavatar, - #[strum(props(default = "0"))] - BccSelf, - #[strum(props(default = "1"))] - E2eeEnabled, - #[strum(props(default = "1"))] - MdnsEnabled, - #[strum(props(default = "1"))] - InboxWatch, - #[strum(props(default = "1"))] - SentboxWatch, - #[strum(props(default = "1"))] - MvboxWatch, - #[strum(props(default = "1"))] - MvboxMove, - #[strum(props(default = "0"))] // also change ShowEmails.default() on changes - ShowEmails, - SaveMimeHeaders, - ConfiguredAddr, - ConfiguredMailServer, - ConfiguredMailUser, - ConfiguredMailPw, - ConfiguredMailPort, - ConfiguredMailSecurity, - ConfiguredImapCertificateChecks, - ConfiguredSendServer, - ConfiguredSendUser, - ConfiguredSendPw, - ConfiguredSendPort, - ConfiguredSmtpCertificateChecks, - ConfiguredServerFlags, - ConfiguredSendSecurity, - ConfiguredE2EEEnabled, - Configured, - // Deprecated - #[strum(serialize = "sys.version")] - SysVersion, - #[strum(serialize = "sys.msgsize_max_recommended")] - SysMsgsizeMaxRecommended, +#[strum_discriminants( + name(ConfigKey), + derive(Display, EnumString), + strum(serialize_all = "snake_case") +)] +pub enum ConfigItem { + Addr(String), + BccSelf(String), + Configured(String), + ConfiguredAddr(String), + ConfiguredE2EEEnabled(String), + ConfiguredImapCertificateChecks(String), + ConfiguredMailPort(String), + ConfiguredMailPw(String), + ConfiguredMailSecurity(String), + ConfiguredMailServer(String), + ConfiguredMailUser(String), + ConfiguredSendPort(String), + ConfiguredSendPw(String), + ConfiguredSendSecurity(String), + ConfiguredSendServer(String), + ConfiguredSendUser(String), + ConfiguredServerFlags(String), + ConfiguredSmtpCertificateChecks(String), + Displayname(String), + E2eeEnabled(String), + ImapCertificateChecks(String), + ImapFolder(String), + InboxWatch(String), + MailPort(String), + MailPw(String), + MailServer(String), + MailUser(String), + MdnsEnabled(String), + MvboxMove(String), + MvboxWatch(String), + SaveMimeHeaders(String), + Selfavatar(String), + Selfstatus(String), + SendPort(String), + SendPw(String), + SendServer(String), + SendUser(String), + SentboxWatch(String), + ServerFlags(String), + ShowEmails(String), + SmtpCertificateChecks(String), #[strum(serialize = "sys.config_keys")] - SysConfigKeys, + SysConfigKeys(String), + #[strum(serialize = "sys.msgsize_max_recommended")] + SysMsgsizeMaxRecommended(String), + #[strum(serialize = "sys.version")] + SysVersion(String), } -impl Context { - /// Get a configuration key. Returns `None` if no value is set, and no default value found. - pub fn get_config(&self, key: Config) -> Option { - let value = match key { - Config::Selfavatar => { - let rel_path = self.sql.get_raw_config(self, key); - rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned()) - } - Config::SysVersion => Some((&*DC_VERSION_STR).clone()), - Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)), - Config::SysConfigKeys => Some(get_config_keys_string()), - _ => self.sql.get_raw_config(self, key), - }; +// Transitional: support the old name for ConfigKey. +pub type Config = ConfigKey; - if value.is_some() { - return value; - } - - // Default values - match key { - Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()), - _ => key.get_str("default").map(|s| s.to_string()), +impl ConfigKey { + /// Creates a [ConfigKey] from a string. + /// + /// Use this rather than [ConfigKey::from_str]. + pub fn from_key_str(s: &str) -> Result { + match s { + "sys.config_keys" => Ok(ConfigKey::SysConfigKeys), + "sys.msgsize_max_recommended" => Ok(ConfigKey::SysMsgsizeMaxRecommended), + "sys.version" => Ok(ConfigKey::SysVersion), + _ => ConfigKey::from_str(s), } } + /// Default values for configuration options. + /// + /// These are returned in case there is no value stored in the + /// database. + fn default_item(&self, context: &Context) -> Option { + match self { + Self::BccSelf => Some(ConfigItem::BccSelf(String::from("1"))), + Self::E2eeEnabled => Some(ConfigItem::E2eeEnabled(String::from("1"))), + Self::ImapFolder => Some(ConfigItem::ImapFolder(String::from("INBOX"))), + Self::InboxWatch => Some(ConfigItem::InboxWatch(String::from("1"))), + Self::MdnsEnabled => Some(ConfigItem::MdnsEnabled(String::from("1"))), + Self::MvboxMove => Some(ConfigItem::MvboxMove(String::from("1"))), + Self::MvboxWatch => Some(ConfigItem::MvboxWatch(String::from("1"))), + Self::Selfstatus => Some(ConfigItem::Selfstatus( + context.stock_str(StockMessage::StatusLine).into_owned(), + )), + Self::SentboxWatch => Some(ConfigItem::SentboxWatch(String::from("1"))), + Self::ShowEmails => Some(ConfigItem::ShowEmails(String::from("0"))), + Self::SysConfigKeys => Some(ConfigItem::SysConfigKeys(get_config_keys_string())), + Self::SysMsgsizeMaxRecommended => Some(ConfigItem::SysMsgsizeMaxRecommended(format!( + "{}", + 24 * 1024 * 1024 / 4 * 3 + ))), + Self::SysVersion => Some(ConfigItem::SysVersion((&*DC_VERSION_STR).clone())), + _ => None, + } + } +} + +impl rusqlite::types::ToSql for ConfigItem { + fn to_sql(&self) -> rusqlite::Result { + let sql_obj = match self { + ConfigItem::Selfavatar(value) => { + let rel_path = std::fs::canonicalize(value) + .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?; + rusqlite::types::Value::Text(rel_path.to_string_lossy().into_owned()) + } + ConfigItem::Addr(value) + | ConfigItem::BccSelf(value) + | ConfigItem::Configured(value) + | ConfigItem::ConfiguredAddr(value) + | ConfigItem::ConfiguredE2EEEnabled(value) + | ConfigItem::ConfiguredImapCertificateChecks(value) + | ConfigItem::ConfiguredMailPort(value) + | ConfigItem::ConfiguredMailPw(value) + | ConfigItem::ConfiguredMailSecurity(value) + | ConfigItem::ConfiguredMailServer(value) + | ConfigItem::ConfiguredMailUser(value) + | ConfigItem::ConfiguredSendPort(value) + | ConfigItem::ConfiguredSendPw(value) + | ConfigItem::ConfiguredSendSecurity(value) + | ConfigItem::ConfiguredSendServer(value) + | ConfigItem::ConfiguredSendUser(value) + | ConfigItem::ConfiguredServerFlags(value) + | ConfigItem::ConfiguredSmtpCertificateChecks(value) + | ConfigItem::Displayname(value) + | ConfigItem::E2eeEnabled(value) + | ConfigItem::ImapCertificateChecks(value) + | ConfigItem::ImapFolder(value) + | ConfigItem::InboxWatch(value) + | ConfigItem::MailPort(value) + | ConfigItem::MailPw(value) + | ConfigItem::MailServer(value) + | ConfigItem::MailUser(value) + | ConfigItem::MdnsEnabled(value) + | ConfigItem::MvboxMove(value) + | ConfigItem::MvboxWatch(value) + | ConfigItem::SaveMimeHeaders(value) + | ConfigItem::Selfstatus(value) + | ConfigItem::SendPort(value) + | ConfigItem::SendPw(value) + | ConfigItem::SendServer(value) + | ConfigItem::SendUser(value) + | ConfigItem::SentboxWatch(value) + | ConfigItem::ServerFlags(value) + | ConfigItem::ShowEmails(value) + | ConfigItem::SmtpCertificateChecks(value) + | ConfigItem::SysConfigKeys(value) + | ConfigItem::SysMsgsizeMaxRecommended(value) + | ConfigItem::SysVersion(value) => rusqlite::types::Value::Text(value.to_string()), + }; + let out = rusqlite::types::ToSqlOutput::Owned(sql_obj); + Ok(out) + } +} + +impl Context { + /// Gets a configuration option. + /// + /// If there is no value set in the database returns the default + /// value from [ConfigKey::default_item]. + pub fn get_config_item(&self, key: ConfigKey) -> Option { + let res = self.sql.query_row( + "SELECT value FROM config WHERE keyname=?;", + params![key.to_string()], + |row| { + row.get_raw_checked(0) + .map(|valref| self.sql_raw_to_config_item(key, valref)) + }, + ); + match res { + Ok(item) => item, + Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => key.default_item(self), + Err(err) => { + warn!(self, "get_config: Failed SQL query: {}", err); + None + } + } + } + + // This is effectively the FromSql impl for ConfigItem, but we + // need to know the ConfigKey which the trait does not give us. + fn sql_raw_to_config_item( + &self, + key: ConfigKey, + raw: rusqlite::types::ValueRef, + ) -> Option { + let to_string = |raw: rusqlite::types::ValueRef| -> Option { + raw.as_str() + .map_err(|err| { + warn!(self, "ConfigItem {}; not a string: {}", key, err); + }) + .map(|s| s.to_string()) + .ok() + }; + match key { + ConfigKey::Addr => to_string(raw).map(|val| ConfigItem::Addr(val)), + ConfigKey::BccSelf => to_string(raw).map(|val| ConfigItem::BccSelf(val)), + ConfigKey::Configured => to_string(raw).map(|val| ConfigItem::Configured(val)), + ConfigKey::ConfiguredAddr => to_string(raw).map(|val| ConfigItem::ConfiguredAddr(val)), + ConfigKey::ConfiguredE2EEEnabled => { + to_string(raw).map(|val| ConfigItem::ConfiguredE2EEEnabled(val)) + } + ConfigKey::ConfiguredImapCertificateChecks => { + to_string(raw).map(|val| ConfigItem::ConfiguredImapCertificateChecks(val)) + } + ConfigKey::ConfiguredMailPort => { + to_string(raw).map(|val| ConfigItem::ConfiguredMailPort(val)) + } + ConfigKey::ConfiguredMailPw => { + to_string(raw).map(|val| ConfigItem::ConfiguredMailPw(val)) + } + ConfigKey::ConfiguredMailSecurity => { + to_string(raw).map(|val| ConfigItem::ConfiguredMailSecurity(val)) + } + ConfigKey::ConfiguredMailServer => { + to_string(raw).map(|val| ConfigItem::ConfiguredMailServer(val)) + } + ConfigKey::ConfiguredMailUser => { + to_string(raw).map(|val| ConfigItem::ConfiguredMailUser(val)) + } + ConfigKey::ConfiguredSendPort => { + to_string(raw).map(|val| ConfigItem::ConfiguredSendPort(val)) + } + ConfigKey::ConfiguredSendPw => { + to_string(raw).map(|val| ConfigItem::ConfiguredSendPw(val)) + } + ConfigKey::ConfiguredSendSecurity => { + to_string(raw).map(|val| ConfigItem::ConfiguredSendSecurity(val)) + } + ConfigKey::ConfiguredSendServer => { + to_string(raw).map(|val| ConfigItem::ConfiguredSendServer(val)) + } + ConfigKey::ConfiguredSendUser => { + to_string(raw).map(|val| ConfigItem::ConfiguredSendUser(val)) + } + ConfigKey::ConfiguredServerFlags => { + to_string(raw).map(|val| ConfigItem::ConfiguredServerFlags(val)) + } + ConfigKey::ConfiguredSmtpCertificateChecks => { + to_string(raw).map(|val| ConfigItem::ConfiguredSmtpCertificateChecks(val)) + } + ConfigKey::Displayname => to_string(raw).map(|val| ConfigItem::Displayname(val)), + ConfigKey::E2eeEnabled => to_string(raw).map(|val| ConfigItem::E2eeEnabled(val)), + ConfigKey::ImapCertificateChecks => { + to_string(raw).map(|val| ConfigItem::ImapCertificateChecks(val)) + } + ConfigKey::ImapFolder => to_string(raw).map(|val| ConfigItem::ImapFolder(val)), + ConfigKey::InboxWatch => to_string(raw).map(|val| ConfigItem::InboxWatch(val)), + ConfigKey::MailPort => to_string(raw).map(|val| ConfigItem::MailPort(val)), + ConfigKey::MailPw => to_string(raw).map(|val| ConfigItem::MailPw(val)), + ConfigKey::MailServer => to_string(raw).map(|val| ConfigItem::MailServer(val)), + ConfigKey::MailUser => to_string(raw).map(|val| ConfigItem::MailUser(val)), + ConfigKey::MdnsEnabled => to_string(raw).map(|val| ConfigItem::MdnsEnabled(val)), + ConfigKey::MvboxMove => to_string(raw).map(|val| ConfigItem::MvboxMove(val)), + ConfigKey::MvboxWatch => to_string(raw).map(|val| ConfigItem::MvboxWatch(val)), + ConfigKey::SaveMimeHeaders => { + to_string(raw).map(|val| ConfigItem::SaveMimeHeaders(val)) + } + ConfigKey::Selfavatar => to_string(raw).map(|val| ConfigItem::Selfavatar(val)), + ConfigKey::Selfstatus => to_string(raw).map(|val| ConfigItem::Selfstatus(val)), + ConfigKey::SendPort => to_string(raw).map(|val| ConfigItem::SendPort(val)), + ConfigKey::SendPw => to_string(raw).map(|val| ConfigItem::SendPw(val)), + ConfigKey::SendServer => to_string(raw).map(|val| ConfigItem::SendServer(val)), + ConfigKey::SendUser => to_string(raw).map(|val| ConfigItem::SendUser(val)), + ConfigKey::SentboxWatch => to_string(raw).map(|val| ConfigItem::SentboxWatch(val)), + ConfigKey::ServerFlags => to_string(raw).map(|val| ConfigItem::ServerFlags(val)), + ConfigKey::ShowEmails => to_string(raw).map(|val| ConfigItem::ShowEmails(val)), + ConfigKey::SmtpCertificateChecks => { + to_string(raw).map(|val| ConfigItem::SmtpCertificateChecks(val)) + } + ConfigKey::SysConfigKeys => to_string(raw).map(|val| ConfigItem::SysConfigKeys(val)), + ConfigKey::SysMsgsizeMaxRecommended => { + to_string(raw).map(|val| ConfigItem::SysMsgsizeMaxRecommended(val)) + } + ConfigKey::SysVersion => to_string(raw).map(|val| ConfigItem::SysVersion(val)), + } + } + + /// Stores a configuration item in the database. + /// + /// # Errors + /// + /// You can not store any of the [ConfigItem::SysVersion], + /// [ConfigItem::SysMsgsizemaxrecommended] or + /// [ConfigItem::SysConfigkeys] variants. + pub fn set_config_item(&self, item: ConfigItem) -> Result { + match item { + ConfigItem::SysConfigKeys(_) + | ConfigItem::SysMsgsizeMaxRecommended(_) + | ConfigItem::SysVersion(_) => bail!("Can not set config item {}", item), + _ => (), + } + // Would prefer to use INSERT OR REPLACE but this needs a + // uniqueness constraint on the keyname column which does not + // yet exist. + if self.sql.exists( + "SELECT value FROM config WHERE keyname=?;", + params![item.to_string()], + )? { + self.sql.execute( + "UPDATE config SET value=? WHERE keyname=?", + params![item, item.to_string()], + )?; + } else { + self.sql.execute( + "INSERT INTO config (keyname, value) VALUES (?, ?)", + params![item.to_string(), item], + )?; + } + match item { + ConfigItem::InboxWatch(_) => interrupt_inbox_idle(self, true), + ConfigItem::SentboxWatch(_) => interrupt_sentbox_idle(self), + ConfigItem::MvboxWatch(_) => interrupt_mvbox_idle(self), + _ => (), + }; + Ok(item) + } + + /// Deletes a configuration option. + /// + /// Returns `true` if the option was deleted, `false` if it wasn't + /// present in the first place. + pub fn del_config_item(&self, key: ConfigKey) -> Result { + match self.sql.execute( + "DELETE FROM config WHERE keyname=?;", + params![key.to_string()], + ) { + Ok(0) => Ok(false), + Ok(_) => Ok(true), + Err(err) => Err(err), + } + } + + /// Transitional: migrate to get_config_item. + /// + /// This will migrate to the FFI once nothing in the core uses + /// this anymore. + pub fn get_config(&self, key: Config) -> Option { + if let Some(item) = self.get_config_item(key) { + let value = match item { + ConfigItem::Addr(value) + | ConfigItem::BccSelf(value) + | ConfigItem::Configured(value) + | ConfigItem::ConfiguredAddr(value) + | ConfigItem::ConfiguredE2EEEnabled(value) + | ConfigItem::ConfiguredImapCertificateChecks(value) + | ConfigItem::ConfiguredMailPort(value) + | ConfigItem::ConfiguredMailPw(value) + | ConfigItem::ConfiguredMailSecurity(value) + | ConfigItem::ConfiguredMailServer(value) + | ConfigItem::ConfiguredMailUser(value) + | ConfigItem::ConfiguredSendPort(value) + | ConfigItem::ConfiguredSendPw(value) + | ConfigItem::ConfiguredSendSecurity(value) + | ConfigItem::ConfiguredSendServer(value) + | ConfigItem::ConfiguredSendUser(value) + | ConfigItem::ConfiguredServerFlags(value) + | ConfigItem::ConfiguredSmtpCertificateChecks(value) + | ConfigItem::Displayname(value) + | ConfigItem::E2eeEnabled(value) + | ConfigItem::ImapCertificateChecks(value) + | ConfigItem::ImapFolder(value) + | ConfigItem::InboxWatch(value) + | ConfigItem::MailPort(value) + | ConfigItem::MailPw(value) + | ConfigItem::MailServer(value) + | ConfigItem::MailUser(value) + | ConfigItem::MdnsEnabled(value) + | ConfigItem::MvboxMove(value) + | ConfigItem::MvboxWatch(value) + | ConfigItem::SaveMimeHeaders(value) + | ConfigItem::Selfavatar(value) + | ConfigItem::Selfstatus(value) + | ConfigItem::SendPort(value) + | ConfigItem::SendPw(value) + | ConfigItem::SendServer(value) + | ConfigItem::SendUser(value) + | ConfigItem::SentboxWatch(value) + | ConfigItem::ServerFlags(value) + | ConfigItem::ShowEmails(value) + | ConfigItem::SmtpCertificateChecks(value) + | ConfigItem::SysConfigKeys(value) + | ConfigItem::SysMsgsizeMaxRecommended(value) + | ConfigItem::SysVersion(value) => value, + }; + Some(value) + } else { + None + } + } + + /// Transitional: migrate to get_config_item. pub fn get_config_int(&self, key: Config) -> i32 { self.get_config(key) .and_then(|s| s.parse().ok()) .unwrap_or_default() } + /// Transitional: migrate to get_config_item. pub fn get_config_bool(&self, key: Config) -> bool { self.get_config_int(key) != 0 } /// Set the given config key. - /// If `None` is passed as a value the value is cleared and set to the default if there is one. + /// + /// Transitional: migrate to set_config_item/del_config_item. + /// + /// If `None` is passed as a value the value is cleared and set to + /// the default if there is one. pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> { - match key { - Config::Selfavatar if value.is_some() => { - let blob = BlobObject::create_from_path(&self, value.unwrap())?; - self.sql.set_raw_config(self, key, Some(blob.as_name())) - } - Config::InboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); - interrupt_inbox_idle(self, true); - ret - } - Config::SentboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); - interrupt_sentbox_idle(self); - ret - } - Config::MvboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); - interrupt_mvbox_idle(self); - ret - } + let maybe_val = match key { Config::Selfstatus => { let def = self.stock_str(StockMessage::StatusLine); - let val = if value.is_none() || value.unwrap() == def { + if value.is_none() || value.unwrap() == def { None } else { value - }; - - self.sql.set_raw_config(self, key, val) + } } - _ => self.sql.set_raw_config(self, key, value), + _ => value, + }; + if let Some(val) = maybe_val { + let v = val.to_string(); + let item = match key { + ConfigKey::Addr => ConfigItem::Addr(v), + ConfigKey::BccSelf => ConfigItem::BccSelf(v), + ConfigKey::Configured => ConfigItem::Configured(v), + ConfigKey::ConfiguredAddr => ConfigItem::ConfiguredAddr(v), + ConfigKey::ConfiguredE2EEEnabled => ConfigItem::ConfiguredE2EEEnabled(v), + ConfigKey::ConfiguredImapCertificateChecks => { + ConfigItem::ConfiguredImapCertificateChecks(v) + } + ConfigKey::ConfiguredMailPort => ConfigItem::ConfiguredMailPort(v), + ConfigKey::ConfiguredMailPw => ConfigItem::ConfiguredMailPw(v), + ConfigKey::ConfiguredMailSecurity => ConfigItem::ConfiguredMailSecurity(v), + ConfigKey::ConfiguredMailServer => ConfigItem::ConfiguredMailServer(v), + ConfigKey::ConfiguredMailUser => ConfigItem::ConfiguredMailUser(v), + ConfigKey::ConfiguredSendPort => ConfigItem::ConfiguredSendPort(v), + ConfigKey::ConfiguredSendPw => ConfigItem::ConfiguredSendPw(v), + ConfigKey::ConfiguredSendSecurity => ConfigItem::ConfiguredSendSecurity(v), + ConfigKey::ConfiguredSendServer => ConfigItem::ConfiguredSendServer(v), + ConfigKey::ConfiguredSendUser => ConfigItem::ConfiguredSendUser(v), + ConfigKey::ConfiguredServerFlags => ConfigItem::ConfiguredServerFlags(v), + ConfigKey::ConfiguredSmtpCertificateChecks => { + ConfigItem::ConfiguredSmtpCertificateChecks(v) + } + ConfigKey::Displayname => ConfigItem::Displayname(v), + ConfigKey::E2eeEnabled => ConfigItem::E2eeEnabled(v), + ConfigKey::ImapCertificateChecks => ConfigItem::ImapCertificateChecks(v), + ConfigKey::ImapFolder => ConfigItem::ImapFolder(v), + ConfigKey::InboxWatch => ConfigItem::InboxWatch(v), + ConfigKey::MailPort => ConfigItem::MailPort(v), + ConfigKey::MailPw => ConfigItem::MailPw(v), + ConfigKey::MailServer => ConfigItem::MailServer(v), + ConfigKey::MailUser => ConfigItem::MailUser(v), + ConfigKey::MdnsEnabled => ConfigItem::MdnsEnabled(v), + ConfigKey::MvboxMove => ConfigItem::MvboxMove(v), + ConfigKey::MvboxWatch => ConfigItem::MvboxWatch(v), + ConfigKey::SaveMimeHeaders => ConfigItem::SaveMimeHeaders(v), + ConfigKey::Selfavatar => ConfigItem::Selfavatar(v), + ConfigKey::Selfstatus => ConfigItem::Selfstatus(v), + ConfigKey::SendPort => ConfigItem::SendPort(v), + ConfigKey::SendPw => ConfigItem::SendPw(v), + ConfigKey::SendServer => ConfigItem::SendServer(v), + ConfigKey::SendUser => ConfigItem::SendUser(v), + ConfigKey::SentboxWatch => ConfigItem::SentboxWatch(v), + ConfigKey::ServerFlags => ConfigItem::ServerFlags(v), + ConfigKey::ShowEmails => ConfigItem::ShowEmails(v), + ConfigKey::SmtpCertificateChecks => ConfigItem::SmtpCertificateChecks(v), + ConfigKey::SysConfigKeys => ConfigItem::SysConfigKeys(v), + ConfigKey::SysMsgsizeMaxRecommended => ConfigItem::SysMsgsizeMaxRecommended(v), + ConfigKey::SysVersion => ConfigItem::SysVersion(v), + }; + self.set_config_item(item).map(|_| ()) + } else { + self.del_config_item(key).map(|_| ()) } } } /// Returns all available configuration keys concated together. fn get_config_keys_string() -> String { - let keys = Config::iter().fold(String::new(), |mut acc, key| { + let keys = ConfigItem::iter().fold(String::new(), |mut acc, key| { acc += key.as_ref(); acc += " "; acc }); - format!(" {} ", keys) } @@ -169,21 +530,90 @@ mod tests { use crate::test_utils::*; - #[test] - fn test_to_string() { - assert_eq!(Config::MailServer.to_string(), "mail_server"); - assert_eq!(Config::from_str("mail_server"), Ok(Config::MailServer)); - - assert_eq!(Config::SysConfigKeys.to_string(), "sys.config_keys"); - assert_eq!( - Config::from_str("sys.config_keys"), - Ok(Config::SysConfigKeys) - ); + impl ConfigKey { + fn to_key_string(&self) -> String { + let s = self.to_string(); + if s.starts_with("sys_") { + s.replacen("sys_", "sys.", 1) + } else { + s + } + } } #[test] - fn test_default_prop() { - assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX")); + fn test_to_string() { + assert_eq!( + ConfigItem::MailServer(Default::default()).to_string(), + "mail_server" + ); + assert_eq!( + ConfigItem::from_str("mail_server"), + Ok(ConfigItem::MailServer(Default::default())) + ); + + assert_eq!( + ConfigItem::SysConfigKeys(Default::default()).to_string(), + "sys.config_keys" + ); + assert_eq!( + ConfigItem::from_str("sys.config_keys"), + Ok(ConfigItem::SysConfigKeys(Default::default())) + ); + + // The ConfigItem.to_string() is used as key in the SQL table + // on set operations and ConfigKey.to_string() is the key used + // on get operations. Therefore we want to make sure they are + // the same keys. + for item in ConfigItem::iter() { + let name = item.to_string(); + let key = ConfigKey::from_key_str(&name).unwrap(); + assert_eq!(name, key.to_key_string()); + } + } + + #[test] + fn test_config_item() { + let t = test_context(Some(Box::new(logging_cb))); + + // An item which has a default. + let opt = t.ctx.get_config_item(ConfigKey::ImapFolder); + assert_eq!(opt, Some(ConfigItem::ImapFolder("INBOX".into()))); + + // Set a different value. + t.ctx + .set_config_item(ConfigItem::ImapFolder("DeltaChat".into())) + .unwrap(); + let opt = t.ctx.get_config_item(ConfigKey::ImapFolder); + assert_eq!(opt, Some(ConfigItem::ImapFolder("DeltaChat".into()))); + + // Set another value, testing update. + t.ctx + .set_config_item(ConfigItem::ImapFolder("Chat".into())) + .unwrap(); + let opt = t.ctx.get_config_item(ConfigKey::ImapFolder); + assert_eq!(opt, Some(ConfigItem::ImapFolder("Chat".into()))); + + // Reset to the default. + t.ctx.del_config_item(ConfigKey::ImapFolder).unwrap(); + let opt = t.ctx.get_config_item(ConfigKey::ImapFolder); + assert_eq!(opt, Some(ConfigItem::ImapFolder("INBOX".into()))); + + // An item without default. + let opt = t.ctx.get_config_item(ConfigKey::Addr); + assert!(opt.is_none()); + + // Set the item. + t.ctx + .set_config_item(ConfigItem::Addr("me@example.com".into())) + .unwrap(); + let opt = t.ctx.get_config_item(ConfigKey::Addr); + assert_eq!(opt, Some(ConfigItem::Addr("me@example.com".into()))); + + // Delete the item. + t.ctx.del_config_item(ConfigKey::Addr).unwrap(); + let opt = t.ctx.get_config_item(ConfigKey::Addr); + assert!(opt.is_none()); } #[test]