diff --git a/Cargo.toml b/Cargo.toml index cd3671605..1c6c72668 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ rusqlite = { version = "0.19", features = ["bundled"] } addr = "0.2.0" r2d2_sqlite = "0.11.0" r2d2 = "0.8.5" +strum = "0.15.0" +strum_macros = "0.15.0" [dev-dependencies] tempfile = "3.0" diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index a82aa51b4..527c37c44 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -91,7 +91,7 @@ pub unsafe extern "C" fn dc_set_config( assert!(!key.is_null(), "invalid key"); let context = &*context; - context::dc_set_config(context, dc_tools::as_str(key), as_opt_str(value)) + config::set(context, dc_tools::as_str(key), as_opt_str(value)) } #[no_mangle] @@ -103,7 +103,7 @@ pub unsafe extern "C" fn dc_get_config( assert!(!key.is_null(), "invalid key"); let context = &*context; - into_cstring(context::dc_get_config(context, dc_tools::as_str(key))) + into_cstring(config::get(context, dc_tools::as_str(key))) } #[no_mangle] diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 5182d7005..3b5d4ee91 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,3 +1,4 @@ +use deltachat::config; use deltachat::constants::*; use deltachat::context::*; use deltachat::dc_array::*; @@ -481,7 +482,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E }, "auth" => { if 0 == S_IS_AUTH { - let is_pw = dc_get_config(context, "mail_pw"); + let is_pw = config::get(context, "mail_pw"); if arg1 == is_pw { S_IS_AUTH = 1; } else { @@ -602,13 +603,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E "set" => { ensure!(!arg1.is_empty(), "Argument missing."); ensure!( - 0 != dc_set_config(context, &arg1, Some(&arg2)), + 0 != config::set(context, &arg1, Some(&arg2)), "Set config failed" ); } "get" => { ensure!(!arg1.is_empty(), "Argument missing."); - let val = dc_get_config(context, &arg1); + let val = config::get(context, &arg1); println!("{}={}", arg1, val); } "info" => { diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 3e42a2aaa..5d887670b 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -17,6 +17,7 @@ use std::borrow::Cow::{self, Borrowed, Owned}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; +use deltachat::config; use deltachat::constants::*; use deltachat::context::*; use deltachat::dc_configure::*; @@ -514,7 +515,7 @@ unsafe fn handle_cmd(line: &str, ctx: Arc>) -> Result { - let addr = dc_get_config(&ctx.read().unwrap(), "addr"); + let addr = config::get(&ctx.read().unwrap(), "addr"); if addr.is_empty() { println!("oauth2: set addr first."); } else { diff --git a/examples/simple.rs b/examples/simple.rs index a7a03df9e..5a5acbaa9 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,6 +5,7 @@ use std::sync::{Arc, RwLock}; use std::{thread, time}; use tempfile::tempdir; +use deltachat::config; use deltachat::constants::Event; use deltachat::context::*; use deltachat::dc_chat::*; @@ -85,8 +86,8 @@ fn main() { let args = std::env::args().collect::>(); assert_eq!(args.len(), 2, "missing password"); let pw = args[1].clone(); - dc_set_config(&ctx, "addr", Some("d@testrun.org")); - dc_set_config(&ctx, "mail_pw", Some(&pw)); + config::set(&ctx, "addr", Some("d@testrun.org")); + config::set(&ctx, "mail_pw", Some(&pw)); dc_configure(&ctx); thread::sleep(duration); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..356a392b5 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,212 @@ +use std::str::FromStr; + +use strum::{EnumProperty, IntoEnumIterator}; +use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString}; + +use crate::constants::DC_VERSION_STR; +use crate::context::Context; +use crate::dc_job::*; +use crate::dc_stock::*; +use crate::dc_tools::*; +use crate::sql; +use crate::x::*; + +/// The available configuration keys. +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty, +)] +#[strum(serialize_all = "snake_case")] +pub enum Config { + Addr, + MailServer, + MailUser, + MailPw, + MailPort, + SendServer, + SendUser, + SendPw, + SendPort, + ServerFlags, + #[strum(props(default = "INBOX"))] + ImapFolder, + Displayname, + Selfstatus, + Selfavatar, + #[strum(props(default = "1"))] + E2eeEnabled, + #[strum(props(default = "1"))] + MdnsEnabled, + InboxWatch, + #[strum(props(default = "1"))] + SentboxWatch, + #[strum(props(default = "1"))] + MvboxWatch, + #[strum(props(default = "1"))] + MvboxMove, + #[strum(props(default = "0"))] + ShowEmails, + SaveMimeHeaders, + ConfiguredAddr, + ConfiguredMailServer, + ConfiguredMailUser, + ConfiguredMailPw, + ConfiguredMailPort, + ConfiguredSendServer, + ConfiguredSendUser, + ConfiguredSendPw, + ConfiguredSendPort, + ConfiguredServerFlags, + Configured, +} + +// deprecated +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter)] +pub enum SysConfig { + #[strum(serialize = "sys.version")] + Version, + #[strum(serialize = "sys.msgsize_max_recommended")] + MsgsizeMaxRecommended, + #[strum(serialize = "sys.config_keys")] + ConfigKeys, +} + +/// Get a configuration key. +/// Returns "" when the key is invalid, or no default was found. +pub fn get(context: &Context, key: impl AsRef) -> String { + let key = key.as_ref(); + + if key.starts_with("sys.") { + return get_sys_config_str(key); + } + + match Config::from_str(key) { + Ok(config_key) => { + let value = match config_key { + Config::Selfavatar => { + let rel_path = sql::get_config(context, &context.sql, key, None); + rel_path.map(|p| { + let v = unsafe { dc_get_abs_path(context, to_cstring(p).as_ptr()) }; + let r = to_string(v); + unsafe { free(v as *mut _) }; + r + }) + } + _ => sql::get_config(context, &context.sql, key, None), + }; + + if value.is_some() { + return value.unwrap(); + } + + // Default values + match config_key { + Config::Selfstatus => { + let s = unsafe { dc_stock_str(context, 13) }; + let res = to_string(s); + unsafe { free(s as *mut _) }; + res + } + _ => config_key + .get_str("default") + .unwrap_or_default() + .to_string(), + } + } + Err(_) => "".into(), + } +} + +fn get_sys_config_str(key: impl AsRef) -> String { + match SysConfig::from_str(key.as_ref()) { + Ok(SysConfig::Version) => std::str::from_utf8(DC_VERSION_STR).unwrap().into(), + Ok(SysConfig::MsgsizeMaxRecommended) => format!("{}", 24 * 1024 * 1024 / 4 * 3), + Ok(SysConfig::ConfigKeys) => get_config_keys_str(), + Err(_) => "".into(), + } +} + +fn get_config_keys_str() -> String { + let keys = Config::iter().fold(String::new(), |mut acc, key| { + acc += key.as_ref(); + acc += " "; + acc + }); + + let sys_keys = SysConfig::iter().fold(String::new(), |mut acc, key| { + acc += key.as_ref(); + acc += " "; + acc + }); + + format!(" {} {} ", keys, sys_keys) +} + +/// Set the given config key. +/// Returns `1` on success and `0` on failure. +pub fn set(context: &Context, key: impl AsRef, value: Option<&str>) -> libc::c_int { + let mut ret = 0; + + // regular keys + match Config::from_str(key.as_ref()) { + Ok(Config::Selfavatar) if value.is_some() => { + let mut rel_path = unsafe { dc_strdup(to_cstring(value.unwrap()).as_ptr()) }; + if 0 != unsafe { dc_make_rel_and_copy(context, &mut rel_path) } { + ret = sql::set_config(context, &context.sql, key, Some(as_str(rel_path))); + } + unsafe { free(rel_path as *mut libc::c_void) }; + } + Ok(Config::InboxWatch) => { + ret = sql::set_config(context, &context.sql, key, value); + unsafe { dc_interrupt_imap_idle(context) }; + } + Ok(Config::SentboxWatch) => { + ret = sql::set_config(context, &context.sql, key, value); + unsafe { dc_interrupt_sentbox_idle(context) }; + } + Ok(Config::MvboxWatch) => { + ret = sql::set_config(context, &context.sql, key, value); + unsafe { dc_interrupt_mvbox_idle(context) }; + } + Ok(Config::Selfstatus) => { + let def = unsafe { dc_stock_str(context, 13) }; + let val = if value.is_none() || value.unwrap() == as_str(def) { + None + } else { + value + }; + + ret = sql::set_config(context, &context.sql, key, val); + unsafe { free(def as *mut libc::c_void) }; + } + Ok(_) => { + ret = sql::set_config(context, &context.sql, key, value); + } + Err(_) => {} + } + ret +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::str::FromStr; + use std::string::ToString; + + #[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!(SysConfig::ConfigKeys.to_string(), "sys.config_keys"); + assert_eq!( + SysConfig::from_str("sys.config_keys"), + Ok(SysConfig::ConfigKeys) + ); + } + + #[test] + fn test_default_prop() { + assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX")); + } +} diff --git a/src/context.rs b/src/context.rs index 3496e2b6d..0d7304ae4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -11,7 +11,6 @@ use crate::dc_lot::dc_lot_t; use crate::dc_move::*; use crate::dc_msg::*; use crate::dc_receive_imf::*; -use crate::dc_stock::*; use crate::dc_tools::*; use crate::imap::*; use crate::key::*; @@ -20,49 +19,6 @@ use crate::sql::{self, Sql}; use crate::types::*; use crate::x::*; -const CONFIG_KEYS: [&'static str; 33] = [ - "addr", - "mail_server", - "mail_user", - "mail_pw", - "mail_port", - "send_server", - "send_user", - "send_pw", - "send_port", - "server_flags", - "imap_folder", - "displayname", - "selfstatus", - "selfavatar", - "e2ee_enabled", - "mdns_enabled", - "inbox_watch", - "sentbox_watch", - "mvbox_watch", - "mvbox_move", - "show_emails", - "save_mime_headers", - "configured_addr", - "configured_mail_server", - "configured_mail_user", - "configured_mail_pw", - "configured_mail_port", - "configured_send_server", - "configured_send_user", - "configured_send_pw", - "configured_send_port", - "configured_server_flags", - "configured", -]; - -// deprecated -const SYS_CONFIG_KEYS: [&'static str; 3] = [ - "sys.version", - "sys.msgsize_max_recommended", - "sys.config_keys", -]; - #[repr(C)] pub struct Context { pub userdata: *mut libc::c_void, @@ -412,129 +368,10 @@ pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char { dc_strdup(*context.blobdir.clone().read().unwrap()) } -pub fn dc_set_config(context: &Context, key: impl AsRef, value: Option<&str>) -> libc::c_int { - let mut ret = 0; - - if !is_settable_config_key(key.as_ref()) { - return 0; - } - - match key.as_ref() { - "selfavatar" if value.is_some() => { - let mut rel_path = unsafe { dc_strdup(to_cstring(value.unwrap()).as_ptr()) }; - if 0 != unsafe { dc_make_rel_and_copy(context, &mut rel_path) } { - ret = sql::set_config(context, &context.sql, key, Some(as_str(rel_path))); - } - unsafe { free(rel_path as *mut libc::c_void) }; - } - "inbox_watch" => { - ret = sql::set_config(context, &context.sql, key, value); - unsafe { dc_interrupt_imap_idle(context) }; - } - "sentbox_watch" => { - ret = sql::set_config(context, &context.sql, key, value); - unsafe { dc_interrupt_sentbox_idle(context) }; - } - "mvbox_watch" => { - ret = sql::set_config(context, &context.sql, key, value); - unsafe { dc_interrupt_mvbox_idle(context) }; - } - "selfstatus" => { - let def = unsafe { dc_stock_str(context, 13) }; - let val = if value.is_none() || value.unwrap() == as_str(def) { - None - } else { - value - }; - - ret = sql::set_config(context, &context.sql, key, val); - unsafe { free(def as *mut libc::c_void) }; - } - _ => { - ret = sql::set_config(context, &context.sql, key, value); - } - } - ret -} - /* ****************************************************************************** * INI-handling, Information ******************************************************************************/ -fn is_settable_config_key(key: impl AsRef) -> bool { - CONFIG_KEYS - .into_iter() - .find(|c| **c == key.as_ref()) - .is_some() -} - -pub fn dc_get_config(context: &Context, key: impl AsRef) -> String { - if key.as_ref().starts_with("sys") { - return get_sys_config_str(key.as_ref()); - } - - if !is_gettable_config_key(key.as_ref()) { - return "".into(); - } - - let value = match key.as_ref() { - "selfavatar" => { - let rel_path = sql::get_config(context, &context.sql, key.as_ref(), None); - rel_path.map(|p| { - let v = unsafe { dc_get_abs_path(context, to_cstring(p).as_ptr()) }; - let r = to_string(v); - unsafe { free(v as *mut _) }; - r - }) - } - _ => sql::get_config(context, &context.sql, key.as_ref(), None), - }; - - if value.is_some() { - return value.unwrap(); - } - - match key.as_ref() { - "e2ee_enabled" => "1".into(), - "mdns_enabled" => "1".into(), - "imap_folder" => "INBOX".into(), - "inbox_watch" => "1".into(), - "sentbox_watch" | "mvbox_watch" | "mvbox_move" => "1".into(), - "show_emails" => "0".into(), - "selfstatus" => { - let s = unsafe { dc_stock_str(context, 13) }; - let res = to_string(s); - unsafe { free(s as *mut _) }; - res - } - _ => "".into(), - } -} - -fn is_gettable_config_key(key: impl AsRef) -> bool { - SYS_CONFIG_KEYS - .into_iter() - .find(|c| **c == key.as_ref()) - .is_some() - || is_settable_config_key(key) -} - -fn get_sys_config_str(key: impl AsRef) -> String { - match key.as_ref() { - "sys.version" => std::str::from_utf8(DC_VERSION_STR).unwrap().into(), - "sys.msgsize_max_recommended" => format!("{}", 24 * 1024 * 1024 / 4 * 3), - "sys.config_keys" => get_config_keys_str(), - _ => "".into(), - } -} - -fn get_config_keys_str() -> String { - let keys = &CONFIG_KEYS[..].join(" "); - let sys_keys = &SYS_CONFIG_KEYS[..].join(" "); - - format!(" {} {} ", keys, sys_keys) -} - pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char { let unset = "0"; let l = dc_loginparam_read(context, &context.sql, ""); diff --git a/src/dc_contact.rs b/src/dc_contact.rs index 93f3a9942..ab249c719 100644 --- a/src/dc_contact.rs +++ b/src/dc_contact.rs @@ -1,7 +1,7 @@ use crate::aheader::EncryptPreference; +use crate::config; use crate::constants::Event; use crate::context::Context; -use crate::context::*; use crate::dc_array::*; use crate::dc_e2ee::*; use crate::dc_loginparam::*; @@ -882,7 +882,7 @@ pub fn dc_contact_get_profile_image(contact: *const dc_contact_t) -> *mut libc:: } if unsafe { (*contact).id } == 1 { - let avatar = dc_get_config(unsafe { (*contact).context }, "selfavatar"); + let avatar = config::get(unsafe { (*contact).context }, "selfavatar"); if !avatar.is_empty() { image_abs = unsafe { dc_strdup(to_cstring(avatar).as_ptr()) }; } diff --git a/src/lib.rs b/src/lib.rs index 354fec256..13ec3304b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ extern crate rusqlite; mod log; pub mod aheader; +pub mod config; pub mod constants; pub mod context; pub mod error; diff --git a/tests/stress.rs b/tests/stress.rs index 6291e4735..02b9c9375 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -6,6 +6,7 @@ use std::ffi::CString; use mmime::mailimf_types::*; use tempfile::{tempdir, TempDir}; +use deltachat::config; use deltachat::constants::*; use deltachat::context::*; use deltachat::dc_array::*; @@ -247,7 +248,7 @@ unsafe fn stress_functions(context: &Context) { free(fn1 as *mut libc::c_void); } - let res = dc_get_config(context, "sys.config_keys"); + let res = config::get(context, "sys.config_keys"); assert!(!res.contains(" probably_never_a_key ")); assert!(res.contains(" addr "));