Compare commits

..

8 Commits

Author SHA1 Message Date
dignifiedquire
b1d03e54d9 more sql fixes 2019-11-28 18:24:28 +01:00
dignifiedquire
718f4507b1 try fix sql 2019-11-27 22:36:38 +01:00
dignifiedquire
10925bd5e1 try fix sql 2019-11-27 01:06:00 +01:00
dignifiedquire
f0ad11a8f9 rename and fix typo 2019-11-27 00:53:25 +01:00
dignifiedquire
6995554f0a fallbackg 2019-11-27 00:47:54 +01:00
dignifiedquire
6fe31278b0 better sql statement 2019-11-27 00:44:20 +01:00
dignifiedquire
34be965525 move thread handling around 2019-11-27 00:40:23 +01:00
Alexander Krotov
22a0e3fe9c job: try_again refactoring
Introduce TryAgain type with C core constants
Make try_again field and method private.
Remove try_again == 2 processing, it was never used.
2019-11-26 22:14:34 +01:00
7 changed files with 310 additions and 773 deletions

View File

@@ -337,16 +337,12 @@ pub unsafe extern "C" fn dc_get_config(
return "".strdup();
}
let ffi_context = &*context;
match config::Config::from_key_str(&to_string_lossy(key)) {
match config::Config::from_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(err) => {
ffi_context.error(&format!(
"dc_get_config(): invalid key {} ({})",
&to_string_lossy(key),
err
));
Err(_) => {
ffi_context.error("dc_get_config(): invalid key");
"".strdup()
}
}

View File

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

View File

@@ -1,575 +1,162 @@
//! # Key-value configuration management
use std::str::FromStr;
use strum::IntoEnumIterator;
use strum_macros::{AsRefStr, Display, EnumDiscriminants, EnumIter, EnumString};
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, 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 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")]
#[strum_discriminants(
name(ConfigKey),
derive(Display, EnumString, EnumIter),
strum(serialize_all = "snake_case")
/// The available configuration keys.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
)]
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),
}
// Transitional: support the old name for ConfigKey.
pub type Config = ConfigKey;
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)
}
#[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,
}
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.
/// 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> {
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
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),
};
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()),
}
}
/// 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.
///
/// 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.
/// 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> {
let maybe_val = match key {
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
}
Config::Selfstatus => {
let def = self.stock_str(StockMessage::StatusLine);
if value.is_none() || value.unwrap() == def {
let val = if value.is_none() || value.unwrap() == def {
None
} else {
value
}
};
self.sql.set_raw_config(self, key, val)
}
_ => 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(|_| ())
_ => self.sql.set_raw_config(self, key, value),
}
}
}
/// Returns all available configuration keys concated together.
fn get_config_keys_string() -> String {
let keys = ConfigKey::iter().fold(String::new(), |mut acc, key| {
acc += &key.to_key_string();
let keys = Config::iter().fold(String::new(), |mut acc, key| {
acc += key.as_ref();
acc += " ";
acc
});
format!(" {} ", keys)
}
@@ -577,78 +164,26 @@ 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_string_conversions() {
assert_eq!(ConfigKey::MailServer.to_key_string(), "mail_server");
assert_eq!(
ConfigKey::from_key_str("mail_server"),
Ok(ConfigKey::MailServer)
);
fn test_to_string() {
assert_eq!(Config::MailServer.to_string(), "mail_server");
assert_eq!(Config::from_str("mail_server"), Ok(Config::MailServer));
assert_eq!(ConfigKey::SysConfigKeys.to_key_string(), "sys.config_keys");
assert_eq!(Config::SysConfigKeys.to_string(), "sys.config_keys");
assert_eq!(
ConfigKey::from_key_str("sys.config_keys"),
Ok(ConfigKey::SysConfigKeys)
Config::from_str("sys.config_keys"),
Ok(Config::SysConfigKeys)
);
}
#[test]
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());
fn test_default_prop() {
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
}
#[test]
@@ -678,48 +213,4 @@ 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, ConfigItem, ConfigKey};
use crate::config::Config;
use crate::constants::*;
use crate::contact::*;
use crate::error::*;
@@ -269,6 +269,7 @@ 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);
@@ -298,9 +299,7 @@ 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());
if let Some(ConfigItem::InboxWatch(val)) = self.get_config_item(ConfigKey::InboxWatch) {
res.insert("inbox_watch", val.to_string());
}
res.insert("inbox_watch", inbox_watch.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,3 +1,4 @@
use std::sync::{Arc, RwLock};
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
@@ -5,7 +6,7 @@ use rand::{thread_rng, Rng};
use crate::blob::BlobObject;
use crate::chat;
use crate::config::{Config, ConfigItem, ConfigKey};
use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
@@ -14,6 +15,7 @@ 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;
@@ -31,6 +33,21 @@ 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
@@ -102,7 +119,7 @@ pub struct Job {
pub added_timestamp: i64,
pub tries: i32,
pub param: Params,
pub try_again: i32,
try_again: TryAgain,
pub pending_error: Option<String>,
}
@@ -136,7 +153,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(3, None);
self.try_again_later(TryAgain::StandardDelay, None);
return;
}
}
@@ -177,7 +194,7 @@ impl Job {
Err(err) => {
smtp.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
@@ -196,14 +213,15 @@ impl Job {
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
fn try_again_later(&mut self, try_again: TryAgain, 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) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
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;
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if context
@@ -230,7 +248,7 @@ impl Job {
&mut dest_uid,
) {
ImapActionResult::RetryLater => {
self.try_again_later(3i32, None);
self.try_again_later(TryAgain::StandardDelay, None);
}
ImapActionResult::Success => {
message::update_server_uid(
@@ -247,8 +265,9 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
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;
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if !msg.rfc724_mid.is_empty() {
@@ -266,7 +285,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(-1i32, None);
self.try_again_later(TryAgain::AtOnce, None);
return;
}
}
@@ -276,30 +295,32 @@ impl Job {
}
#[allow(non_snake_case)]
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 {
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 {
if let Some(mvbox_folder) = context
.sql
.get_raw_config(context, "configured_mvbox_folder")
{
imap_inbox.empty_folder(context, &mvbox_folder);
}
}
if self.foreign_id & DC_EMPTY_INBOX > 0 {
} else if thread == ThreadExecutor::Inbox && 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) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
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;
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(3i32, None);
self.try_again_later(TryAgain::StandardDelay, None);
}
ImapActionResult::AlreadyDone => {}
ImapActionResult::Success | ImapActionResult::Failed => {
@@ -320,16 +341,19 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context, thread: ThreadExecutor) {
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 imap_inbox = &context.inbox_thread.read().unwrap().imap;
let t = get_mailbox_for_executor(context, thread);
let imap_inbox = &t.read().unwrap().imap;
if imap_inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
self.try_again_later(3i32, None);
self.try_again_later(TryAgain::StandardDelay, None);
return;
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
@@ -349,7 +373,7 @@ impl Job {
if ImapActionResult::RetryLater
== imap_inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(3, None);
self.try_again_later(TryAgain::StandardDelay, None);
}
}
}
@@ -368,12 +392,7 @@ pub fn job_kill_action(context: &Context, action: Action) -> bool {
}
pub fn perform_inbox_fetch(context: &Context) {
let use_network =
if let Some(ConfigItem::InboxWatch(val)) = context.get_config_item(ConfigKey::InboxWatch) {
val
} else {
false
};
let use_network = context.get_config_bool(Config::InboxWatch);
context
.inbox_thread
@@ -410,12 +429,7 @@ pub fn perform_inbox_idle(context: &Context) {
);
return;
}
let use_network =
if let Some(ConfigItem::InboxWatch(val)) = context.get_config_item(Config::InboxWatch) {
val
} else {
false
};
let use_network = context.get_config_bool(Config::InboxWatch);
context
.inbox_thread
@@ -491,7 +505,7 @@ pub fn perform_smtp_jobs(context: &Context) {
};
info!(context, "SMTP-jobs started...",);
job_perform(context, Thread::Smtp, probe_smtp_network);
job_perform(context, ThreadExecutor::Smtp, probe_smtp_network);
info!(context, "SMTP-jobs ended.");
{
@@ -704,40 +718,81 @@ 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, Thread::Imap, probe_imap_network);
job_perform(context, ThreadExecutor::Inbox, probe_imap_network);
info!(context, "dc_perform_inbox_jobs ended.",);
}
pub fn perform_mvbox_jobs(context: &Context) {
info!(context, "dc_perform_mbox_jobs EMPTY (for now).",);
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.",);
}
pub fn perform_sentbox_jobs(context: &Context) {
info!(context, "dc_perform_sentbox_jobs EMPTY (for now).",);
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.",);
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
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();
let query = if !probe_network {
// processing for first-try and after backoff-timeouts:
// process jobs in the order they were added.
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;"
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;"
} else {
// processing after call to dc_maybe_network():
// process _all_ pending jobs that failed before
// in the order of their backoff-times.
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
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;"
};
let params_no_probe = params![thread as i64, time()];
let params_probe = params![thread as i64];
let params_no_probe = params![thread as i64, time(), folder];
let params_probe = params![thread as i64, folder];
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
params_no_probe
} else {
params_probe
};
let jobs: Result<Vec<Job>, _> = context
.sql
.query_map(
@@ -752,7 +807,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
added_timestamp: row.get(4)?,
tries: row.get(6)?,
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
try_again: 0,
try_again: TryAgain::Dont,
pending_error: None,
};
@@ -767,14 +822,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
for mut job in jobs.unwrap_or_default() {
info!(
context,
"{}-job #{}, action {} started...",
if thread == Thread::Imap {
"INBOX"
} else {
"SMTP"
},
job.job_id,
job.action,
"{:?}-job #{}, action {} started...", thread, job.job_id, job.action,
);
// some configuration jobs are "exclusive":
@@ -801,18 +849,18 @@ fn job_perform(context: &Context, thread: Thread, 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 = 0;
job.try_again = TryAgain::Dont;
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),
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::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::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) {
@@ -831,7 +879,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
Action::SendMdnOld => {}
Action::SendMsgToSmtpOld => {}
}
if job.try_again != -1 {
if job.try_again != TryAgain::AtOnce {
break;
}
tries += 1
@@ -851,19 +899,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
.unsuspend(context);
suspend_smtp_thread(context, false);
break;
} 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 {
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
let tries = job.tries + 1;
if tries < 17 {
job.tries = tries;
@@ -872,18 +908,14 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
job.update(context);
info!(
context,
"{}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
if thread == Thread::Imap {
"INBOX"
} else {
"SMTP"
},
"{:?}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
thread,
job.job_id as u32,
tries,
time_offset,
job.added_timestamp + time_offset - time()
);
if thread == Thread::Smtp && tries < 17 - 1 {
if thread == ThreadExecutor::Smtp && tries < 17 - 1 {
context
.smtp_state
.clone()
@@ -1011,12 +1043,69 @@ pub fn job_add(
).ok();
match thread {
Thread::Imap => interrupt_inbox_idle(context, false),
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::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 {
}
}
fn get_watch_folder(&self, context: &Context) -> Option<String> {
pub(crate) 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 => {