mirror of
https://github.com/chatmail/core.git
synced 2026-09-21 20:51:23 +03:00
Compare commits
8 Commits
flub-confi
...
refactor/t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1d03e54d9 | ||
|
|
718f4507b1 | ||
|
|
10925bd5e1 | ||
|
|
f0ad11a8f9 | ||
|
|
6995554f0a | ||
|
|
6fe31278b0 | ||
|
|
34be965525 | ||
|
|
22a0e3fe9c |
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
755
src/config.rs
755
src/config.rs
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
257
src/job.rs
257
src/job.rs
@@ -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...",);
|
||||
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user