Compare commits

...
Author SHA1 Message Date
Alexander Krotov fb325be888 update_autodelete_timeout: ignore trashed messages
They are removed in prune_tombstones() which only happens during housekeeping.
2020-05-24 02:52:32 +03:00
Alexander Krotov f3137d87c5 Add DC_EVENT_MSG_DELETE_TIMEOUT_CHANGED
This event tells the client when the next message will be deleted. When
this event is received, the client should start a timer and update
current chat or chatlist when timeout expires.
2020-05-24 02:52:32 +03:00
Alexander Krotov 9d033e2ea1 Start autodelete timer when message is displayed 2020-05-23 21:09:33 +03:00
Alexander Krotov cdc15ce980 Do not store NULL values in msgs.autodelete_* columns 2020-05-23 21:09:33 +03:00
Alexander Krotov f017ffa0b3 Cleanup test_autodelete_timer()
Better comments and removed unnecessary .consume_events()
2020-05-23 21:09:33 +03:00
Alexander Krotov 28c79516da Test that autodelete timer is set for received messages 2020-05-23 21:09:33 +03:00
Alexander Krotov af1be65e8b Set autodelete timer for received messages 2020-05-23 21:09:33 +03:00
Alexander Krotov 93ffcf530d Add autodelete timer and timestamp to message info 2020-05-23 21:09:33 +03:00
Alexander Krotov 028187eeaf Generate IMAP deletion jobs for ephemeral messages 2020-05-23 21:09:33 +03:00
Alexander Krotov 032b387c95 Process timer changes before calc_timestamps()
This way system message about timer change gets lower timestamp than the
message itself, and it is clear that new timer is applied to the message
that just arrived.
2020-05-23 21:09:33 +03:00
Alexander Krotov 5523e7ba19 Delete autodelete-expired messages locally 2020-05-23 21:09:33 +03:00
Alexander Krotov b54c2a3196 Do not set autodelete timer on special chat IDs 2020-05-23 21:09:33 +03:00
Alexander Krotov 42d8502531 Parse Autodelete-Timer header in MDNs
It is not sent there yet.
2020-05-23 21:09:33 +03:00
Alexander Krotov 6002f54ce1 Set autodelete timer columns for outgoing messages 2020-05-23 21:09:33 +03:00
Alexander Krotov a830d499eb Add autodelete timer columns to message table 2020-05-23 21:09:32 +03:00
Alexander Krotov 49e821d029 Apply autodelete timer changes to the chat before adding messages
Timer is started only when the message is read, but we want to be completely sure that correct value is used.

This commit also add some comments.
2020-05-23 21:09:32 +03:00
Alexander Krotov 010cf7140f Do not send messages in autodelete timer tests
System messages are sent automatically now, so it is not needed.
2020-05-23 21:09:32 +03:00
Alexander Krotov e1773edf7f Send system message to chat when user changes autodelete timer 2020-05-23 21:09:32 +03:00
Alexander Krotov b4337685cf Add info message when autodelete timer is changed 2020-05-23 21:09:32 +03:00
Alexander Krotov 997c5ad68a Set timer to 0 if no Autodelete-Timer header is present 2020-05-23 21:09:32 +03:00
Alexander Krotov 67b486bfbd Transmit autodelete timer over the network in test_autodelete_timer 2020-05-23 21:09:32 +03:00
Alexander Krotov f5ee9c530d Add Event::ChatAutodeleteTimerModified 2020-05-23 21:09:32 +03:00
Alexander Krotov 0189644f08 Add python test for autodelete timer API 2020-05-23 21:09:32 +03:00
Alexander Krotov 0ca3e3100e Add {get,set}_autodelete_timer to python API 2020-05-23 21:09:32 +03:00
Alexander Krotov d4c4070b3f Add C interface for autodelete timer settings 2020-05-23 21:09:32 +03:00
Alexander Krotov 51133ce0d6 Add autodelete timer to the database 2020-05-23 21:09:32 +03:00
Hocuri 26b0c43cc4 Remove help for nonexisting open and close commands.
See #1496
2020-05-22 18:45:42 +02:00
B. Petersen 779a906d97 move standards.md here
the document standards.md exist since a long time
to get an overview but also a nice reference to get some links.

however, its placement in the android-repo is a bit unlogical,
probably comes from a time where there was only one implementation :)

as ios and desktop use the same standards and as
all acutal implementations are in the core,
we move it to the core repo.

