Compare commits

...

3 Commits

Author SHA1 Message Date
Floris Bruynooghe
0eab93257c Change selfavatar type to BlobObject
This changes the type of ConfigItem::Selfavatar to a BlobObject.  This
is what also happened on master because there was a bug with how
selfavatar was not being correctly handled as a blob.

As a side-effect this also adds a lifetime to the ConfigItem object.
This resulted in some strum derives no longer working which in itself
resulted in some simplifications between ConfigKey and ConfigItem:

ConfigKey::to_key_string & ConfigKey::from_key_string are used to
create the SQL keynames.  The ConfigItem is converted to its
ConfigKey discriminant in the SQL write path which avoids the
duplicate source for SQL keyname.

FFI-level tests are added for testing the copy behaviour since that is
now effectively a problem of the FFI, in Rust this is impossible to
have thanks to the types.
2019-11-25 21:07:02 +01:00
Floris Bruynooghe
e6d3bd284b Convert InboxWatch config to a bool
This introduces InboxWatch as a bool rather than the old string type.
Introducing the bool handling in the config machinery and updating all
the users to use the get_config_item/set_config_item API.
2019-11-25 21:07:02 +01:00
Floris Bruynooghe
f6cdc7b498 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.
2019-11-25 21:02:37 +01:00
6 changed files with 703 additions and 141 deletions

View File

@@ -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()
}
}

View File

@@ -1,4 +1,7 @@
from __future__ import print_function
import pytest
from deltachat import capi, cutil, const, set_context_callback, clear_context_callback
from deltachat.capi import ffi
from deltachat.capi import lib
@@ -155,3 +158,41 @@ def test_is_open_actually_open(tmpdir):
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
assert lib.dc_is_open(ctx) == 1
class TestConfig:
@pytest.fixture
def ctx(self, tmpdir):
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("test.db")
lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL)
assert lib.dc_is_open(ctx) == 1
return ctx
def test_selfavatar_copy(self, ctx, tmpdir):
# Setting an avatar outside of the blobdir should copy it into
# the blobdir.
avatar_src = tmpdir.join("avatar.jpg")
avatar_src.write("avatar")
lib.dc_set_config(ctx, b"selfavatar",
cutil.as_dc_charpointer(avatar_src.strpath))
avatar_blob = tmpdir.join("test.db-blobs/avatar.jpg")
assert avatar_blob.exists()
assert avatar_blob.read() == "avatar"
avatar_cfg = cutil.from_dc_charpointer(
lib.dc_get_config(ctx, b"selfavatar"))
assert avatar_cfg == avatar_blob
def test_selfavatar_nocopy(self, ctx, tmpdir):
# Setting an avatar already in the blobdir should not copy it.
avatar_src = tmpdir.join("test.db-blobs/avatar.jpg")
avatar_src.write("avatar")
lib.dc_set_config(ctx, b"selfavatar",
cutil.as_dc_charpointer(avatar_src.strpath))
avatar_cfg = cutil.from_dc_charpointer(
lib.dc_get_config(ctx, b"selfavatar"))
assert avatar_src == avatar_cfg

View File

@@ -55,7 +55,6 @@ deps =
commands =
sphinx-build -w toxdoc-warnings.log -b html . _build/html
[testenv:lintdoc]
skipsdist = True
usedevelop = True
@@ -66,14 +65,12 @@ commands =
{[testenv:lint]commands}
{[testenv:doc]commands}
[pytest]
addopts = -v -rs
python_files = tests/test_*.py
norecursedirs = .tox
addopts = -v -ra
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true
timeout = 60
timeout = 60
timeout_method = thread
[flake8]

View File

