mirror of
https://github.com/chatmail/core.git
synced 2026-07-06 15:34:58 +03:00
Compare commits
3 Commits
remove-sus
...
flub-confi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eab93257c | ||
|
|
e6d3bd284b | ||
|
|
f6cdc7b498 |
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
759
src/config.rs
759
src/config.rs
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,18 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
.unwrap()
|
||||
.imap
|
||||
.disconnect(context);
|
||||
context
|
||||
.sentbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.disconnect(context);
|
||||
context
|
||||
.mvbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.disconnect(context);
|
||||
context.smtp.clone().lock().unwrap().disconnect();
|
||||
info!(context, "Configure ...",);
|
||||
|
||||
|
||||
@@ -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());
|
||||
@@ -498,6 +499,8 @@ pub struct BobStatus {
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SmtpState {
|
||||
pub idle: bool,
|
||||
pub suspended: bool,
|
||||
pub doing_jobs: bool,
|
||||
pub perform_jobs_needed: i32,
|
||||
pub probe_network: bool,
|
||||
}
|
||||
|
||||
113
src/job.rs
113
src/job.rs
@@ -5,7 +5,7 @@ use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, ConfigItem, ConfigKey};
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
@@ -31,13 +31,6 @@ enum Thread {
|
||||
Smtp = 5000,
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
enum TryAgain {
|
||||
Dont,
|
||||
AtOnce,
|
||||
StandardDelay,
|
||||
}
|
||||
|
||||
impl Default for Thread {
|
||||
fn default() -> Self {
|
||||
Thread::Unknown
|
||||
@@ -109,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>,
|
||||
}
|
||||
|
||||
@@ -143,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;
|
||||
}
|
||||
}
|
||||
@@ -184,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
|
||||
@@ -203,7 +196,7 @@ 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;
|
||||
}
|
||||
@@ -237,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(
|
||||
@@ -273,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;
|
||||
}
|
||||
}
|
||||
@@ -306,7 +299,7 @@ impl Job {
|
||||
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 => {
|
||||
@@ -336,7 +329,7 @@ impl Job {
|
||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||
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() {
|
||||
@@ -356,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,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
|
||||
@@ -412,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
|
||||
@@ -479,12 +482,24 @@ pub fn perform_smtp_jobs(context: &Context) {
|
||||
state.probe_network = false;
|
||||
state.perform_jobs_needed = 0;
|
||||
|
||||
if state.suspended {
|
||||
info!(context, "SMTP-jobs suspended.",);
|
||||
return;
|
||||
}
|
||||
state.doing_jobs = true;
|
||||
probe_smtp_network
|
||||
};
|
||||
|
||||
info!(context, "SMTP-jobs started...",);
|
||||
job_perform(context, Thread::Smtp, probe_smtp_network);
|
||||
info!(context, "SMTP-jobs ended.");
|
||||
|
||||
{
|
||||
let &(ref lock, _) = &*context.smtp_state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.doing_jobs = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn perform_smtp_idle(context: &Context) {
|
||||
@@ -737,7 +752,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: TryAgain::Dont,
|
||||
try_again: 0,
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
@@ -763,15 +778,30 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
);
|
||||
|
||||
// some configuration jobs are "exclusive":
|
||||
// - they are always executed in the imap-thread and the smtp-thread is suspended during execution
|
||||
// - they may change the database handle change the database handle; we do not keep old pointers therefore
|
||||
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
job_kill_action(context, job.action);
|
||||
context
|
||||
.sentbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.suspend(context);
|
||||
context
|
||||
.mvbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.suspend(context);
|
||||
suspend_smtp_thread(context, true);
|
||||
}
|
||||
|
||||
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 => {
|
||||
@@ -801,14 +831,39 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
Action::SendMdnOld => {}
|
||||
Action::SendMsgToSmtpOld => {}
|
||||
}
|
||||
if job.try_again != TryAgain::AtOnce {
|
||||
if job.try_again != -1 {
|
||||
break;
|
||||
}
|
||||
tries += 1
|
||||
}
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
context
|
||||
.sentbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.unsuspend(context);
|
||||
context
|
||||
.mvbox_thread
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.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;
|
||||
@@ -874,6 +929,18 @@ fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
||||
seconds as i64
|
||||
}
|
||||
|
||||
fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
||||
context.smtp_state.0.lock().unwrap().suspended = suspend;
|
||||
if suspend {
|
||||
loop {
|
||||
if !context.smtp_state.0.lock().unwrap().doing_jobs {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
|
||||
unsafe { mimefactory.render()? };
|
||||
|
||||
@@ -16,6 +16,7 @@ pub struct JobThread {
|
||||
pub struct JobState {
|
||||
idle: bool,
|
||||
jobs_needed: bool,
|
||||
suspended: bool,
|
||||
using_handle: bool,
|
||||
}
|
||||
|
||||
@@ -29,6 +30,32 @@ impl JobThread {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suspend(&self, context: &Context) {
|
||||
info!(context, "Suspending {}-thread.", self.name,);
|
||||
{
|
||||
self.state.0.lock().unwrap().suspended = true;
|
||||
}
|
||||
self.interrupt_idle(context);
|
||||
loop {
|
||||
let using_handle = self.state.0.lock().unwrap().using_handle;
|
||||
if !using_handle {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsuspend(&self, context: &Context) {
|
||||
info!(context, "Unsuspending {}-thread.", self.name);
|
||||
|
||||
let &(ref lock, ref cvar) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.suspended = false;
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub fn interrupt_idle(&self, context: &Context) {
|
||||
{
|
||||
self.state.0.lock().unwrap().jobs_needed = true;
|
||||
@@ -50,6 +77,10 @@ impl JobThread {
|
||||
let &(ref lock, _) = &*self.state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
if state.suspended {
|
||||
return;
|
||||
}
|
||||
|
||||
state.using_handle = true;
|
||||
}
|
||||
|
||||
@@ -114,6 +145,14 @@ impl JobThread {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.suspended {
|
||||
while !state.idle {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
state.idle = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state.using_handle = true;
|
||||
|
||||
if !use_network {
|
||||
|
||||
Reference in New Issue
Block a user