android can have a link to the new place.
2020-05-20 15:48:13 +02:00
B. Petersen 7e78d6a92b fix link 2020-05-19 22:52:11 +03:00
B. Petersen 6483b8c138 change to WAL only on one handle.
journal_mode is persisted, it is sufficient to change it only for one handle.
with_init() is called for a bunch of handles directly on pool-creation,
so changing to WAL here easily results in busy-errors.
2020-05-18 18:19:08 +02:00
B. Petersen 9702647044 enable sqlite's WAL-mode unconditionally 2020-05-18 06:55:06 +02:00
B. Petersen 3b2192a046 make clippy happy 2020-05-18 06:55:06 +02:00
B. Petersen b864911e18 return current journal_mode in dc_get_info()
values are either 'delete' or 'wal';
having this info in debug reports is useful for debugging anyway,
if we switch to WAL or not.
2020-05-18 06:55:06 +02:00
bjoern 9e0506cb48 Merge pull request #1488 from deltachat/fix-get-sent-folder
improve finding out the sent-folder
2020-05-17 03:01:39 +02:00
B. Petersen a0000b9489 update provider-db 2020-05-16 23:26:53 +02:00
Hocuri df3fac4e5e Make clear that dc_set_chat_mute_duration() and dc_chat_is_muted() belong together. 2020-05-16 22:32:07 +02:00
bjoern 8a36f55439 Merge pull request #1491 from deltachat/fix-get-sent-folder1
Code style (#1488)
2020-05-16 15:25:04 +02:00
Hocuri ab253744f8 Code style 2020-05-16 15:07:01 +02:00
bjoern fff1eaba45 Merge pull request #1485 from deltachat/tweak-delete-docs
clarify delete_server_after option
2020-05-16 11:29:13 +02:00
B. Petersen eafb7b979d make clippy happy 2020-05-16 00:09:22 +02:00
B. Petersen 9e22bf39cc smoother searching for sentbox
search for the sentbox in two passes:
- first check the folder attributes
- only if that fails, check for some known names

this way, the sent-attribute always has precedence
over the name; this was not the case before.

moreover, this fixes an possibly wrong early exist
when the attribute list is completely empty.
2020-05-16 00:01:42 +02:00
B. Petersen fc6691ce5b add 'sentmail' to the list of known names for 'sent', acutally used by chello.at 2020-05-15 23:05:23 +02:00
holger krekel 598dc86ca5 make sure we don't garble output during test runs by more carefully
switching logging on/off globally
2020-05-15 21:19:57 +02:00
Alexander Krotov 3e2bfc35e3 Delete hidden expired messages
The condition remains from the time when expired messages were hidden
instead of being moved into trash chat. As a result, old hidden
messages, such as location messages, were not deleted.
2020-05-15 20:46:41 +03:00
B. Petersen 6658ad8618 clarify delete_server_after option 2020-05-15 17:12:19 +02:00
bjoern 759ccdbee2 Merge pull request #1441 from deltachat/config-from-provider-db
optionally get default-options from provider-db
2020-05-15 12:19:14 +02:00
bjoern 635060a02d Merge pull request #1481 from deltachat/adbenitez-patch-1
Update CHANGELOG.md
2020-05-14 11:16:15 +02:00
Asiel Díaz Benítez a128e7e7ab Update CHANGELOG.md 2020-05-14 03:22:45 -04:00
Jikstra 3cb69b8035 Merge pull request #1479 from deltachat/prep-1.33
Prep 1.33
2020-05-13 23:17:54 +02:00
B. Petersen 4b744337fe apply user_defaults after the first successful call to dc_configure() 2020-05-12 23:02:22 +02:00
B. Petersen 8d7d2f7a44 run provider/update.py 2020-05-12 23:02:22 +02:00
B. Petersen 24bf1dbffb fixup 2020-05-12 23:02:22 +02:00
B. Petersen c1890bb126 adapt provider/update.py so that it generates config_defaults 2020-05-12 23:02:22 +02:00
B. Petersen a4a570896a add config_defaults array to provider-db 2020-05-12 23:02:22 +02:00
26 changed files with 722 additions and 90 deletions
+1 -1
View File
@@ -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
+43 -4
View File
@@ -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.
+46
View File
@@ -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,
+1 -3
View File
@@ -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",
+12 -2
View File
@@ -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:
+16
View File
@@ -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.
+1
View File
@@ -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
+66 -8
View File
@@ -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)
+53
View File
@@ -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
View File
@@ -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
}
"#;
+10
View File
@@ -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(&param.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
View File
@@ -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
View File
@@ -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;
+15
View File
@@ -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.
+1
View File
@@ -41,6 +41,7 @@ pub enum HeaderDef {
SecureJoinFingerprint,
SecureJoinInvitenumber,
SecureJoinAuth,
AutodeleteTimer,
_TestHeader,
}
+14 -21
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()));
+16
View File
@@ -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>> {
+32
View File
@@ -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> = [
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+3
View File
@@ -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,
}
/*
+19
View File
@@ -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/))