@@ -1,162 +1,575 @@
//! # 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.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
)]
/// 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, Display, PartialEq, Eq, EnumDiscriminants, EnumProperty, 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(serialize = "sys.config_keys")]
SysConfigKeys,
#[strum_discriminants(
name(ConfigKey),
derive(Display, EnumString, EnumIter),
strum(serialize_all = "snake_case")
)]
pub enum ConfigItem<'a> {
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(bool),
MailPort(String),
MailPw(String),
MailServer(String),
MailUser(String),
MdnsEnabled(String),
MvboxMove(String),
MvboxWatch(String),
SaveMimeHeaders(String),
Selfavatar(BlobObject<'a>),
Selfstatus(String),
SendPort(String),
SendPw(String),
SendServer(String),
SendUser(String),
SentboxWatch(String),
ServerFlags(String),
ShowEmails(String),
SmtpCertificateChecks(String),
SysConfigKeys(String),
SysMsgsizeMaxRecommended(String),
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<String> {
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<ConfigKey, strum::ParseError> {
match s {
"sys.config_keys" => Ok(ConfigKey::SysConfigKeys),
"sys.msgsize_max_recommended" => Ok(ConfigKey::SysMsgsizeMaxRecommended),
"sys.version" => Ok(ConfigKey::SysVersion),
_ => ConfigKey::from_str(s),
}
}
/// Returns a string version of the [ConfigKey].
///
/// Use this rather than [ConfigKey::to_string].
pub fn to_key_string(&self) -> String {
let mut s = self.to_string();
if s.starts_with("sys_") {
s = s.replacen("sys_", "sys.", 1);
}
s
}
/// Default values for configuration options.
///
/// These are returned in case there is no value stored in the
/// database.
fn default_item<'a>(&self, context: &Context) -> Option<ConfigItem<'a>> {
match self {
Self::BccSelf => Some(ConfigItem::BccSelf(String::from("0"))),
Self::E2eeEnabled => Some(ConfigItem::E2eeEnabled(String::from("1"))),
Self::ImapFolder => Some(ConfigItem::ImapFolder(String::from("INBOX"))),
Self::InboxWatch => Some(ConfigItem::InboxWatch(true)),
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<'a> rusqlite::types::ToSql for ConfigItem<'a> {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let sql_obj = match self {
ConfigItem::Selfavatar(blob) => {
rusqlite::types::Value::Text(blob.as_name().to_string())
}
ConfigItem::InboxWatch(value) => rusqlite::types::Value::Integer(*value as i64),
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::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]. Any `Sys*` key should
/// always end up with the default.
pub fn get_config_item(&self, key: ConfigKey) -> Option<ConfigItem> {
let res = self.sql.query_row(
"SELECT value FROM config WHERE keyname=?;",
params![key.to_key_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<ConfigItem> {
let to_string = |raw: rusqlite::types::ValueRef| -> Option<String> {
raw.as_str()
.map_err(|err| {
warn!(self, "ConfigItem {}; not a string: {}", key, err);
})
.map(|s| s.to_string())
.ok()
};
let to_blob = |raw: rusqlite::types::ValueRef| -> Option<BlobObject> {
// from_path() case because there was a time when
// rust-core inserted absolute filenames into the database
// and we want to gracefully recover from this.
to_string(raw).and_then(|s| {
BlobObject::from_name(&self, s.clone())
.or_else(|_| BlobObject::from_path(&self, s))
.ok()
})
};
let to_int = |raw: rusqlite::types::ValueRef| -> Option<i64> {
match raw {
// Current way this is stored.
rusqlite::types::ValueRef::Integer(val) => Some(val),
// Backward compatibility.
rusqlite::types::ValueRef::Text(val) => std::str::from_utf8(val)
.map_err(|e| {
warn!(self, "ConfigItem {}; not UTF-8: {}", key, e);
})
.ok()
.and_then(|v| match v.parse::<i64>() {
Ok(i) => Some(i),
Err(e) => {
warn!(self, "ConfigItem {}; not parsed as int: {}", key, e);
None
}
}),
_ => {
warn!(self, "ConfigItem {}; bad SQLite type: {:?}", key, raw);
None
}
}
};
let to_bool = |raw: rusqlite::types::ValueRef| -> Option<bool> {
to_int(raw).and_then(|i| match i {
0 => Some(false),
1 => Some(true),
v => {
warn!(self, "ConfigItem {}; bad bool value: {}", key, v);
None
}
})
};
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_bool(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_blob(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<'a>(&self, item: ConfigItem<'a>) -> Result<ConfigItem<'a>, Error> {
match item {
ConfigItem::SysConfigKeys(_)
| ConfigItem::SysMsgsizeMaxRecommended(_)
| ConfigItem::SysVersion(_) => bail!("Can not set config item {}", item),
_ => (),
}
let keyname = ConfigKey::from(&item).to_key_string();
// 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![keyname],
)? {
self.sql.execute(
"UPDATE config SET value=? WHERE keyname=?",
params![item, keyname],
)?;
} else {
self.sql.execute(
"INSERT INTO config (keyname, value) VALUES (?, ?)",
params![keyname, 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<bool, Error> {
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<String> {
if let Some(item) = self.get_config_item(key) {
let value = match item {
// Bool values.
ConfigItem::InboxWatch(value) => format!("{}", value as u32),
// String values.
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::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) => value,
// Blob values
ConfigItem::Selfavatar(blob) => blob.to_abs_path().to_string_lossy().into_owned(),
};
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 => {
let val = match v.parse::<u32>() {
Ok(0) => false,
Ok(1) => true,
_ => bail!("set_config for {}: not a bool: {}", key, v),
};
ConfigItem::InboxWatch(val)
}
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(BlobObject::create_from_path(self, 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| {
acc += key.as_ref();
let keys = ConfigKey::iter().fold(String::new(), |mut acc, key| {
acc += &key.to_key_string();
acc += " ";
acc
});
format!(" {} ", keys)
}
@@ -164,26 +577,78 @@ fn get_config_keys_string() -> String {
mod tests {
use super::*;
use std::str::FromStr;
use std::string::ToString;
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");
fn test_string_conversions() {
assert_eq!(ConfigKey::MailServer.to_key_string(), "mail_server");
assert_eq!(
Config::from_str("sys.config_keys"),
Ok(Config::SysConfigKeys)
ConfigKey::from_key_str("mail_server"),
Ok(ConfigKey::MailServer)
);
assert_eq!(ConfigKey::SysConfigKeys.to_key_string(), "sys.config_keys");
assert_eq!(
ConfigKey::from_key_str("sys.config_keys"),
Ok(ConfigKey::SysConfigKeys)
);
}
#[test]
fn test_default_prop() {
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
fn test_sys_config_keys() {
let t = dummy_context();
if let Some(ConfigItem::SysConfigKeys(s)) = t.ctx.get_config_item(ConfigKey::SysConfigKeys)
{
assert!(s.contains(" sys.msgsize_max_recommended "));
assert!(s.contains(" sys.version "));
assert!(s.contains(" imap_folder "));
}
}
#[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]
@@ -213,4 +678,48 @@ mod tests {
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
Ok(())
}
#[test]
fn test_config_item_bool() {
let t = test_context(Some(Box::new(logging_cb)));
// Backwards compatible value.
t.ctx
.sql
.execute(
"INSERT INTO config (keyname, value) VALUES (?, ?)",
params![ConfigKey::InboxWatch.to_string(), "0"],
)
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(false));
t.ctx
.sql
.execute(
"UPDATE config SET value=? WHERE keyname=?",
params!["1", ConfigKey::InboxWatch.to_string()],
)
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(true));
t.ctx
.sql
.execute(
"UPDATE config SET value=? WHERE keyname=?",
params!["bad", ConfigKey::InboxWatch.to_string()],
)
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch);
assert!(item.is_none());
// Normal value.
t.ctx.set_config_item(ConfigItem::InboxWatch(true)).unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(true));
t.ctx
.set_config_item(ConfigItem::InboxWatch(false))
.unwrap();
let item = t.ctx.get_config_item(ConfigKey::InboxWatch).unwrap();
assert_eq!(item, ConfigItem::InboxWatch(false));
}
}

View File

@@ -6,7 +6,7 @@ use std::sync::{Arc, Condvar, Mutex, RwLock};
use libc::uintptr_t;
use crate::chat::*;
use crate::config::Config;
use crate::config::{Config, ConfigItem, ConfigKey};
use crate::constants::*;
use crate::contact::*;
use crate::error::*;
@@ -269,7 +269,6 @@ impl Context {
"<Not yet calculated>".into()
};
let inbox_watch = self.get_config_int(Config::InboxWatch);
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
let mvbox_move = self.get_config_int(Config::MvboxMove);
@@ -299,7 +298,9 @@ impl Context {
res.insert("is_configured", is_configured.to_string());
res.insert("entered_account_settings", l.to_string());
res.insert("used_account_settings", l2.to_string());
res.insert("inbox_watch", inbox_watch.to_string());
if let Some(ConfigItem::InboxWatch(val)) = self.get_config_item(ConfigKey::InboxWatch) {
res.insert("inbox_watch", val.to_string());
}
res.insert("sentbox_watch", sentbox_watch.to_string());
res.insert("mvbox_watch", mvbox_watch.to_string());
res.insert("mvbox_move", mvbox_move.to_string());

View File

@@ -5,7 +5,7 @@ use rand::{thread_rng, Rng};
use crate::blob::BlobObject;
use crate::chat;
use crate::config::Config;
use crate::config::{Config, ConfigItem, ConfigKey};
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
@@ -368,7 +368,12 @@ pub fn job_kill_action(context: &Context, action: Action) -> bool {
}
pub fn perform_inbox_fetch(context: &Context) {
let use_network = context.get_config_bool(Config::InboxWatch);
let use_network =
if let Some(ConfigItem::InboxWatch(val)) = context.get_config_item(ConfigKey::InboxWatch) {
val
} else {
false
};
context
.inbox_thread
@@ -405,7 +410,12 @@ pub fn perform_inbox_idle(context: &Context) {
);
return;
}
let use_network = context.get_config_bool(Config::InboxWatch);
let use_network =
if let Some(ConfigItem::InboxWatch(val)) = context.get_config_item(Config::InboxWatch) {
val
} else {
false
};
context
.inbox_thread