mirror of
https://github.com/chatmail/core.git
synced 2026-04-11 10:02:09 +03:00
Compare commits
6 Commits
flub-confi
...
fix_html_p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dc87e9a0f | ||
|
|
c011a8cfef | ||
|
|
2d7d79fc44 | ||
|
|
119d4fa689 | ||
|
|
b7a9f73329 | ||
|
|
a52a3e0d24 |
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -620,7 +620,6 @@ dependencies = [
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"deltachat_derive 0.1.0",
|
||||
"derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)",
|
||||
"escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -717,16 +716,6 @@ dependencies = [
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_deref"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.14.1"
|
||||
@@ -3463,7 +3452,6 @@ dependencies = [
|
||||
"checksum deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "814dba060d9fdc7a989fccdc4810ada9d1c7a1f09131c78e42412bc6c634b93b"
|
||||
"checksum derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ac53fa6a3cda160df823a9346442525dcaf1e171999a1cf23e67067e4fd64d4"
|
||||
"checksum derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0288a23da9333c246bb18c143426074a6ae96747995c5819d2947b64cd942b37"
|
||||
"checksum derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11554fdb0aa42363a442e0c4278f51c9621e20c1ce3bac51d79e60646f3b8b8f"
|
||||
"checksum derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839"
|
||||
"checksum des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af"
|
||||
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
|
||||
@@ -55,7 +55,6 @@ webpki-roots = "0.18.0"
|
||||
webpki = "0.21.0"
|
||||
mailparse = "0.10.1"
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
derive_deref = "1.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
|
||||
243
src/config.rs
243
src/config.rs
@@ -1,6 +1,5 @@
|
||||
//! # Key-value configuration management
|
||||
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use strum::{EnumProperty, IntoEnumIterator};
|
||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||
|
||||
@@ -9,7 +8,6 @@ use crate::constants::DC_VERSION_STR;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::job::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// The available configuration keys.
|
||||
@@ -90,164 +88,6 @@ pub enum Config {
|
||||
SysConfigKeys,
|
||||
}
|
||||
|
||||
/// A trait defining a [Context]-wide configuration item.
|
||||
///
|
||||
/// Configuration items are stored in database of a [Context]. Most
|
||||
/// configuration items are newtypes which implement [std::ops::Deref]
|
||||
/// and [std::ops::DerefMut] though this is not required. However
|
||||
/// what **is required** for the struct to implement
|
||||
/// [rusqlite::ToSql] and [rusqlite::types::FromSql].
|
||||
pub trait ConfigItem {
|
||||
/// Returns the name of the key used in the SQLite database.
|
||||
fn keyname() -> &'static str;
|
||||
|
||||
/// Loads the configuration item from the [Context]'s database.
|
||||
///
|
||||
/// If the configuration item is not available in the database,
|
||||
/// `None` will be returned.
|
||||
fn load(context: &Context) -> Result<Option<Self>, sql::Error>
|
||||
where
|
||||
Self: std::marker::Sized + rusqlite::types::FromSql,
|
||||
{
|
||||
context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT value FROM config WHERE keyname=?;",
|
||||
params!(Self::keyname()),
|
||||
|row| row.get(0),
|
||||
)
|
||||
.or_else(|err| match err {
|
||||
sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
e => Err(e),
|
||||
})
|
||||
}
|
||||
|
||||
/// Stores the configuration item in the [Context]'s database.
|
||||
fn store(&self, context: &Context) -> Result<(), sql::Error>
|
||||
where
|
||||
Self: rusqlite::ToSql,
|
||||
{
|
||||
if context.sql.exists(
|
||||
"select value FROM config WHERE keyname=?;",
|
||||
params!(Self::keyname()),
|
||||
)? {
|
||||
context.sql.execute(
|
||||
"UPDATE config SET value=? WHERE keyname=?",
|
||||
params![&self, Self::keyname()],
|
||||
)?;
|
||||
} else {
|
||||
context.sql.execute(
|
||||
"INSERT INTO config (keyname, value) VALUES (?, ?)",
|
||||
params![Self::keyname(), &self],
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the configuration item from the [Context]'s database.
|
||||
fn delete(context: &Context) -> Result<(), sql::Error> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM config WHERE keyname=?",
|
||||
params![Self::keyname()],
|
||||
)
|
||||
.and(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration item: display address for this account.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deref, DerefMut)]
|
||||
pub struct Addr(pub String);
|
||||
|
||||
impl rusqlite::ToSql for Addr {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
self.0.to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::types::FromSql for Addr {
|
||||
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
value.as_str().map(|v| Addr(v.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigItem for Addr {
|
||||
fn keyname() -> &'static str {
|
||||
"addr"
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration item:
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deref, DerefMut)]
|
||||
pub struct MailServer(pub String);
|
||||
|
||||
impl rusqlite::ToSql for MailServer {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
self.0.to_sql()
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::types::FromSql for MailServer {
|
||||
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
value.as_str().map(|v| MailServer(v.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigItem for MailServer {
|
||||
fn keyname() -> &'static str {
|
||||
"mail_server"
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration item:
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deref, DerefMut)]
|
||||
pub struct MailUser(pub String);
|
||||
// XXX TODO
|
||||
|
||||
/// Configuration item: whether to watch the INBOX folder for changes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deref, DerefMut)]
|
||||
pub struct InboxWatch(pub bool);
|
||||
|
||||
impl Default for InboxWatch {
|
||||
fn default() -> Self {
|
||||
InboxWatch(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::ToSql for InboxWatch {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
// Column affinity is "text" so gets stored as string by SQLite.
|
||||
let obj = rusqlite::types::Value::Integer(self.0 as i64);
|
||||
Ok(rusqlite::types::ToSqlOutput::Owned(obj))
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::types::FromSql for InboxWatch {
|
||||
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let str_to_int = |s: &str| {
|
||||
s.parse::<i64>()
|
||||
.map_err(|e| rusqlite::types::FromSqlError::Other(Box::new(e)))
|
||||
};
|
||||
let int_to_bool = |i| match i {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
v => Err(rusqlite::types::FromSqlError::OutOfRange(v)),
|
||||
};
|
||||
value
|
||||
.as_str()
|
||||
.and_then(str_to_int)
|
||||
.and_then(int_to_bool)
|
||||
.map(InboxWatch)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigItem for InboxWatch {
|
||||
fn keyname() -> &'static str {
|
||||
"inbox_watch"
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -335,16 +175,11 @@ fn get_config_keys_string() -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
static ref TC: TestContext = dummy_context();
|
||||
}
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_string() {
|
||||
@@ -390,80 +225,4 @@ mod tests {
|
||||
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inbox_watch() {
|
||||
// Loading from context when it is not in the DB.
|
||||
let val = InboxWatch::load(&TC.ctx).unwrap();
|
||||
assert_eq!(val, None);
|
||||
|
||||
// Create in-memory from default.
|
||||
let mut val = InboxWatch::default();
|
||||
assert_eq!(*val, true);
|
||||
|
||||
// Assign using deref.
|
||||
*val = false;
|
||||
assert_eq!(*val, false);
|
||||
|
||||
// Construct newtype directly.
|
||||
let val = InboxWatch(false);
|
||||
assert_eq!(*val, false);
|
||||
|
||||
// Helper to query raw DB value.
|
||||
let query_db_raw = || {
|
||||
TC.ctx
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT value FROM config WHERE KEYNAME=?",
|
||||
params![InboxWatch::keyname()],
|
||||
|row| row.get::<_, String>(0),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// Save (non-default) value to the DB.
|
||||
InboxWatch(false).store(&TC.ctx).unwrap();
|
||||
assert_eq!(query_db_raw(), "0");
|
||||
let val = InboxWatch::load(&TC.ctx).unwrap().unwrap();
|
||||
assert_eq!(val, InboxWatch(false));
|
||||
|
||||
// Save true (aka default) value to the DB.
|
||||
InboxWatch(true).store(&TC.ctx).unwrap();
|
||||
assert_eq!(query_db_raw(), "1");
|
||||
let val = InboxWatch::load(&TC.ctx).unwrap().unwrap();
|
||||
assert_eq!(val, InboxWatch(true));
|
||||
|
||||
// Delete the value from the DB.
|
||||
InboxWatch::delete(&TC.ctx).unwrap();
|
||||
assert!(!TC
|
||||
.ctx
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT value FROM config WHERE KEYNAME=?",
|
||||
params![InboxWatch::keyname()],
|
||||
)
|
||||
.unwrap());
|
||||
let val = InboxWatch::load(&TC.ctx).unwrap();
|
||||
assert_eq!(val, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr() {
|
||||
// In-memory creation
|
||||
let val = Addr("me@example.com".into());
|
||||
assert_eq!(*val, "me@example.com");
|
||||
|
||||
// Load when DB is empty.
|
||||
let val = Addr::load(&TC.ctx).unwrap();
|
||||
assert_eq!(val, None);
|
||||
|
||||
// Store and load.
|
||||
Addr("me@example.com".into()).store(&TC.ctx).unwrap();
|
||||
let val = Addr::load(&TC.ctx).unwrap();
|
||||
assert_eq!(val, Some(Addr("me@example.com".into())));
|
||||
|
||||
// Delete
|
||||
Addr::delete(&TC.ctx).unwrap();
|
||||
assert_eq!(Addr::load(&TC.ctx).unwrap(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ fn add_parts(
|
||||
}
|
||||
|
||||
// 1 or 0 for yes/no
|
||||
msgrmsg = mime_parser.is_send_by_messenger as _;
|
||||
msgrmsg = mime_parser.has_chat_version() as _;
|
||||
if msgrmsg == 0 && 0 != dc_is_reply_to_messenger_message(context, mime_parser) {
|
||||
// 2=no, but is reply to messenger message
|
||||
msgrmsg = 2;
|
||||
@@ -1112,7 +1112,7 @@ fn create_or_lookup_group(
|
||||
// the only critical situation is if the user hits "Reply" instead
|
||||
// of "Reply all" in a non-messenger-client */
|
||||
if to_ids_cnt == 1
|
||||
&& !mime_parser.is_send_by_messenger
|
||||
&& !mime_parser.has_chat_version()
|
||||
&& chat::get_chat_contact_cnt(context, chat_id) > 3
|
||||
{
|
||||
// to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt.
|
||||
|
||||
@@ -30,7 +30,6 @@ pub struct MimeParser<'a> {
|
||||
pub parts: Vec<Part>,
|
||||
pub header: HashMap<String, String>,
|
||||
pub subject: Option<String>,
|
||||
pub is_send_by_messenger: bool,
|
||||
pub decrypting_failed: bool,
|
||||
pub encrypted: bool,
|
||||
pub signatures: HashSet<String>,
|
||||
@@ -75,7 +74,6 @@ impl<'a> MimeParser<'a> {
|
||||
parts: Vec::new(),
|
||||
header: Default::default(),
|
||||
subject: None,
|
||||
is_send_by_messenger: false,
|
||||
decrypting_failed: false,
|
||||
encrypted: false,
|
||||
signatures: Default::default(),
|
||||
@@ -153,10 +151,6 @@ impl<'a> MimeParser<'a> {
|
||||
self.subject = Some(field.clone());
|
||||
}
|
||||
|
||||
if self.lookup_field("Chat-Version").is_some() {
|
||||
self.is_send_by_messenger = true
|
||||
}
|
||||
|
||||
if self.lookup_field("Autocrypt-Setup-Message").is_some() {
|
||||
let has_setup_file = self.parts.iter().any(|p| {
|
||||
p.mimetype.is_some() && p.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
|
||||
@@ -203,7 +197,7 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_send_by_messenger && self.parts.len() == 2 {
|
||||
if self.has_chat_version() && self.parts.len() == 2 {
|
||||
let need_drop = {
|
||||
let textpart = &self.parts[0];
|
||||
let filepart = &self.parts[1];
|
||||
@@ -237,7 +231,7 @@ impl<'a> MimeParser<'a> {
|
||||
let colon = subject.find(':');
|
||||
if colon == Some(2)
|
||||
|| colon == Some(3)
|
||||
|| self.is_send_by_messenger
|
||||
|| self.has_chat_version()
|
||||
|| subject.contains("Chat:")
|
||||
{
|
||||
prepend_subject = 0i32
|
||||
@@ -323,7 +317,7 @@ impl<'a> MimeParser<'a> {
|
||||
part.typ = Viewtype::Text;
|
||||
|
||||
if let Some(ref subject) = self.subject {
|
||||
if !self.is_send_by_messenger {
|
||||
if !self.has_chat_version() {
|
||||
part.msg = subject.to_string();
|
||||
}
|
||||
}
|
||||
@@ -342,6 +336,10 @@ impl<'a> MimeParser<'a> {
|
||||
self.parts.iter_mut().rev().find(|part| !part.is_meta)
|
||||
}
|
||||
|
||||
pub(crate) fn has_chat_version(&self) -> bool {
|
||||
self.header.contains_key("chat-version")
|
||||
}
|
||||
|
||||
pub fn lookup_field(&self, field_name: &str) -> Option<&String> {
|
||||
self.header.get(&field_name.to_lowercase())
|
||||
}
|
||||
@@ -541,15 +539,12 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
// check header directly as is_send_by_messenger is not yet set up
|
||||
let is_msgrmsg = self.lookup_field("Chat-Version").is_some();
|
||||
|
||||
let mut simplifier = Simplify::new();
|
||||
let simplified_txt = if decoded_data.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
let is_html = mime_type == mime::TEXT_HTML;
|
||||
simplifier.simplify(&decoded_data, is_html, is_msgrmsg)
|
||||
simplifier.simplify(&decoded_data, is_html, self.has_chat_version())
|
||||
};
|
||||
|
||||
if !simplified_txt.is_empty() {
|
||||
@@ -766,11 +761,11 @@ impl<'a> MimeParser<'a> {
|
||||
mdn_consumed = true;
|
||||
}
|
||||
|
||||
if self.is_send_by_messenger || mdn_consumed {
|
||||
if self.has_chat_version() || mdn_consumed {
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
if self.is_send_by_messenger && self.context.get_config_bool(Config::MvboxMove) {
|
||||
if self.has_chat_version() && self.context.get_config_bool(Config::MvboxMove) {
|
||||
param.set_int(Param::AlsoMove, 1);
|
||||
}
|
||||
job_add(self.context, Action::MarkseenMdnOnImap, 0, param, 0);
|
||||
|
||||
Reference in New Issue
Block a user