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
7 changed files with 775 additions and 312 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

@@ -1,4 +1,3 @@
use std::sync::{Arc, RwLock};
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
@@ -6,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;
@@ -15,7 +14,6 @@ use crate::error::Error;
use crate::events::Event;
use crate::imap::*;
use crate::imex::*;
use crate::job_thread::JobThread;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::MsgId;
@@ -33,21 +31,6 @@ enum Thread {
Smtp = 5000,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ThreadExecutor {
Inbox,
Mvbox,
Sentbox,
Smtp,
}
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
enum TryAgain {
Dont,
AtOnce,
StandardDelay,
}
impl Default for Thread {
fn default() -> Self {
Thread::Unknown
@@ -119,7 +102,7 @@ pub struct Job {
pub added_timestamp: i64,
pub tries: i32,
pub param: Params,
try_again: TryAgain,
pub try_again: i32,
pub pending_error: Option<String>,
}
@@ -153,7 +136,7 @@ impl Job {
let loginparam = LoginParam::from_database(context, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if connected.is_err() {
self.try_again_later(TryAgain::StandardDelay, None);
self.try_again_later(3, None);
return;
}
}
@@ -194,7 +177,7 @@ impl Job {
Err(err) => {
smtp.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
self.try_again_later(-1, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
@@ -213,15 +196,14 @@ impl Job {
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: TryAgain, pending_error: Option<String>) {
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
self.try_again = try_again;
self.pending_error = pending_error;
}
#[allow(non_snake_case)]
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context, thread: ThreadExecutor) {
let t = get_mailbox_for_executor(context, thread);
let imap_inbox = &t.read().unwrap().imap;
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if context
@@ -248,7 +230,7 @@ impl Job {
&mut dest_uid,
) {
ImapActionResult::RetryLater => {
self.try_again_later(TryAgain::StandardDelay, None);
self.try_again_later(3i32, None);
}
ImapActionResult::Success => {
message::update_server_uid(
@@ -265,9 +247,8 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context, thread: ThreadExecutor) {
let t = get_mailbox_for_executor(context, thread);
let imap_inbox = &t.read().unwrap().imap;
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if !msg.rfc724_mid.is_empty() {
@@ -285,7 +266,7 @@ impl Job {
let res =
imap_inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
if res == ImapActionResult::RetryLater {
self.try_again_later(TryAgain::AtOnce, None);
self.try_again_later(-1i32, None);
return;
}
}
@@ -295,32 +276,30 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_EMPTY_SERVER(&mut self, context: &Context, thread: ThreadExecutor) {
let t = get_mailbox_for_executor(context, thread);
let imap_inbox = &t.read().unwrap().imap;
if thread == ThreadExecutor::Mvbox && DC_EMPTY_MVBOX > 0 {
fn do_DC_JOB_EMPTY_SERVER(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
if let Some(mvbox_folder) = context
.sql
.get_raw_config(context, "configured_mvbox_folder")
{
imap_inbox.empty_folder(context, &mvbox_folder);
}
} else if thread == ThreadExecutor::Inbox && DC_EMPTY_INBOX > 0 {
}
if self.foreign_id & DC_EMPTY_INBOX > 0 {
imap_inbox.empty_folder(context, "INBOX");
}
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context, thread: ThreadExecutor) {
let t = get_mailbox_for_executor(context, thread);
let imap_inbox = &t.read().unwrap().imap;
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
let folder = msg.server_folder.as_ref().unwrap();
match imap_inbox.set_seen(context, folder, msg.server_uid) {
ImapActionResult::RetryLater => {
self.try_again_later(TryAgain::StandardDelay, None);
self.try_again_later(3i32, None);
}
ImapActionResult::AlreadyDone => {}
ImapActionResult::Success | ImapActionResult::Failed => {
@@ -341,19 +320,16 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context, thread: ThreadExecutor) {
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
let folder = self
.param
.get(Param::ServerFolder)
.unwrap_or_default()
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let t = get_mailbox_for_executor(context, thread);
let imap_inbox = &t.read().unwrap().imap;
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if imap_inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
self.try_again_later(TryAgain::StandardDelay, None);
self.try_again_later(3i32, None);
return;
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
@@ -373,7 +349,7 @@ impl Job {
if ImapActionResult::RetryLater
== imap_inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(TryAgain::StandardDelay, None);
self.try_again_later(3, None);
}
}
}
@@ -392,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
@@ -429,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
@@ -505,7 +491,7 @@ pub fn perform_smtp_jobs(context: &Context) {
};
info!(context, "SMTP-jobs started...",);
job_perform(context, ThreadExecutor::Smtp, probe_smtp_network);
job_perform(context, Thread::Smtp, probe_smtp_network);
info!(context, "SMTP-jobs ended.");
{
@@ -718,81 +704,40 @@ pub fn perform_inbox_jobs(context: &Context) {
*context.probe_imap_network.write().unwrap() = false;
*context.perform_inbox_jobs_needed.write().unwrap() = false;
job_perform(context, ThreadExecutor::Inbox, probe_imap_network);
job_perform(context, Thread::Imap, probe_imap_network);
info!(context, "dc_perform_inbox_jobs ended.",);
}
pub fn perform_mvbox_jobs(context: &Context) {
info!(context, "dc_perform_mbox_jobs starting.",);
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
*context.probe_imap_network.write().unwrap() = false;
*context.perform_inbox_jobs_needed.write().unwrap() = false;
job_perform(context, ThreadExecutor::Mvbox, probe_imap_network);
info!(context, "dc_perform_mbox_jobs ended.",);
info!(context, "dc_perform_mbox_jobs EMPTY (for now).",);
}
pub fn perform_sentbox_jobs(context: &Context) {
info!(context, "dc_perform_sentbox_jobs starting.",);
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
*context.probe_imap_network.write().unwrap() = false;
*context.perform_inbox_jobs_needed.write().unwrap() = false;
job_perform(context, ThreadExecutor::Sentbox, probe_imap_network);
info!(context, "dc_perform_sentbox_jobs ended.",);
info!(context, "dc_perform_sentbox_jobs EMPTY (for now).",);
}
fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
let folder = get_folder_for_executor(context, thread);
let fallback = if thread == ThreadExecutor::Inbox || thread == ThreadExecutor::Smtp {
// only select non empty ones on the inbox thread or smtp
"OR (j.foreign_id IS NULL)) "
} else {
") "
};
let query_start = concat!(
"SELECT ",
"j.id AS id, ",
"j.action AS action, ",
"j.foreign_id AS foreign_id, ",
"j.param AS param, ",
"j.added_timestamp AS added_timestamp, ",
"j.desired_timestamp AS desired_timestamp, ",
"j.tries AS tries ",
"FROM jobs j ",
"LEFT JOIN msgs m ON j.foreign_id=m.id ",
)
.to_string();
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
let query = if !probe_network {
// processing for first-try and after backoff-timeouts:
// process jobs in the order they were added.
query_start
+ "WHERE j.thread=? AND j.desired_timestamp<=? AND "
+ "((j.foreign_id IS NOT NULL AND m.server_folder=?) "
+ fallback
+ "ORDER BY j.action DESC, j.added_timestamp;"
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;"
} else {
// processing after call to dc_maybe_network():
// process _all_ pending jobs that failed before
// in the order of their backoff-times.
query_start
+ "WHERE j.thread=? AND j.tries>0 AND "
+ "((j.foreign_id IS NOT NULL AND m.server_folder=?) "
+ fallback
+ "ORDER BY j.desired_timestamp, j.action DESC;"
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
};
let params_no_probe = params![thread as i64, time(), folder];
let params_probe = params![thread as i64, folder];
let params_no_probe = params![thread as i64, time()];
let params_probe = params![thread as i64];
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
params_no_probe
} else {
params_probe
};
let jobs: Result<Vec<Job>, _> = context
.sql
.query_map(
@@ -807,7 +752,7 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
added_timestamp: row.get(4)?,
tries: row.get(6)?,
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
try_again: TryAgain::Dont,
try_again: 0,
pending_error: None,
};
@@ -822,7 +767,14 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
for mut job in jobs.unwrap_or_default() {
info!(
context,
"{:?}-job #{}, action {} started...", thread, job.job_id, job.action,
"{}-job #{}, action {} started...",
if thread == Thread::Imap {
"INBOX"
} else {
"SMTP"
},
job.job_id,
job.action,
);
// some configuration jobs are "exclusive":
@@ -849,18 +801,18 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
let mut tries = 0;
while tries <= 1 {
// this can be modified by a job using dc_job_try_again_later()
job.try_again = TryAgain::Dont;
job.try_again = 0;
match job.action {
Action::Unknown => {
warn!(context, "Unknown job id found");
}
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
Action::EmptyServer => job.do_DC_JOB_EMPTY_SERVER(context, thread),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context, thread),
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context, thread),
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context, thread),
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context, thread),
Action::EmptyServer => job.do_DC_JOB_EMPTY_SERVER(context),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
@@ -879,7 +831,7 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
Action::SendMdnOld => {}
Action::SendMsgToSmtpOld => {}
}
if job.try_again != TryAgain::AtOnce {
if job.try_again != -1 {
break;
}
tries += 1
@@ -899,7 +851,19 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
.unsuspend(context);
suspend_smtp_thread(context, false);
break;
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
} else if job.try_again == 2 {
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
info!(
context,
"{}-job #{} not yet ready and will be delayed.",
if thread == Thread::Imap {
"INBOX"
} else {
"SMTP"
},
job.job_id
);
} else if job.try_again == -1 || job.try_again == 3 {
let tries = job.tries + 1;
if tries < 17 {
job.tries = tries;
@@ -908,14 +872,18 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
job.update(context);
info!(
context,
"{:?}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
thread,
"{}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
if thread == Thread::Imap {
"INBOX"
} else {
"SMTP"
},
job.job_id as u32,
tries,
time_offset,
job.added_timestamp + time_offset - time()
);
if thread == ThreadExecutor::Smtp && tries < 17 - 1 {
if thread == Thread::Smtp && tries < 17 - 1 {
context
.smtp_state
.clone()
@@ -1043,69 +1011,12 @@ pub fn job_add(
).ok();
match thread {
Thread::Imap => {
let folder: String = context
.sql
.query_get_value(
context,
"SELECT server_folder FROM msgs WHERE id=?",
params![foreign_id],
)
.unwrap_or_default();
let mbox_thread = get_matching_mailbox(context, &folder);
let res = mbox_thread.try_read();
match res {
Ok(mbox_thread) => {
mbox_thread.interrupt_idle(context);
}
Err(err) => {
*context.perform_inbox_jobs_needed.write().unwrap() = true;
warn!(context, "could not interrupt idle: {}", err);
}
}
}
Thread::Imap => interrupt_inbox_idle(context, false),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
}
fn get_folder_for_executor(context: &Context, thread: ThreadExecutor) -> String {
let t = get_mailbox_for_executor(context, thread);
let b = t.read().unwrap();
b.get_watch_folder(context).unwrap_or_default().to_string()
}
fn get_mailbox_for_executor(context: &Context, thread: ThreadExecutor) -> Arc<RwLock<JobThread>> {
match thread {
ThreadExecutor::Inbox => context.inbox_thread.clone(),
ThreadExecutor::Mvbox => context.mvbox_thread.clone(),
ThreadExecutor::Sentbox => context.sentbox_thread.clone(),
ThreadExecutor::Smtp => panic!("do not use for smtp"),
}
}
fn get_matching_mailbox(context: &Context, folder: &String) -> Arc<RwLock<JobThread>> {
let mvbox_folder = context
.mvbox_thread
.read()
.unwrap()
.get_watch_folder(context);
let sentbox_folder = context
.sentbox_thread
.read()
.unwrap()
.get_watch_folder(context);
if mvbox_folder.is_some() && folder == mvbox_folder.as_ref().unwrap() {
context.mvbox_thread.clone()
} else if sentbox_folder.is_some() && folder == sentbox_folder.as_ref().unwrap() {
context.sentbox_thread.clone()
} else {
context.inbox_thread.clone()
}
}
pub fn interrupt_smtp_idle(context: &Context) {
info!(context, "Interrupting SMTP-idle...",);

View File

@@ -116,7 +116,7 @@ impl JobThread {
}
}
pub(crate) fn get_watch_folder(&self, context: &Context) -> Option<String> {
fn get_watch_folder(&self, context: &Context) -> Option<String> {
match context.sql.get_raw_config(context, self.folder_config_name) {
Some(name) => Some(name),
None => {