mirror of
https://github.com/chatmail/core.git
synced 2026-10-17 18:33:01 +03:00
Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb325be888 | ||
|
|
f3137d87c5 | ||
|
|
9d033e2ea1 | ||
|
|
cdc15ce980 | ||
|
|
f017ffa0b3 | ||
|
|
28c79516da | ||
|
|
af1be65e8b | ||
|
|
93ffcf530d | ||
|
|
028187eeaf | ||
|
|
032b387c95 | ||
|
|
5523e7ba19 | ||
|
|
b54c2a3196 | ||
|
|
42d8502531 | ||
|
|
6002f54ce1 | ||
|
|
a830d499eb | ||
|
|
49e821d029 | ||
|
|
010cf7140f | ||
|
|
e1773edf7f | ||
|
|
b4337685cf | ||
|
|
997c5ad68a | ||
|
|
67b486bfbd | ||
|
|
f5ee9c530d | ||
|
|
0189644f08 | ||
|
|
0ca3e3100e | ||
|
|
d4c4070b3f | ||
|
|
51133ce0d6 | ||
|
|
26b0c43cc4 | ||
|
|
779a906d97 | ||
|
|
7e78d6a92b | ||
|
|
6483b8c138 | ||
|
|
9702647044 | ||
|
|
3b2192a046 | ||
|
|
b864911e18 | ||
|
|
9e0506cb48 | ||
|
|
a0000b9489 | ||
|
|
df3fac4e5e | ||
|
|
8a36f55439 | ||
|
|
ab253744f8 | ||
|
|
fff1eaba45 | ||
|
|
eafb7b979d | ||
|
|
9e22bf39cc | ||
|
|
fc6691ce5b | ||
|
|
598dc86ca5 | ||
|
|
3e2bfc35e3 | ||
|
|
6658ad8618 | ||
|
|
759ccdbee2 | ||
|
|
635060a02d | ||
|
|
a128e7e7ab | ||
|
|
3cb69b8035 | ||
|
|
4b744337fe | ||
|
|
8d7d2f7a44 | ||
|
|
24bf1dbffb | ||
|
|
c1890bb126 | ||
|
|
a4a570896a |
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
- let `dc_set_muted()` also mute one-to-one chats #1470
|
||||
|
||||
- fix a but that led to load and traffic if the server does not use sent-folder
|
||||
- fix a bug that led to load and traffic if the server does not use sent-folder
|
||||
#1472
|
||||
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ typedef struct _dc_provider dc_provider_t;
|
||||
* - Strings in function arguments or return values are usually UTF-8 encoded.
|
||||
*
|
||||
* - The issue-tracker for the core library is here:
|
||||
* <https://github.com/deltachat/deltachat-core/issues>
|
||||
* <https://github.com/deltachat/deltachat-core-rust/issues>
|
||||
*
|
||||
* The following points are important mainly
|
||||
* for the authors of the library itself:
|
||||
@@ -380,7 +380,8 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* Messages are deleted whether they were seen or not, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
* - `delete_server_after` = 0=do not delete messages from server automatically (default),
|
||||
* >=1=seconds, after which messages are deleted automatically from the server.
|
||||
* 1=delete messages directly after receiving from server, mvbox is skipped.
|
||||
* >1=seconds, after which messages are deleted automatically from the server, mvbox is used as defined.
|
||||
* "Saved messages" are deleted from the server as well as
|
||||
* emails matching the `show_emails` settings above, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
@@ -1480,6 +1481,16 @@ void dc_delete_chat (dc_context_t* context, uint32_t ch
|
||||
*/
|
||||
dc_array_t* dc_get_chat_contacts (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
/**
|
||||
* Get the chat's autodelete message timer.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @param chat_id The chat ID.
|
||||
*
|
||||
* @return autodelete timer value in seconds or 0 if the timer is disabled.
|
||||
*/
|
||||
uint32_t dc_get_chat_autodelete_timer (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
/**
|
||||
* Search messages containing the given query string.
|
||||
@@ -1612,6 +1623,17 @@ int dc_remove_contact_from_chat (dc_context_t* context, uint32_t ch
|
||||
*/
|
||||
int dc_set_chat_name (dc_context_t* context, uint32_t chat_id, const char* name);
|
||||
|
||||
/**
|
||||
* Set the chat's autodelete message timer.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @param chat_id The chat ID to set the autodelete message timer for.
|
||||
* @param timer The timer value in seconds or 0 to disable the timer.
|
||||
*
|
||||
* @return 1=success, 0=error
|
||||
*/
|
||||
int dc_set_chat_autodelete_timer (dc_context_t* context, uint32_t chat_id, uint32_t timer);
|
||||
|
||||
/**
|
||||
* Set group profile image.
|
||||
@@ -1639,7 +1661,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch
|
||||
/**
|
||||
* Set mute duration of a chat.
|
||||
*
|
||||
* This value can be checked by the ui upon receiving a new message to decide whether it should trigger an notification.
|
||||
* The ui can then call dc_chat_is_muted() when receiving a new message to decide whether it should trigger an notification.
|
||||
*
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED.
|
||||
*
|
||||
@@ -2860,6 +2882,7 @@ char* dc_chat_get_name (const dc_chat_t* chat);
|
||||
char* dc_chat_get_profile_image (const dc_chat_t* chat);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a color for the chat.
|
||||
* For 1:1 chats, the color is calculated from the contact's email address.
|
||||
@@ -2977,7 +3000,7 @@ int dc_chat_is_sending_locations (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the chat is currently muted
|
||||
* Check whether the chat is currently muted (can be changed by dc_set_chat_mute_duration()).
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
* @param chat The chat object.
|
||||
@@ -4382,6 +4405,17 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_EVENT_MSG_READ 2015
|
||||
|
||||
/**
|
||||
* Time left until the next message deletion has changed.
|
||||
*
|
||||
* The only parameter is the number of seconds left until next message
|
||||
* deletion. It is rounded up. If the timer is 0, it means there are no
|
||||
* messages to be deleted in the future.
|
||||
*
|
||||
* @param data1 (int) Timer in seconds
|
||||
* @param data2 (int) 0
|
||||
*/
|
||||
#define DC_EVENT_MSG_DELETE_TIMEOUT_CHANGED 2016
|
||||
|
||||
/**
|
||||
* Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
@@ -4394,6 +4428,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_EVENT_CHAT_MODIFIED 2020
|
||||
|
||||
/**
|
||||
* Chat autodelete timer changed.
|
||||
*/
|
||||
#define DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED 2021
|
||||
|
||||
|
||||
/**
|
||||
* Contact(s) created, renamed, verified, blocked or deleted.
|
||||
|
||||
@@ -167,9 +167,20 @@ impl ContextWrapper {
|
||||
msg_id.to_u32() as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::MsgDeleteTimeoutChanged { timer } => {
|
||||
ffi_cb(self, event_id, timer as uintptr_t, 0);
|
||||
}
|
||||
Event::ChatModified(chat_id) => {
|
||||
ffi_cb(self, event_id, chat_id.to_u32() as uintptr_t, 0);
|
||||
}
|
||||
Event::ChatAutodeleteTimerModified { chat_id, timer } => {
|
||||
ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id.to_u32() as uintptr_t,
|
||||
timer as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
|
||||
let id = id.unwrap_or_default();
|
||||
ffi_cb(self, event_id, id as uintptr_t, 0);
|
||||
@@ -1451,6 +1462,41 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration(
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_chat_autodelete_timer(
|
||||
context: *mut dc_context_t,
|
||||
chat_id: u32,
|
||||
) -> u32 {
|
||||
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
eprintln!("ignoring careless call to dc_set_chat_autodelete_timer()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| chat::get_autodelete_timer(ctx, ChatId::new(chat_id)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_set_chat_autodelete_timer(
|
||||
context: *mut dc_context_t,
|
||||
chat_id: u32,
|
||||
timer: u32,
|
||||
) -> libc::c_int {
|
||||
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
eprintln!("ignoring careless call to dc_set_chat_autodelete_timer()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::set_autodelete_timer(ctx, ChatId::new(chat_id), timer)
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(ctx, "Failed to set autodelete timer")
|
||||
})
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_msg_info(
|
||||
context: *mut dc_context_t,
|
||||
|
||||
@@ -247,10 +247,8 @@ const IMEX_COMMANDS: [&str; 12] = [
|
||||
"stop",
|
||||
];
|
||||
|
||||
const DB_COMMANDS: [&str; 11] = [
|
||||
const DB_COMMANDS: [&str; 9] = [
|
||||
"info",
|
||||
"open",
|
||||
"close",
|
||||
"set",
|
||||
"get",
|
||||
"oauth2",
|
||||
|
||||
@@ -30,7 +30,7 @@ class Account(object):
|
||||
"""
|
||||
MissingCredentials = MissingCredentials
|
||||
|
||||
def __init__(self, db_path, os_name=None):
|
||||
def __init__(self, db_path, os_name=None, logging=True):
|
||||
""" initialize account object.
|
||||
|
||||
:param db_path: a path to the account database. The database
|
||||
@@ -39,6 +39,7 @@ class Account(object):
|
||||
"""
|
||||
# initialize per-account plugin system
|
||||
self._pm = hookspec.PerAccount._make_plugin_manager()
|
||||
self._logging = logging
|
||||
self.add_account_plugin(self)
|
||||
|
||||
self._dc_context = ffi.gc(
|
||||
@@ -63,6 +64,14 @@ class Account(object):
|
||||
atexit.register(self.shutdown)
|
||||
hook.dc_account_init(account=self)
|
||||
|
||||
def disable_logging(self):
|
||||
""" disable logging. """
|
||||
self._logging = False
|
||||
|
||||
def enable_logging(self):
|
||||
""" re-enable logging. """
|
||||
self._logging = True
|
||||
|
||||
@hookspec.account_hookimpl
|
||||
def ac_process_ffi_event(self, ffi_event):
|
||||
for name, kwargs in self._map_ffi_event(ffi_event):
|
||||
@@ -73,7 +82,8 @@ class Account(object):
|
||||
# self.shutdown()
|
||||
|
||||
def ac_log_line(self, msg):
|
||||
self._pm.hook.ac_log_line(message=msg)
|
||||
if self._logging:
|
||||
self._pm.hook.ac_log_line(message=msg)
|
||||
|
||||
def _check_config_key(self, name):
|
||||
if name not in self._configkeys:
|
||||
|
||||
@@ -138,6 +138,22 @@ class Chat(object):
|
||||
"""
|
||||
return bool(lib.dc_chat_get_remaining_mute_duration(self.id))
|
||||
|
||||
def get_autodelete_timer(self):
|
||||
""" get autodelete timer.
|
||||
|
||||
:returns: autodelete timer value in seconds
|
||||
"""
|
||||
return lib.dc_get_chat_autodelete_timer(self._dc_context, self.id)
|
||||
|
||||
def set_autodelete_timer(self, timer):
|
||||
""" set autodelete timer.
|
||||
|
||||
:param: timer value in seconds
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
return lib.dc_set_chat_autodelete_timer(self._dc_context, self.id, timer)
|
||||
|
||||
def get_type(self):
|
||||
""" (deprecated) return type of this chat.
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ DC_EVENT_MSG_DELIVERED = 2010
|
||||
DC_EVENT_MSG_FAILED = 2012
|
||||
DC_EVENT_MSG_READ = 2015
|
||||
DC_EVENT_CHAT_MODIFIED = 2020
|
||||
DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED = 2021
|
||||
DC_EVENT_CONTACTS_CHANGED = 2030
|
||||
DC_EVENT_LOCATION_CHANGED = 2035
|
||||
DC_EVENT_CONFIGURE_PROGRESS = 2041
|
||||
|
||||
@@ -5,9 +5,13 @@ import subprocess
|
||||
import queue
|
||||
import threading
|
||||
import fnmatch
|
||||
import time
|
||||
import weakref
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
import time
|
||||
|
||||
from . import Account, const
|
||||
from .tracker import ConfigureTracker
|
||||
from .capi import lib
|
||||
@@ -15,7 +19,7 @@ from .eventlogger import FFIEventLogger, FFIEventTracker
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest._code import Source
|
||||
|
||||
import tempfile
|
||||
import deltachat
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -40,11 +44,55 @@ def pytest_configure(config):
|
||||
if cfg:
|
||||
config.option.liveconfig = cfg
|
||||
|
||||
# Make sure we don't get garbled output because threads keep running
|
||||
# collect all ever created accounts in a weakref-set (so we don't
|
||||
# keep objects unneccessarily alive) and enable/disable logging
|
||||
# for each pytest test phase # (setup/call/teardown).
|
||||
# Additionally make the acfactory use a logging/no-logging default.
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if (list(item.iter_markers(name="ignored"))
|
||||
and not item.config.getoption("ignored")):
|
||||
pytest.skip("Ignored tests not requested, use --ignored")
|
||||
class LoggingAspect:
|
||||
def __init__(self):
|
||||
self._accounts = weakref.WeakSet()
|
||||
|
||||
@deltachat.global_hookimpl
|
||||
def dc_account_init(self, account):
|
||||
self._accounts.add(account)
|
||||
|
||||
def disable_logging(self, item):
|
||||
for acc in self._accounts:
|
||||
acc.disable_logging()
|
||||
acfactory = item.funcargs.get("acfactory")
|
||||
if acfactory:
|
||||
acfactory.set_logging_default(False)
|
||||
|
||||
def enable_logging(self, item):
|
||||
for acc in self._accounts:
|
||||
acc.enable_logging()
|
||||
acfactory = item.funcargs.get("acfactory")
|
||||
if acfactory:
|
||||
acfactory.set_logging_default(True)
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_setup(self, item):
|
||||
self.enable_logging(item)
|
||||
yield
|
||||
self.disable_logging(item)
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(self, pyfuncitem):
|
||||
self.enable_logging(pyfuncitem)
|
||||
yield
|
||||
self.disable_logging(pyfuncitem)
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_teardown(self, item):
|
||||
self.enable_logging(item)
|
||||
yield
|
||||
self.disable_logging(item)
|
||||
|
||||
la = LoggingAspect()
|
||||
config.pluginmanager.register(la)
|
||||
deltachat.register_global_plugin(la)
|
||||
|
||||
|
||||
def pytest_report_header(config, startdir):
|
||||
@@ -164,24 +212,34 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
|
||||
self.live_count = 0
|
||||
self.offline_count = 0
|
||||
self._finalizers = []
|
||||
self._accounts = []
|
||||
self.init_time = time.time()
|
||||
self._generated_keys = ["alice", "bob", "charlie",
|
||||
"dom", "elena", "fiona"]
|
||||
self.set_logging_default(False)
|
||||
|
||||
def finalize(self):
|
||||
while self._finalizers:
|
||||
fin = self._finalizers.pop()
|
||||
fin()
|
||||
|
||||
while self._accounts:
|
||||
acc = self._accounts.pop()
|
||||
acc.shutdown()
|
||||
acc.disable_logging()
|
||||
|
||||
def make_account(self, path, logid, quiet=False):
|
||||
ac = Account(path)
|
||||
ac = Account(path, logging=self._logging)
|
||||
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
|
||||
ac._configtracker = ac.add_account_plugin(ConfigureTracker())
|
||||
if not quiet:
|
||||
ac.add_account_plugin(FFIEventLogger(ac, logid=logid))
|
||||
self._finalizers.append(ac.shutdown)
|
||||
self._accounts.append(ac)
|
||||
return ac
|
||||
|
||||
def set_logging_default(self, logging):
|
||||
self._logging = bool(logging)
|
||||
|
||||
def get_unconfigured_account(self):
|
||||
self.offline_count += 1
|
||||
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
|
||||
|
||||
@@ -1473,6 +1473,59 @@ class TestOnlineAccount:
|
||||
locations3 = chat2.get_locations(contact=contact)
|
||||
assert not locations3
|
||||
|
||||
def test_autodelete_timer(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat1 = self.get_chat(ac1, ac2)
|
||||
chat2 = self.get_chat(ac2, ac1)
|
||||
|
||||
lp.sec("ac1: set autodelete timer to 60")
|
||||
chat1.set_autodelete_timer(60)
|
||||
|
||||
lp.sec("ac1: check that autodelete timer is set for chat")
|
||||
assert chat1.get_autodelete_timer() == 60
|
||||
chat1_summary = chat1.get_summary()
|
||||
assert chat1_summary["autodelete_timer"] == 60
|
||||
|
||||
lp.sec("ac2: receive system message about autodelete timer modification")
|
||||
ac2._evtracker.get_matching("DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED")
|
||||
incoming_message_event1 = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
system_message1 = ac2.get_message_by_id(incoming_message_event1.data2)
|
||||
assert chat2.get_autodelete_timer() == 60
|
||||
assert system_message1.is_system_message()
|
||||
assert "Autodelete timer: 60\n" in system_message1.get_message_info()
|
||||
|
||||
lp.sec("ac2: send message to ac1")
|
||||
sent_message = chat2.send_text("message")
|
||||
assert sent_message.is_encrypted()
|
||||
assert "Autodelete timer: 60\n" in sent_message.get_message_info()
|
||||
|
||||
# Timer is started immediately for sent messages
|
||||
assert "Expires: " in sent_message.get_message_info()
|
||||
|
||||
lp.sec("ac1: waiting for message from ac2")
|
||||
incoming_message_event2 = ac1._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
text_message = ac1.get_message_by_id(incoming_message_event2.data2)
|
||||
assert text_message.text == "message"
|
||||
assert text_message.is_encrypted()
|
||||
assert "Autodelete timer: 60\n" in text_message.get_message_info()
|
||||
|
||||
# Timer should not start until message is displayed
|
||||
assert "Expires: " not in text_message.get_message_info()
|
||||
text_message.mark_seen()
|
||||
assert "Expires: " in text_message.get_message_info()
|
||||
|
||||
lp.sec("ac2: set autodelete timer to 0")
|
||||
chat2.set_autodelete_timer(0)
|
||||
|
||||
lp.sec("ac1: receive system message about autodelete timer modification")
|
||||
ac1._evtracker.get_matching("DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED")
|
||||
incoming_message_event3 = ac1._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
system_message2 = ac1.get_message_by_id(incoming_message_event3.data2)
|
||||
assert "Autodelete timer: " not in system_message2.get_message_info()
|
||||
assert chat1.get_autodelete_timer() == 0
|
||||
|
||||
|
||||
class TestGroupStressTests:
|
||||
def test_group_many_members_add_leave_remove(self, acfactory, lp):
|
||||
|
||||
+158
-33
@@ -1,6 +1,6 @@
|
||||
//! # Chat module
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
@@ -663,6 +663,7 @@ impl Chat {
|
||||
profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new),
|
||||
draft,
|
||||
is_muted: self.is_muted(),
|
||||
autodelete_timer: get_autodelete_timer(context, self.id),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -881,12 +882,20 @@ impl Chat {
|
||||
);
|
||||
}
|
||||
|
||||
// get autodelete timer
|
||||
let autodelete_timer = get_autodelete_timer(context, self.id);
|
||||
let autodelete_timestamp = if autodelete_timer == 0 {
|
||||
0
|
||||
} else {
|
||||
timestamp + i64::from(autodelete_timer)
|
||||
};
|
||||
|
||||
// add message to the database
|
||||
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);",
|
||||
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id, autodelete_timer, autodelete_timestamp) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?,?,?);",
|
||||
params![
|
||||
new_rfc724_mid,
|
||||
self.id,
|
||||
@@ -901,6 +910,8 @@ impl Chat {
|
||||
new_in_reply_to,
|
||||
new_references,
|
||||
location_id as i32,
|
||||
autodelete_timer,
|
||||
autodelete_timestamp
|
||||
]
|
||||
).is_ok() {
|
||||
msg_id = sql::get_rowid(
|
||||
@@ -920,6 +931,7 @@ impl Chat {
|
||||
} else {
|
||||
error!(context, "Cannot send message, not configured.",);
|
||||
}
|
||||
update_autodelete_timeout(context);
|
||||
|
||||
Ok(MsgId::new(msg_id))
|
||||
}
|
||||
@@ -1022,6 +1034,8 @@ pub struct ChatInfo {
|
||||
// - [ ] lastUpdated,
|
||||
// - [ ] freshMessageCounter,
|
||||
// - [ ] email
|
||||
/// Automatic message deletion timer.
|
||||
pub autodelete_timer: u32,
|
||||
}
|
||||
|
||||
/// Create a chat from a message ID.
|
||||
@@ -1587,46 +1601,99 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emits an event to update the time until the next local deletion.
|
||||
///
|
||||
/// This takes into account only per-chat timeouts, because global device
|
||||
/// timeouts are at least one hour long and deletion is triggered often enough
|
||||
/// by user actions.
|
||||
pub fn update_autodelete_timeout(context: &Context) {
|
||||
let autodelete_timestamp: Option<i64> = match context.sql.query_get_value_result(
|
||||
"SELECT autodelete_timestamp \
|
||||
FROM msgs \
|
||||
WHERE autodelete_timestamp != 0 \
|
||||
AND chat_id != ? \
|
||||
ORDER BY autodelete_timestamp ASC \
|
||||
LIMIT 1",
|
||||
params![DC_CHAT_ID_TRASH],
|
||||
) {
|
||||
Err(err) => {
|
||||
warn!(context, "Can't calculate next autodelete timeout: {}", err);
|
||||
return;
|
||||
}
|
||||
Ok(autodelete_timestamp) => autodelete_timestamp,
|
||||
};
|
||||
|
||||
let timer = if let Some(next_autodelete) = autodelete_timestamp {
|
||||
let now = time();
|
||||
if now > next_autodelete {
|
||||
1
|
||||
} else {
|
||||
(next_autodelete - now)
|
||||
.try_into()
|
||||
.unwrap_or(u32::MAX)
|
||||
.saturating_add(1)
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
emit_event!(context, Event::MsgDeleteTimeoutChanged { timer });
|
||||
}
|
||||
|
||||
/// Deletes messages which are expired according to "delete_device_after" setting.
|
||||
///
|
||||
/// Returns true if any message is deleted, so event can be emitted. If nothing
|
||||
/// has been deleted, returns false.
|
||||
pub fn delete_device_expired_messages(context: &Context) -> Result<bool, Error> {
|
||||
if let Some(delete_device_after) = context.get_config_delete_device_after() {
|
||||
let threshold_timestamp = time() - delete_device_after;
|
||||
pub(crate) fn delete_device_expired_messages(context: &Context) -> Result<bool, Error> {
|
||||
let now = time();
|
||||
|
||||
let self_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_SELF)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
let device_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
let threshold_timestamp = match context.get_config_delete_device_after() {
|
||||
None => 0,
|
||||
Some(delete_device_after) => now - delete_device_after,
|
||||
};
|
||||
|
||||
// Delete expired messages
|
||||
//
|
||||
// Only update the rows that have to be updated, to avoid emitting
|
||||
// unnecessary "chat modified" events.
|
||||
let rows_modified = context.sql.execute(
|
||||
"UPDATE msgs \
|
||||
let self_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_SELF)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
let device_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
|
||||
// Delete expired messages
|
||||
//
|
||||
// Only update the rows that have to be updated, to avoid emitting
|
||||
// unnecessary "chat modified" events.
|
||||
let rows_modified = context.sql.execute(
|
||||
"UPDATE msgs \
|
||||
SET txt = 'DELETED', chat_id = ? \
|
||||
WHERE timestamp < ? \
|
||||
WHERE \
|
||||
( \
|
||||
( \
|
||||
timestamp < ? \
|
||||
AND chat_id != ? \
|
||||
) \
|
||||
OR \
|
||||
( \
|
||||
autodelete_timestamp != 0 \
|
||||
AND autodelete_timestamp < ? \
|
||||
) \
|
||||
) \
|
||||
AND chat_id > ? \
|
||||
AND chat_id != ? \
|
||||
AND chat_id != ? \
|
||||
AND NOT hidden",
|
||||
params![
|
||||
DC_CHAT_ID_TRASH,
|
||||
threshold_timestamp,
|
||||
DC_CHAT_ID_LAST_SPECIAL,
|
||||
self_chat_id,
|
||||
device_chat_id
|
||||
],
|
||||
)?;
|
||||
AND chat_id != ?",
|
||||
params![
|
||||
DC_CHAT_ID_TRASH,
|
||||
threshold_timestamp,
|
||||
self_chat_id,
|
||||
now,
|
||||
DC_CHAT_ID_LAST_SPECIAL,
|
||||
device_chat_id
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(rows_modified > 0)
|
||||
} else {
|
||||
Ok(false)
|
||||
let updated = rows_modified > 0;
|
||||
if updated {
|
||||
update_autodelete_timeout(context);
|
||||
}
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
pub fn get_chat_media(
|
||||
@@ -2568,6 +2635,63 @@ pub(crate) fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef<
|
||||
});
|
||||
}
|
||||
|
||||
/// Get autodelete timer value in seconds.
|
||||
pub fn get_autodelete_timer(context: &Context, chat_id: ChatId) -> u32 {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT autodelete_timer FROM chats WHERE id=?;",
|
||||
params![chat_id],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Set autodelete timer value without sending a message.
|
||||
///
|
||||
/// Used when a message arrives indicating that someone else has
|
||||
/// changed the timer value for a chat.
|
||||
pub(crate) fn inner_set_autodelete_timer(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
timer: u32,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(!chat_id.is_special(), "Invalid chat ID");
|
||||
|
||||
context.sql.execute(
|
||||
"UPDATE chats
|
||||
SET autodelete_timer=?
|
||||
WHERE id=?;",
|
||||
params![timer, chat_id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set autodelete timer value in seconds.
|
||||
///
|
||||
/// If timer value is 0, disable autodelete timer.
|
||||
pub fn set_autodelete_timer(context: &Context, chat_id: ChatId, timer: u32) -> Result<(), Error> {
|
||||
if timer == get_autodelete_timer(context, chat_id) {
|
||||
return Ok(());
|
||||
}
|
||||
inner_set_autodelete_timer(context, chat_id, timer)?;
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgAutodeleteTimerChanged,
|
||||
timer.to_string(),
|
||||
"",
|
||||
0,
|
||||
));
|
||||
msg.param.set_cmd(SystemMessage::AutodeleteTimerChanged);
|
||||
if let Err(err) = send_msg(context, chat_id, &mut msg) {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to send a message about autodelete timer change: {:?}", err
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -2598,7 +2722,8 @@ mod tests {
|
||||
"color": 15895624,
|
||||
"profile_image": "",
|
||||
"draft": "",
|
||||
"is_muted": false
|
||||
"is_muted": false,
|
||||
"autodelete_timer": 0
|
||||
}
|
||||
"#;
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status {
|
||||
let mut smtp_connected_here = false;
|
||||
|
||||
let mut param_autoconfig: Option<LoginParam> = None;
|
||||
let was_configured_before = context.is_configured();
|
||||
|
||||
context
|
||||
.inbox_thread
|
||||
@@ -441,6 +442,15 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status {
|
||||
}
|
||||
|
||||
if let Some(provider) = provider::get_provider_info(¶m.addr) {
|
||||
if !was_configured_before {
|
||||
if let Some(config_defaults) = &provider.config_defaults {
|
||||
for def in config_defaults.iter() {
|
||||
info!(context, "apply config_defaults {}={}", def.key, def.value);
|
||||
context.set_config(def.key, Some(def.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !provider.after_login_hint.is_empty() {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(provider.after_login_hint.to_string());
|
||||
|
||||
+5
-1
@@ -240,7 +240,10 @@ impl Context {
|
||||
.sql
|
||||
.get_raw_config_int(self, "dbversion")
|
||||
.unwrap_or_default();
|
||||
|
||||
let journal_mode = self
|
||||
.sql
|
||||
.query_get_value(self, "PRAGMA journal_mode;", rusqlite::NO_PARAMS)
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
|
||||
let bcc_self = self.get_config_int(Config::BccSelf);
|
||||
@@ -285,6 +288,7 @@ impl Context {
|
||||
res.insert("number_of_contacts", contacts.to_string());
|
||||
res.insert("database_dir", self.get_dbfile().display().to_string());
|
||||
res.insert("database_version", dbversion.to_string());
|
||||
res.insert("journal_mode", journal_mode);
|
||||
res.insert("blobdir", self.get_blobdir().display().to_string());
|
||||
res.insert("display_name", displayname.unwrap_or_else(|| unset.into()));
|
||||
res.insert(
|
||||
|
||||
+58
-2
@@ -221,6 +221,19 @@ pub fn dc_receive_imf(
|
||||
}
|
||||
}
|
||||
|
||||
// if we delete we don't need to try moving messages
|
||||
if needs_delete_job && !created_db_entries.is_empty() {
|
||||
job_add(
|
||||
context,
|
||||
Action::DeleteMsgOnImap,
|
||||
created_db_entries[0].1.to_u32() as i32,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
context.do_heuristics_moves(server_folder.as_ref(), insert_msg_id);
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"received message {} has Message-Id: {}", server_uid, rfc724_mid
|
||||
@@ -578,6 +591,48 @@ fn add_parts(
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract autodelete timer from the message.
|
||||
let timer = if let Some(value) = mime_parser.get(HeaderDef::AutodeleteTimer) {
|
||||
match value.parse::<u32>() {
|
||||
Ok(timer) => timer,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"can't parse autodelete timer \"{}\": {}", value, err
|
||||
);
|
||||
0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Apply autodelete timer changes to the chat.
|
||||
if chat::get_autodelete_timer(context, *chat_id) != timer {
|
||||
match chat::inner_set_autodelete_timer(context, *chat_id, timer) {
|
||||
Ok(()) => {
|
||||
let stock_str = context.stock_system_msg(
|
||||
StockMessage::MsgAutodeleteTimerChanged,
|
||||
timer.to_string(),
|
||||
"",
|
||||
from_id,
|
||||
);
|
||||
chat::add_info_msg(context, *chat_id, stock_str);
|
||||
context.call_cb(Event::ChatAutodeleteTimerModified {
|
||||
chat_id: *chat_id,
|
||||
timer,
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"failed to modify timer for chat {}: {}", chat_id, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// correct message_timestamp, it should not be used before,
|
||||
// however, we cannot do this earlier as we need from_id to be set
|
||||
calc_timestamps(
|
||||
@@ -617,8 +672,8 @@ fn add_parts(
|
||||
"INSERT INTO msgs \
|
||||
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
||||
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
|
||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
|
||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references, autodelete_timer) \
|
||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?);",
|
||||
|mut stmt, conn| {
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
|
||||
@@ -671,6 +726,7 @@ fn add_parts(
|
||||
},
|
||||
mime_in_reply_to,
|
||||
mime_references,
|
||||
timer
|
||||
])?;
|
||||
|
||||
txt_raw = None;
|
||||
|
||||
@@ -132,13 +132,28 @@ pub enum Event {
|
||||
#[strum(props(id = "2015"))]
|
||||
MsgRead { chat_id: ChatId, msg_id: MsgId },
|
||||
|
||||
/// Time left until the next message deletion has changed.
|
||||
///
|
||||
/// The only parameter is the number of seconds left until next message
|
||||
/// deletion. It is rounded up. If the timer is 0, it means there are no
|
||||
/// messages to be deleted in the future.
|
||||
#[strum(props(id = "2016"))]
|
||||
MsgDeleteTimeoutChanged { timer: u32 },
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// This event does not include autodelete timer modification, which
|
||||
/// is a separate event.
|
||||
#[strum(props(id = "2020"))]
|
||||
ChatModified(ChatId),
|
||||
|
||||
/// Chat autodelete timer changed.
|
||||
#[strum(props(id = "2021"))]
|
||||
ChatAutodeleteTimerModified { chat_id: ChatId, timer: u32 },
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
|
||||
@@ -41,6 +41,7 @@ pub enum HeaderDef {
|
||||
SecureJoinFingerprint,
|
||||
SecureJoinInvitenumber,
|
||||
SecureJoinAuth,
|
||||
AutodeleteTimer,
|
||||
_TestHeader,
|
||||
}
|
||||
|
||||
|
||||
+14
-21
@@ -135,7 +135,7 @@ impl async_imap::Authenticator for OAuth2 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum FolderMeaning {
|
||||
Unknown,
|
||||
SentObjects,
|
||||
@@ -1063,13 +1063,15 @@ impl Imap {
|
||||
}
|
||||
};
|
||||
|
||||
let sentbox_folder =
|
||||
folders
|
||||
.iter()
|
||||
.find(|folder| match get_folder_meaning(folder) {
|
||||
FolderMeaning::SentObjects => true,
|
||||
_ => false,
|
||||
});
|
||||
let sentbox_folder = folders
|
||||
.iter()
|
||||
.find(|folder| get_folder_meaning(folder) == FolderMeaning::SentObjects)
|
||||
.or_else(|| {
|
||||
info!(context, "can't find sentbox by attributes, checking names");
|
||||
folders.iter().find(|folder| {
|
||||
get_folder_meaning_by_name(folder) == FolderMeaning::SentObjects
|
||||
})
|
||||
});
|
||||
info!(context, "sentbox folder is {:?}", sentbox_folder);
|
||||
|
||||
let mut delimiter = ".";
|
||||
@@ -1235,7 +1237,7 @@ impl Imap {
|
||||
// CAVE: if possible, take care not to add a name here that is "sent" in one language
|
||||
// but sth. different in others - a hard job.
|
||||
fn get_folder_meaning_by_name(folder_name: &Name) -> FolderMeaning {
|
||||
let sent_names = vec!["sent", "sent objects", "gesendet"];
|
||||
let sent_names = vec!["sent", "sentmail", "sent objects", "gesendet"];
|
||||
let lower = folder_name.name().to_lowercase();
|
||||
|
||||
if sent_names.into_iter().any(|s| s == lower) {
|
||||
@@ -1246,27 +1248,18 @@ fn get_folder_meaning_by_name(folder_name: &Name) -> FolderMeaning {
|
||||
}
|
||||
|
||||
fn get_folder_meaning(folder_name: &Name) -> FolderMeaning {
|
||||
if folder_name.attributes().is_empty() {
|
||||
return FolderMeaning::Unknown;
|
||||
}
|
||||
|
||||
let mut res = FolderMeaning::Unknown;
|
||||
let special_names = vec!["\\Spam", "\\Trash", "\\Drafts", "\\Junk"];
|
||||
|
||||
for attr in folder_name.attributes() {
|
||||
if let NameAttribute::Custom(ref label) = attr {
|
||||
if special_names.iter().any(|s| *s == label) {
|
||||
res = FolderMeaning::Other;
|
||||
return FolderMeaning::Other;
|
||||
} else if label == "\\Sent" {
|
||||
res = FolderMeaning::SentObjects
|
||||
return FolderMeaning::SentObjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match res {
|
||||
FolderMeaning::Unknown => get_folder_meaning_by_name(folder_name),
|
||||
_ => res,
|
||||
}
|
||||
FolderMeaning::Unknown
|
||||
}
|
||||
|
||||
fn precheck_imf(
|
||||
|
||||
+16
-12
@@ -994,19 +994,23 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
}
|
||||
|
||||
fn load_imap_deletion_msgid(context: &Context) -> sql::Result<Option<MsgId>> {
|
||||
if let Some(delete_server_after) = context.get_config_delete_server_after() {
|
||||
let threshold_timestamp = time() - delete_server_after;
|
||||
let now = time();
|
||||
|
||||
context.sql.query_row_optional(
|
||||
"SELECT id FROM msgs \
|
||||
WHERE timestamp < ? \
|
||||
AND server_uid != 0",
|
||||
params![threshold_timestamp],
|
||||
|row| row.get::<_, MsgId>(0),
|
||||
)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
let threshold_timestamp = match context.get_config_delete_server_after() {
|
||||
None => 0,
|
||||
Some(delete_server_after) => now - delete_server_after,
|
||||
};
|
||||
|
||||
context.sql.query_row_optional(
|
||||
"SELECT id FROM msgs \
|
||||
WHERE ( \
|
||||
timestamp < ? \
|
||||
OR (autodelete_timestamp != 0 AND autodelete_timestamp < ?) \
|
||||
) \
|
||||
AND server_uid != 0",
|
||||
params![threshold_timestamp, now],
|
||||
|row| row.get::<_, MsgId>(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn load_imap_deletion_job(context: &Context) -> sql::Result<Option<Job>> {
|
||||
|
||||
+56
-1
@@ -6,7 +6,7 @@ use deltachat_derive::{FromSql, ToSql};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::chat::{self, Chat, ChatId};
|
||||
use crate::chat::{self, update_autodelete_timeout, Chat, ChatId};
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::*;
|
||||
@@ -133,6 +133,36 @@ impl MsgId {
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns autodelete timer value for the message.
|
||||
pub(crate) fn autodelete_timer(self, context: &Context) -> sql::Result<Option<i64>> {
|
||||
let res = match context.sql.query_get_value_result(
|
||||
"SELECT autodelete_timer FROM msgs WHERE id=?",
|
||||
params![self],
|
||||
)? {
|
||||
None | Some(0) => None,
|
||||
Some(timer) => Some(timer),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Starts autodelete timer for the message if it is not started yet.
|
||||
pub(crate) fn start_autodelete_timer(self, context: &Context) -> sql::Result<()> {
|
||||
if let Some(autodelete_timer) = self.autodelete_timer(context)? {
|
||||
let autodelete_timestamp = time() + autodelete_timer;
|
||||
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET autodelete_timestamp = ? \
|
||||
WHERE (autodelete_timestamp == 0 OR autodelete_timestamp > ?) \
|
||||
AND id = ?",
|
||||
params![autodelete_timestamp, autodelete_timestamp, self],
|
||||
)?;
|
||||
update_autodelete_timeout(context);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bad evil escape hatch.
|
||||
///
|
||||
/// Avoid using this, eventually types should be cleaned up enough
|
||||
@@ -247,6 +277,8 @@ pub struct Message {
|
||||
pub(crate) timestamp_sort: i64,
|
||||
pub(crate) timestamp_sent: i64,
|
||||
pub(crate) timestamp_rcvd: i64,
|
||||
pub(crate) autodelete_timer: i64,
|
||||
pub(crate) autodelete_timestamp: i64,
|
||||
pub(crate) text: Option<String>,
|
||||
pub(crate) rfc724_mid: String,
|
||||
pub(crate) in_reply_to: Option<String>,
|
||||
@@ -288,6 +320,8 @@ impl Message {
|
||||
" m.timestamp AS timestamp,",
|
||||
" m.timestamp_sent AS timestamp_sent,",
|
||||
" m.timestamp_rcvd AS timestamp_rcvd,",
|
||||
" m.autodelete_timer AS autodelete_timer,",
|
||||
" m.autodelete_timestamp AS autodelete_timestamp,",
|
||||
" m.type AS type,",
|
||||
" m.state AS state,",
|
||||
" m.msgrmsg AS msgrmsg,",
|
||||
@@ -315,6 +349,8 @@ impl Message {
|
||||
msg.timestamp_sort = row.get("timestamp")?;
|
||||
msg.timestamp_sent = row.get("timestamp_sent")?;
|
||||
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
|
||||
msg.autodelete_timer = row.get("autodelete_timer")?;
|
||||
msg.autodelete_timestamp = row.get("autodelete_timestamp")?;
|
||||
msg.viewtype = row.get("type")?;
|
||||
msg.state = row.get("state")?;
|
||||
msg.is_dc_message = row.get("msgrmsg")?;
|
||||
@@ -865,6 +901,17 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
ret += "\n";
|
||||
}
|
||||
|
||||
if msg.autodelete_timer != 0 {
|
||||
ret += &format!("Autodelete timer: {}\n", msg.autodelete_timer);
|
||||
}
|
||||
|
||||
if msg.autodelete_timestamp != 0 {
|
||||
ret += &format!(
|
||||
"Expires: {}\n",
|
||||
dc_timestamp_to_str(msg.autodelete_timestamp)
|
||||
);
|
||||
}
|
||||
|
||||
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
|
||||
// device-internal message, no further details needed
|
||||
return ret;
|
||||
@@ -1060,6 +1107,14 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
let msgs = msgs.unwrap_or_default();
|
||||
|
||||
for (id, curr_state, curr_blocked) in msgs.into_iter() {
|
||||
if let Err(err) = id.start_autodelete_timer(context) {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to start autodelete timer for message {}: {}", id, err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if curr_blocked == Blocked::Not {
|
||||
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
|
||||
update_msg_state(context, *id, MessageState::InSeen);
|
||||
|
||||
+15
-1
@@ -2,7 +2,7 @@ use chrono::TimeZone;
|
||||
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::chat::{self, get_autodelete_timer, Chat};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
@@ -453,6 +453,14 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
Loaded::MDN { .. } => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
|
||||
};
|
||||
|
||||
let autodelete_timer = get_autodelete_timer(self.context, self.msg.chat_id);
|
||||
if autodelete_timer > 0 {
|
||||
protected_headers.push(Header::new(
|
||||
"Autodelete-Timer".to_string(),
|
||||
autodelete_timer.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// we could also store the message-id in the protected headers
|
||||
// which would probably help to survive providers like
|
||||
// Outlook.com or hotmail which mangle the Message-ID.
|
||||
@@ -702,6 +710,12 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
"location-streaming-enabled".into(),
|
||||
));
|
||||
}
|
||||
SystemMessage::AutodeleteTimerChanged => {
|
||||
protected_headers.push(Header::new(
|
||||
"Chat-Content".to_string(),
|
||||
"autodelete-timer-changed".to_string(),
|
||||
));
|
||||
}
|
||||
SystemMessage::AutocryptSetupMessage => {
|
||||
unprotected_headers
|
||||
.push(Header::new("Autocrypt-Setup-Message".into(), "v1".into()));
|
||||
|
||||
@@ -71,6 +71,9 @@ pub enum SystemMessage {
|
||||
SecurejoinMessage = 7,
|
||||
LocationStreamingEnabled = 8,
|
||||
LocationOnly = 9,
|
||||
|
||||
/// Chat autodelete timer is changed.
|
||||
AutodeleteTimerChanged = 10,
|
||||
}
|
||||
|
||||
impl Default for SystemMessage {
|
||||
@@ -206,6 +209,8 @@ impl MimeMessage {
|
||||
} else if let Some(value) = self.get(HeaderDef::ChatContent) {
|
||||
if value == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
} else if value == "autodelete-timer-changed" {
|
||||
self.is_system_message = SystemMessage::AutodeleteTimerChanged;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -813,9 +818,15 @@ impl MimeMessage {
|
||||
.collect()
|
||||
});
|
||||
|
||||
let autodelete_timer = report_fields
|
||||
.get_header_value(HeaderDef::AutodeleteTimer)
|
||||
.and_then(|v| v.parse::<u32>().ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
return Ok(Some(Report {
|
||||
original_message_id,
|
||||
additional_message_ids,
|
||||
autodelete_timer,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -899,6 +910,11 @@ pub(crate) struct Report {
|
||||
original_message_id: String,
|
||||
/// Additional-Message-IDs
|
||||
additional_message_ids: Vec<String>,
|
||||
|
||||
/// MDNs should contain the same autodelete timer value as used in
|
||||
/// the message they reference. It is used to determine autodelete
|
||||
/// timer support.
|
||||
autodelete_timer: u32,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_message_ids(ids: &str) -> Result<Vec<String>> {
|
||||
|
||||
@@ -18,6 +18,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "newyear.aktivix.org", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "newyear.aktivix.org", port: 25, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// aol.md: aol.com
|
||||
@@ -28,6 +29,7 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/aol",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// autistici.org.md: autistici.org
|
||||
@@ -40,6 +42,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "mail.autistici.org", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.autistici.org", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// bluewin.ch.md: bluewin.ch
|
||||
@@ -52,6 +55,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imaps.bluewin.ch", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtpauths.bluewin.ch", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// comcast.md: xfinity.com, comcast.net
|
||||
@@ -73,6 +77,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.example.com", port: 1337, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.example.com", port: 1337, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// fastmail.md: fastmail.com
|
||||
@@ -83,6 +88,7 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/fastmail",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// freenet.de.md: freenet.de
|
||||
@@ -95,6 +101,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "mx.freenet.de", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "mx.freenet.de", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// gmail.md: gmail.com, googlemail.com
|
||||
@@ -107,6 +114,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.gmail.com", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.gmail.com", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// gmx.net.md: gmx.net, gmx.de, gmx.at, gmx.ch, gmx.org, gmx.eu, gmx.info, gmx.biz, gmx.com
|
||||
@@ -120,6 +128,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "mail.gmx.net", port: 465, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "mail.gmx.net", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// i.ua.md: i.ua
|
||||
@@ -135,6 +144,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.mail.me.com", port: 993, username_pattern: EMAILLOCALPART },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.mail.me.com", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// kolst.com.md: kolst.com
|
||||
@@ -159,6 +169,15 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "imap.nauta.cu", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.nauta.cu", port: 25, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: Some(vec![
|
||||
ConfigDefault { key: Config::DeleteServerAfter, value: "1" },
|
||||
ConfigDefault { key: Config::BccSelf, value: "0" },
|
||||
ConfigDefault { key: Config::SentboxWatch, value: "0" },
|
||||
ConfigDefault { key: Config::MvboxWatch, value: "0" },
|
||||
ConfigDefault { key: Config::MvboxMove, value: "0" },
|
||||
ConfigDefault { key: Config::E2eeEnabled, value: "0" },
|
||||
ConfigDefault { key: Config::MediaQuality, value: "1" },
|
||||
]),
|
||||
};
|
||||
|
||||
// outlook.com.md: hotmail.com, outlook.com, office365.com, outlook.com.tr, live.com
|
||||
@@ -171,6 +190,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap-mail.outlook.com", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp-mail.outlook.com", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// posteo.md: posteo.de
|
||||
@@ -183,6 +203,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "posteo.de", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "posteo.de", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// protonmail.md: protonmail.com, protonmail.ch
|
||||
@@ -193,6 +214,7 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/protonmail",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// riseup.net.md: riseup.net
|
||||
@@ -201,6 +223,9 @@ lazy_static::lazy_static! {
|
||||
// rogers.com.md: rogers.com
|
||||
// - skipping provider with status OK and no special things to do
|
||||
|
||||
// systemli.org.md: systemli.org
|
||||
// - skipping provider with status OK and no special things to do
|
||||
|
||||
// t-online.md: t-online.de, magenta.de
|
||||
static ref P_T_ONLINE: Provider = Provider {
|
||||
status: Status::PREPARATION,
|
||||
@@ -209,6 +234,7 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/t-online",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// testrun.md: testrun.org
|
||||
@@ -222,6 +248,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "testrun.org", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "testrun.org", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// tiscali.it.md: tiscali.it
|
||||
@@ -234,6 +261,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.tiscali.it", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.tiscali.it", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// ukr.net.md: ukr.net
|
||||
@@ -253,6 +281,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "imap.web.de", port: 143, username_pattern: EMAILLOCALPART },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.web.de", port: 587, username_pattern: EMAILLOCALPART },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// yahoo.md: yahoo.com, yahoo.de, yahoo.it, yahoo.fr, yahoo.es, yahoo.se, yahoo.co.uk, yahoo.co.nz, yahoo.com.au, yahoo.com.ar, yahoo.com.br, yahoo.com.mx, ymail.com, rocketmail.com, yahoodns.net
|
||||
@@ -265,6 +294,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.mail.yahoo.com", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.mail.yahoo.com", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// yandex.ru.md: yandex.ru, yandex.com
|
||||
@@ -275,6 +305,7 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/yandex-ru",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// ziggo.nl.md: ziggo.nl
|
||||
@@ -287,6 +318,7 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.ziggo.nl", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.ziggo.nl", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
pub static ref PROVIDER_DATA: HashMap<&'static str, &'static Provider> = [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
mod data;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::dc_tools::EmailAddress;
|
||||
use crate::provider::data::PROVIDER_DATA;
|
||||
|
||||
@@ -57,6 +58,12 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigDefault {
|
||||
pub key: Config,
|
||||
pub value: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Provider {
|
||||
pub status: Status,
|
||||
@@ -64,6 +71,7 @@ pub struct Provider {
|
||||
pub after_login_hint: &'static str,
|
||||
pub overview_page: &'static str,
|
||||
pub server: Vec<Server>,
|
||||
pub config_defaults: Option<Vec<ConfigDefault>>,
|
||||
}
|
||||
|
||||
impl Provider {
|
||||
|
||||
+19
-1
@@ -9,6 +9,9 @@ out_all = ""
|
||||
out_domains = ""
|
||||
domains_dict = {}
|
||||
|
||||
def camel(name):
|
||||
words = name.split("_")
|
||||
return "".join(w.capitalize() for i, w in enumerate(words))
|
||||
|
||||
def cleanstr(s):
|
||||
s = s.strip()
|
||||
@@ -31,6 +34,18 @@ def file2url(f):
|
||||
return "https://providers.delta.chat/" + f
|
||||
|
||||
|
||||
def process_config_defaults(data):
|
||||
if not "config_defaults" in data:
|
||||
return "None"
|
||||
defaults = "Some(vec![\n"
|
||||
config_defaults = data.get("config_defaults", "")
|
||||
for key in config_defaults:
|
||||
value = str(config_defaults[key])
|
||||
defaults += " ConfigDefault { key: Config::" + camel(key) + ", value: \"" + value + "\" },\n"
|
||||
defaults += " ])"
|
||||
return defaults
|
||||
|
||||
|
||||
def process_data(data, file):
|
||||
status = data.get("status", "")
|
||||
if status != "OK" and status != "PREPARATION" and status != "BROKEN":
|
||||
@@ -83,6 +98,8 @@ def process_data(data, file):
|
||||
server += (" Server { protocol: " + protocol + ", socket: " + socket + ", hostname: \""
|
||||
+ hostname + "\", port: " + str(port) + ", username_pattern: " + username_pattern + " },\n")
|
||||
|
||||
config_defaults = process_config_defaults(data)
|
||||
|
||||
provider = ""
|
||||
before_login_hint = cleanstr(data.get("before_login_hint", ""))
|
||||
after_login_hint = cleanstr(data.get("after_login_hint", ""))
|
||||
@@ -93,6 +110,7 @@ def process_data(data, file):
|
||||
provider += " after_login_hint: \"" + after_login_hint + "\",\n"
|
||||
provider += " overview_page: \"" + file2url(file) + "\",\n"
|
||||
provider += " server: vec![\n" + server + " ],\n"
|
||||
provider += " config_defaults: " + config_defaults + ",\n"
|
||||
provider += " };\n\n"
|
||||
else:
|
||||
raise TypeError("SMTP and IMAP must be specified together or left out both")
|
||||
@@ -103,7 +121,7 @@ def process_data(data, file):
|
||||
# finally, add the provider
|
||||
global out_all, out_domains
|
||||
out_all += " // " + file[file.rindex("/")+1:] + ": " + comment.strip(", ") + "\n"
|
||||
if status == "OK" and before_login_hint == "" and after_login_hint == "" and server == "":
|
||||
if status == "OK" and before_login_hint == "" and after_login_hint == "" and server == "" and config_defaults == "None":
|
||||
out_all += " // - skipping provider with status OK and no special things to do\n\n"
|
||||
else:
|
||||
out_all += provider
|
||||
|
||||
+38
@@ -398,6 +398,10 @@ fn open(
|
||||
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
|
||||
open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE);
|
||||
}
|
||||
|
||||
// this actually creates min_idle database handles just now.
|
||||
// therefore, with_init() must not try to modify the database as otherwise
|
||||
// we easily get busy-errors (eg. table-creation, journal_mode etc. should be done on only one handle)
|
||||
let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref())
|
||||
.with_flags(open_flags)
|
||||
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
|
||||
@@ -413,6 +417,13 @@ fn open(
|
||||
}
|
||||
|
||||
if !readonly {
|
||||
// journal_mode is persisted, it is sufficient to change it only for one handle.
|
||||
// (nb: execute() always returns errors for this PRAGMA call, just discard it.
|
||||
// but even if execute() would handle errors more gracefully, we should continue on errors -
|
||||
// systems might not be able to handle WAL, in which case the standard-journal is used.
|
||||
// that may be not optimal, but better than not working at all :)
|
||||
sql.execute("PRAGMA journal_mode=WAL;", NO_PARAMS).ok();
|
||||
|
||||
let mut exists_before_update = false;
|
||||
let mut dbversion_before_update = 0;
|
||||
/* Init tables to dbversion=0 */
|
||||
@@ -891,6 +902,33 @@ fn open(
|
||||
sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 63)?;
|
||||
}
|
||||
if dbversion < 64 {
|
||||
info!(context, "[migration] v64");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN autodelete_timer INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 64)?;
|
||||
}
|
||||
if dbversion < 65 {
|
||||
info!(context, "[migration] v65");
|
||||
// Timer value in seconds. For incoming messages this
|
||||
// timer starts when message is read, so we want to have
|
||||
// the value stored here until the timer starts.
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN autodelete_timer INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
// Timestamp indicating when the message should be
|
||||
// deleted. It is convenient to store it here because UI
|
||||
// needs this value to display how much time is left until
|
||||
// the message is deleted.
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN autodelete_timestamp INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 65)?;
|
||||
}
|
||||
|
||||
// (2) updates that require high-level objects
|
||||
// (the structure is complete now and all objects are usable)
|
||||
|
||||
@@ -179,6 +179,9 @@ pub enum StockMessage {
|
||||
|
||||
#[strum(props(fallback = "Unknown Sender for this chat. See 'info' for more details."))]
|
||||
UnknownSenderForChat = 72,
|
||||
|
||||
#[strum(props(fallback = "Autodelete timer changed to %1$s."))]
|
||||
MsgAutodeleteTimerChanged = 73,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Standards used in Delta Chat
|
||||
|
||||
Some of the standards Delta Chat is based on:
|
||||
|
||||
Tasks | Standards
|
||||
---------------------------------|---------------------------------------------
|
||||
Transport | IMAP v4 ([RFC 3501](https://tools.ietf.org/html/rfc3501)), SMTP ([RFC 5321](https://tools.ietf.org/html/rfc5321)) and Internet Message Format (IMF, [RFC 5322](https://tools.ietf.org/html/rfc5322))
|
||||
Embedded media | MIME Document Series ([RFC 2045](https://tools.ietf.org/html/rfc2045), [RFC 2046](https://tools.ietf.org/html/rfc2046)), Content-Disposition Header ([RFC 2183](https://tools.ietf.org/html/rfc2183)), Multipart/Related ([RFC 2387](https://tools.ietf.org/html/rfc2387))
|
||||
Identify server folders | IMAP LIST Extension ([RFC 6154](https://tools.ietf.org/html/rfc6154))
|
||||
Push | IMAP IDLE ([RFC 2177](https://tools.ietf.org/html/rfc2177))
|
||||
Authorization | OAuth2 ([RFC 6749](https://tools.ietf.org/html/rfc6749))
|
||||
End-to-end encryption | [Autocrypt Level 1](https://autocrypt.org/level1.html), OpenPGP ([RFC 4880](https://tools.ietf.org/html/rfc4880)) and Security Multiparts for MIME ([RFC 1847](https://tools.ietf.org/html/rfc1847))
|
||||
Configuration assistance | [Autoconfigure](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover](https://technet.microsoft.com/library/bb124251(v=exchg.150).aspx)
|
||||
Messenger functions | [Chat-over-Email](https://github.com/deltachat/deltachat-core-rust/blob/master/spec.md#chat-over-email-specification)
|
||||
Detect mailing list | List-Id ([RFC 2919](https://tools.ietf.org/html/rfc2919)) and Precedence ([RFC 3834](https://tools.ietf.org/html/rfc3834))
|
||||
Send and receive system messages | Multipart/Report Media Type ([RFC 6522](https://tools.ietf.org/html/rfc6522))
|
||||
Return receipts | Message Disposition Notification (MDN, [RFC 8098](https://tools.ietf.org/html/rfc8098), [RFC 3503](https://tools.ietf.org/html/rfc3503)) using the Chat-Disposition-Notification-To header
|
||||
Locations | KML ([Open Geospatial Consortium](http://www.opengeospatial.org/standards/kml/), [Google Dev](https://developers.google.com/kml/))
|
||||
|
||||
Reference in New Issue
Block a user