mirror of
https://github.com/chatmail/core.git
synced 2026-06-28 02:26:35 +03:00
Compare commits
2 Commits
1.48.0
...
test_imex_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff34ed6054 | ||
|
|
8aaea09011 |
112
CHANGELOG.md
112
CHANGELOG.md
@@ -1,115 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## 1.48.0
|
||||
## Unreleased
|
||||
|
||||
- `fetch_existing` renamed to `fetch_existing_msgs` and disabled by default
|
||||
#2035 #2042
|
||||
|
||||
- skip fetch existing messages/contacts if config-option `bot` set #2017
|
||||
|
||||
- always log why a message is sorted to trash #2045
|
||||
|
||||
- display a quote if top posting is detected #2047
|
||||
|
||||
- add ephemeral task cancellation to `dc_stop_io()`;
|
||||
before, there was no way to quickly terminate pending ephemeral tasks #2051
|
||||
|
||||
- when saved-messages chat is deleted,
|
||||
a device-message about recreation is added #2050
|
||||
|
||||
- use `max_smtp_rcpt_to` from provider-db,
|
||||
sending messages to many recipients in configurable chunks #2056
|
||||
|
||||
- fix handling of empty autoconfigure files #2027
|
||||
|
||||
- fix adding saved messages to wrong chats on multi-device #2034 #2039
|
||||
|
||||
- fix hang on android4.4 and other systems
|
||||
by adding a workaround to executer-blocking-handling bug #2040
|
||||
|
||||
- fix secret key export/import roundtrip #2048
|
||||
|
||||
- fix mistakenly unarchived chats #2057
|
||||
|
||||
- fix outdated-reminder test that fails only 7 days a year,
|
||||
including halloween :) #2059
|
||||
|
||||
- improve python bindings #2021 #2036 #2038
|
||||
|
||||
- update provider-database #2037
|
||||
|
||||
|
||||
## 1.47.0
|
||||
|
||||
- breaking change: `dc_update_device_chats()` removed;
|
||||
this is now done automatically during configure
|
||||
unless the new config-option `bot` is set #1957
|
||||
|
||||
- breaking change: split `DC_EVENT_MSGS_NOTICED` off `DC_EVENT_MSGS_CHANGED`
|
||||
and remove `dc_marknoticed_all_chats()` #1942 #1981
|
||||
|
||||
- breaking change: remove unused starring options #1965
|
||||
|
||||
- breaking change: `DC_CHAT_TYPE_VERIFIED_GROUP` replaced by
|
||||
`dc_chat_is_protected()`; also single-chats may be protected now, this may
|
||||
happen over the wire even if the UI do not offer an option for that #1968
|
||||
|
||||
- breaking change: split quotes off message text,
|
||||
UIs should use at least `dc_msg_get_quoted_text()` to show quotes now #1975
|
||||
|
||||
- new api for quote handling: `dc_msg_set_quote()`, `dc_msg_get_quoted_text()`,
|
||||
`dc_msg_get_quoted_msg()` #1975 #1984 #1985 #1987 #1989 #2004
|
||||
|
||||
- require quorum to enable encryption #1946
|
||||
|
||||
- speed up and clean up account creation #1912 #1927 #1960 #1961
|
||||
|
||||
- configure now collects recent contacts and fetches last messages
|
||||
unless disabled by `fetch_existing` config-option #1913 #2003
|
||||
EDIT: `fetch_existing` renamed to `fetch_existing_msgs` in 1.48.0 #2042
|
||||
|
||||
- emit `DC_EVENT_CHAT_MODIFIED` on contact rename
|
||||
and set contact-id on `DC_EVENT_CONTACTS_CHANGED` #1935 #1936 #1937
|
||||
|
||||
- add `dc_set_chat_protection()`; the `protect` parameter in
|
||||
`dc_create_group_chat()` will be removed in an upcoming release;
|
||||
up to then, UIs using the "verified group" paradigm
|
||||
should not use `dc_set_chat_protection()` #1968 #2014 #2001 #2012 #2007
|
||||
|
||||
- remove unneeded `DC_STR_COUNT` #1991
|
||||
|
||||
- mark all failed messages as failed when receiving an NDN #1993
|
||||
|
||||
- check some easy cases for bad system clock and outdated app #1901
|
||||
|
||||
- fix import temporary directory usage #1929
|
||||
|
||||
- fix forcing encryption for reset peers #1998
|
||||
|
||||
- fix: do not allow to save drafts in non-writeable chats #1997
|
||||
|
||||
- fix: do not show HTML if there is no content and there is an attachment #1988
|
||||
|
||||
- fix recovering offline/lost connections, fixes background receive bug #1983
|
||||
|
||||
- fix ordering of accounts returned by `dc_accounts_get_all()` #1909
|
||||
|
||||
- fix whitespace for summaries #1938
|
||||
|
||||
- fix: improve sentbox name guessing #1941
|
||||
|
||||
- fix: avoid manual poll impl for accounts events #1944
|
||||
|
||||
- fix encoding newlines in param as a preparation for storing quotes #1945
|
||||
|
||||
- fix: internal and ffi error handling #1967 #1966 #1959 #1911 #1916 #1917 #1915
|
||||
|
||||
- fix ci #1928 #1931 #1932 #1933 #1934 #1943
|
||||
|
||||
- update provider-database #1940 #2005 #2006
|
||||
|
||||
- update dependencies #1919 #1908 #1950 #1963 #1996 #2010 #2013
|
||||
- breaking change: `dc_update_device_chats()` was removed. This is now done automatically during configure.
|
||||
|
||||
- Added a `bot` config. Currently, it only prevents filling the device chats automatically.
|
||||
|
||||
## 1.46.0
|
||||
|
||||
@@ -856,3 +751,4 @@
|
||||
For a full list of changes, please see our closed Pull Requests:
|
||||
|
||||
https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
|
||||
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1003,7 +1003,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.48.0"
|
||||
version = "1.46.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"anyhow",
|
||||
@@ -1079,7 +1079,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.48.0"
|
||||
version = "1.46.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.48.0"
|
||||
version = "1.46.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.48.0"
|
||||
version = "1.46.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -350,7 +350,7 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* The type `jitsi:` may be handled by external apps.
|
||||
* If no type is prefixed, the videochat is handled completely in a browser.
|
||||
* - `bot` = Set to "1" if this is a bot. E.g. prevents adding the "Device messages" and "Saved messages" chats.
|
||||
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
||||
* - `fetch_existing` = 1=fetch most recent existing messages on configure (default),
|
||||
* 0=do not fetch existing messages on configure.
|
||||
* In both cases, existing recipients are added to the contact database.
|
||||
*
|
||||
@@ -562,12 +562,9 @@ void dc_start_io (dc_context_t* context);
|
||||
int dc_is_io_running(const dc_context_t* context);
|
||||
|
||||
/**
|
||||
* Stop job, IMAP, SMTP and other tasks and return when they
|
||||
* are finished.
|
||||
* Stop job and IMAP/SMTP tasks and return when they are finished.
|
||||
* If IO is not running, nothing happens.
|
||||
* To check the current IO state, use dc_is_io_running().
|
||||
* Even if IO is not running, there may be pending tasks,
|
||||
* so this function should always be called before releasing
|
||||
* context to ensure clean termination of event loop.
|
||||
*
|
||||
* If the context was created by the dc_accounts_t account manager,
|
||||
* use dc_accounts_stop_io() instead of this function.
|
||||
@@ -2376,6 +2373,7 @@ void dc_accounts_start_io (dc_accounts_t* accounts);
|
||||
|
||||
/**
|
||||
* Stop job and IMAP/SMTP tasks for all accounts and return when they are finished.
|
||||
* If IO is not running, nothing happens.
|
||||
* This is similar to dc_stop_io(), which, however,
|
||||
* must not be called for accounts handled by the account manager.
|
||||
*
|
||||
@@ -4990,7 +4988,6 @@ void dc_event_unref(dc_event_t* event);
|
||||
#define DC_STR_PROTECTION_ENABLED 88
|
||||
#define DC_STR_PROTECTION_DISABLED 89
|
||||
#define DC_STR_REPLY_NOUN 90 /* eg. "Reply", used in summaries, a noun, not a verb (not: "to reply") */
|
||||
#define DC_STR_SELF_DELETED_MSG_BODY 91
|
||||
|
||||
/*
|
||||
* @}
|
||||
|
||||
@@ -1727,15 +1727,13 @@ pub unsafe extern "C" fn dc_imex(
|
||||
|
||||
let ctx = &*context;
|
||||
|
||||
if let Some(param1) = to_opt_string_lossy(param1) {
|
||||
spawn(async move {
|
||||
imex::imex(&ctx, what, ¶m1)
|
||||
.await
|
||||
.log_err(ctx, "IMEX failed")
|
||||
});
|
||||
} else {
|
||||
eprintln!("dc_imex called without a valid directory");
|
||||
}
|
||||
let param1 = to_opt_string_lossy(param1);
|
||||
|
||||
spawn(async move {
|
||||
imex::imex(&ctx, what, param1)
|
||||
.await
|
||||
.log_err(ctx, "IMEX failed")
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
@@ -443,20 +443,20 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"export-backup" => {
|
||||
let dir = dirs::home_dir().unwrap_or_default();
|
||||
imex(&context, ImexMode::ExportBackup, &dir).await?;
|
||||
imex(&context, ImexMode::ExportBackup, Some(&dir)).await?;
|
||||
println!("Exported to {}.", dir.to_string_lossy());
|
||||
}
|
||||
"import-backup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
|
||||
imex(&context, ImexMode::ImportBackup, arg1).await?;
|
||||
imex(&context, ImexMode::ImportBackup, Some(arg1)).await?;
|
||||
}
|
||||
"export-keys" => {
|
||||
let dir = dirs::home_dir().unwrap_or_default();
|
||||
imex(&context, ImexMode::ExportSelfKeys, &dir).await?;
|
||||
imex(&context, ImexMode::ExportSelfKeys, Some(&dir)).await?;
|
||||
println!("Exported to {}.", dir.to_string_lossy());
|
||||
}
|
||||
"import-keys" => {
|
||||
imex(&context, ImexMode::ImportSelfKeys, arg1).await?;
|
||||
imex(&context, ImexMode::ImportSelfKeys, Some(arg1)).await?;
|
||||
}
|
||||
"export-setup" => {
|
||||
let setup_code = create_setup_code(&context);
|
||||
|
||||
@@ -197,12 +197,14 @@ const CHAT_COMMANDS: [&str; 27] = [
|
||||
"unpin",
|
||||
"delchat",
|
||||
];
|
||||
const MESSAGE_COMMANDS: [&str; 6] = [
|
||||
const MESSAGE_COMMANDS: [&str; 8] = [
|
||||
"listmsgs",
|
||||
"msginfo",
|
||||
"listfresh",
|
||||
"forward",
|
||||
"markseen",
|
||||
"star",
|
||||
"unstar",
|
||||
"delmsg",
|
||||
];
|
||||
const CONTACT_COMMANDS: [&str; 6] = [
|
||||
|
||||
@@ -214,19 +214,6 @@ class Account(object):
|
||||
:param name: (optional) display name for this contact
|
||||
:returns: :class:`deltachat.contact.Contact` instance.
|
||||
"""
|
||||
(name, addr) = self.get_contact_addr_and_name(obj, name)
|
||||
name = as_dc_charpointer(name)
|
||||
addr = as_dc_charpointer(addr)
|
||||
contact_id = lib.dc_create_contact(self._dc_context, name, addr)
|
||||
return Contact(self, contact_id)
|
||||
|
||||
def get_contact(self, obj):
|
||||
if isinstance(obj, Contact):
|
||||
return obj
|
||||
(_, addr) = self.get_contact_addr_and_name(obj)
|
||||
return self.get_contact_by_addr(addr)
|
||||
|
||||
def get_contact_addr_and_name(self, obj, name=None):
|
||||
if isinstance(obj, Account):
|
||||
if not obj.is_configured():
|
||||
raise ValueError("can only add addresses from configured accounts")
|
||||
@@ -242,7 +229,13 @@ class Account(object):
|
||||
|
||||
if name is None and displayname:
|
||||
name = displayname
|
||||
return (name, addr)
|
||||
return self._create_contact(addr, name)
|
||||
|
||||
def _create_contact(self, addr, name):
|
||||
addr = as_dc_charpointer(addr)
|
||||
name = as_dc_charpointer(name)
|
||||
contact_id = lib.dc_create_contact(self._dc_context, name, addr)
|
||||
return Contact(self, contact_id)
|
||||
|
||||
def delete_contact(self, contact):
|
||||
""" delete a Contact.
|
||||
@@ -270,17 +263,6 @@ class Account(object):
|
||||
"""
|
||||
return Contact(self, contact_id)
|
||||
|
||||
def get_blocked_contacts(self):
|
||||
""" return a list of all blocked contacts.
|
||||
|
||||
:returns: list of :class:`deltachat.contact.Contact` objects.
|
||||
"""
|
||||
dc_array = ffi.gc(
|
||||
lib.dc_get_blocked_contacts(self._dc_context),
|
||||
lib.dc_array_unref
|
||||
)
|
||||
return list(iter_array(dc_array, lambda x: Contact(self, x)))
|
||||
|
||||
def get_contacts(self, query=None, with_self=False, only_verified=False):
|
||||
""" get a (filtered) list of contacts.
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ class Chat(object):
|
||||
:raises ValueError: if contact could not be removed
|
||||
:returns: None
|
||||
"""
|
||||
contact = self.account.get_contact(obj)
|
||||
contact = self.account.create_contact(obj)
|
||||
ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
|
||||
if ret != 1:
|
||||
raise ValueError("could not remove contact {!r} from chat".format(contact))
|
||||
|
||||
@@ -52,17 +52,9 @@ class Contact(object):
|
||||
return lib.dc_contact_is_blocked(self._dc_contact)
|
||||
|
||||
def set_blocked(self, block=True):
|
||||
""" [Deprecated, use block/unblock methods] Block or unblock a contact. """
|
||||
""" Block or unblock a contact. """
|
||||
return lib.dc_block_contact(self.account._dc_context, self.id, block)
|
||||
|
||||
def block(self):
|
||||
""" Block this contact. Message will not be seen/retrieved from this contact. """
|
||||
return lib.dc_block_contact(self.account._dc_context, self.id, True)
|
||||
|
||||
def unblock(self):
|
||||
""" Unblock this contact. Messages from this contact will be retrieved (again)."""
|
||||
return lib.dc_block_contact(self.account._dc_context, self.id, False)
|
||||
|
||||
def is_verified(self):
|
||||
""" Return True if the contact is verified. """
|
||||
return lib.dc_contact_is_verified(self._dc_contact)
|
||||
|
||||
@@ -103,14 +103,6 @@ class FFIEventTracker:
|
||||
if rex.search(ev.data2):
|
||||
return ev
|
||||
|
||||
def get_info_regex_groups(self, regex, check_error=True):
|
||||
rex = re.compile(regex)
|
||||
while 1:
|
||||
ev = self.get_matching("DC_EVENT_INFO", check_error=check_error)
|
||||
m = rex.match(ev.data2)
|
||||
if m is not None:
|
||||
return m.groups()
|
||||
|
||||
def ensure_event_not_queued(self, event_name_regex):
|
||||
__tracebackhide__ = True
|
||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||
|
||||
@@ -330,13 +330,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
|
||||
return accounts
|
||||
|
||||
def clone_online_account(self, account, pre_generated_key=True):
|
||||
""" Clones addr, mail_pw, mvbox_watch, mvbox_move, sentbox_watch and the
|
||||
direct_imap object of an online account. This simulates the user setting
|
||||
up a new device without importing a backup.
|
||||
|
||||
`pre_generated_key` only means that a key from python/tests/data/key is
|
||||
used in order to speed things up.
|
||||
"""
|
||||
self.live_count += 1
|
||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
||||
@@ -510,9 +503,6 @@ def lp():
|
||||
def step(self, msg):
|
||||
print("-" * 5, "step " + msg, "-" * 5)
|
||||
|
||||
def indent(self, msg):
|
||||
print(" " + msg)
|
||||
|
||||
return Printer()
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,14 @@ class ImexTracker:
|
||||
elif ffi_event.name == "DC_EVENT_IMEX_FILE_WRITTEN":
|
||||
self._imex_events.put(ffi_event.data2)
|
||||
|
||||
def wait_progress(self, target_progress, progress_timeout=60):
|
||||
while True:
|
||||
ev = self._imex_events.get(timeout=progress_timeout)
|
||||
if isinstance(ev, int) and ev >= target_progress:
|
||||
return ev
|
||||
if ev == 1000 or ev == 0:
|
||||
return None
|
||||
|
||||
def wait_finish(self, progress_timeout=60):
|
||||
""" Return list of written files, raise ValueError if ExportFailed. """
|
||||
files_written = []
|
||||
|
||||
@@ -6,6 +6,7 @@ import queue
|
||||
import time
|
||||
from deltachat import const, Account
|
||||
from deltachat.message import Message
|
||||
from deltachat.tracker import ImexTracker
|
||||
from deltachat.hookspec import account_hookimpl
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@@ -129,20 +130,6 @@ class TestOfflineContact:
|
||||
assert not contact1.is_blocked()
|
||||
assert not contact1.is_verified()
|
||||
|
||||
def test_get_blocked(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@example.org", name="some1")
|
||||
contact2 = ac1.create_contact("some2@example.org", name="some2")
|
||||
ac1.create_contact("some3@example.org", name="some3")
|
||||
assert ac1.get_blocked_contacts() == []
|
||||
contact1.block()
|
||||
assert ac1.get_blocked_contacts() == [contact1]
|
||||
contact2.block()
|
||||
blocked = ac1.get_blocked_contacts()
|
||||
assert len(blocked) == 2 and contact1 in blocked and contact2 in blocked
|
||||
contact2.unblock()
|
||||
assert ac1.get_blocked_contacts() == [contact1]
|
||||
|
||||
def test_create_self_contact(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(ac1.get_config("addr"))
|
||||
@@ -288,28 +275,6 @@ class TestOfflineChat:
|
||||
qr = chat.get_join_qr()
|
||||
assert ac2.check_qr(qr).is_ask_verifygroup
|
||||
|
||||
def test_removing_blocked_user_from_group(self, ac1, lp):
|
||||
"""
|
||||
Test that blocked contact is not unblocked when removed from a group.
|
||||
See https://github.com/deltachat/deltachat-core-rust/issues/2030
|
||||
"""
|
||||
lp.sec("Create a group chat with a contact")
|
||||
contact = ac1.create_contact("some1@example.org")
|
||||
group = ac1.create_group_chat("title", contacts=[contact])
|
||||
group.send_text("First group message")
|
||||
|
||||
lp.sec("ac1 blocks contact")
|
||||
contact.block()
|
||||
assert contact.is_blocked()
|
||||
|
||||
lp.sec("ac1 removes contact from their group")
|
||||
group.remove_contact(contact)
|
||||
assert contact.is_blocked()
|
||||
|
||||
lp.sec("ac1 adding blocked contact unblocks it")
|
||||
group.add_contact(contact)
|
||||
assert not contact.is_blocked()
|
||||
|
||||
def test_get_set_profile_image_simple(self, ac1, data):
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
p = data.get_path("d.png")
|
||||
@@ -668,7 +633,7 @@ class TestOnlineAccount:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_export_import_self_keys(self, acfactory, tmpdir, lp):
|
||||
def test_export_import_self_keys(self, acfactory, tmpdir):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
dir = tmpdir.mkdir("exportdir")
|
||||
@@ -676,17 +641,8 @@ class TestOnlineAccount:
|
||||
assert len(export_files) == 2
|
||||
for x in export_files:
|
||||
assert x.startswith(dir.strpath)
|
||||
key_id, = ac1._evtracker.get_info_regex_groups(r".*xporting.*KeyId\((.*)\).*")
|
||||
ac1._evtracker.consume_events()
|
||||
|
||||
lp.sec("exported keys (private and public)")
|
||||
for name in os.listdir(dir.strpath):
|
||||
lp.indent(dir.strpath + os.sep + name)
|
||||
lp.sec("importing into existing account")
|
||||
ac2.import_self_keys(dir.strpath)
|
||||
key_id2, = ac2._evtracker.get_info_regex_groups(
|
||||
r".*stored.*KeyId\((.*)\).*", check_error=False)
|
||||
assert key_id2 == key_id
|
||||
|
||||
def test_one_account_send_bcc_setting(self, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
@@ -969,30 +925,6 @@ class TestOnlineAccount:
|
||||
except queue.Empty:
|
||||
pass # mark_seen_messages() has generated events before it returns
|
||||
|
||||
def test_reply_privately(self, acfactory):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
group1 = ac1.create_group_chat("group")
|
||||
group1.add_contact(ac2)
|
||||
group1.send_text("hello")
|
||||
|
||||
msg2 = ac2._evtracker.wait_next_messages_changed()
|
||||
group2 = msg2.create_chat()
|
||||
assert group2.get_name() == group1.get_name()
|
||||
|
||||
msg_reply = Message.new_empty(ac2, "text")
|
||||
msg_reply.set_text("message reply")
|
||||
msg_reply.quote = msg2
|
||||
|
||||
private_chat1 = ac1.create_chat(ac2)
|
||||
private_chat2 = ac2.create_chat(ac1)
|
||||
private_chat2.send_msg(msg_reply)
|
||||
|
||||
msg_reply1 = ac1._evtracker.wait_next_incoming_message()
|
||||
assert msg_reply1.quoted_text == "hello"
|
||||
assert not msg_reply1.chat.is_group()
|
||||
assert msg_reply1.chat.id == private_chat1.id
|
||||
|
||||
def test_mdn_asymetric(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts(move=True)
|
||||
|
||||
@@ -1314,8 +1246,16 @@ class TestOnlineAccount:
|
||||
backupdir = tmpdir.mkdir("backup")
|
||||
|
||||
lp.sec("export all to {}".format(backupdir))
|
||||
path = ac1.export_all(backupdir.strpath)
|
||||
assert os.path.exists(path)
|
||||
with ac1.temp_plugin(ImexTracker()) as imex_tracker:
|
||||
path = ac1.export_all(backupdir.strpath)
|
||||
assert os.path.exists(path)
|
||||
|
||||
# check progress events for export
|
||||
imex_tracker.wait_progress(250)
|
||||
imex_tracker.wait_progress(500)
|
||||
imex_tracker.wait_progress(750)
|
||||
imex_tracker.wait_progress(1000)
|
||||
# return mex_tracker.wait_finish()
|
||||
t = time.time()
|
||||
|
||||
lp.sec("get fresh empty account")
|
||||
@@ -1326,7 +1266,15 @@ class TestOnlineAccount:
|
||||
assert path2 == path
|
||||
|
||||
lp.sec("import backup and check it's proper")
|
||||
ac2.import_all(path)
|
||||
with ac2.temp_plugin(ImexTracker()) as imex_tracker:
|
||||
ac2.import_all(path)
|
||||
|
||||
# check progress events for import
|
||||
imex_tracker.wait_progress(250)
|
||||
imex_tracker.wait_progress(500)
|
||||
imex_tracker.wait_progress(750)
|
||||
imex_tracker.wait_progress(1000)
|
||||
|
||||
contacts = ac2.get_contacts(query="some1")
|
||||
assert len(contacts) == 1
|
||||
contact2 = contacts[0]
|
||||
@@ -1576,7 +1524,7 @@ class TestOnlineAccount:
|
||||
|
||||
lp.sec("ac1 blocks ac2")
|
||||
contact = ac1.create_contact(ac2)
|
||||
contact.block()
|
||||
contact.set_blocked()
|
||||
assert contact.is_blocked()
|
||||
ev = ac1._evtracker.get_matching("DC_EVENT_CONTACTS_CHANGED")
|
||||
assert ev.data1 == contact.id
|
||||
@@ -2030,7 +1978,6 @@ class TestOnlineAccount:
|
||||
assert ac1.direct_imap.idle_wait_for_seen()
|
||||
|
||||
ac1_clone = acfactory.clone_online_account(ac1)
|
||||
ac1_clone.set_config("fetch_existing_msgs", "1")
|
||||
ac1_clone._configtracker.wait_finish()
|
||||
ac1_clone.start_io()
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ impl Accounts {
|
||||
let id = self.add_account().await?;
|
||||
let ctx = self.get_account(id).await.expect("just added");
|
||||
|
||||
match crate::imex::imex(&ctx, crate::imex::ImexMode::ImportBackup, &file).await {
|
||||
match crate::imex::imex(&ctx, crate::imex::ImexMode::ImportBackup, Some(file)).await {
|
||||
Ok(_) => Ok(id),
|
||||
Err(err) => {
|
||||
// remove temp account
|
||||
|
||||
10
src/blob.rs
10
src/blob.rs
@@ -63,12 +63,6 @@ impl<'a> BlobObject<'a> {
|
||||
blobname: name.clone(),
|
||||
cause: err.into(),
|
||||
})?;
|
||||
|
||||
// workaround a bug in async-std
|
||||
// (the executor does not handle blocking operation in Drop correctly,
|
||||
// see https://github.com/async-rs/async-std/issues/900 )
|
||||
let _ = file.flush().await;
|
||||
|
||||
let blob = BlobObject {
|
||||
blobdir,
|
||||
name: format!("$BLOBDIR/{}", name),
|
||||
@@ -157,10 +151,6 @@ impl<'a> BlobObject<'a> {
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
|
||||
// workaround, see create() for details
|
||||
let _ = dst_file.flush().await;
|
||||
|
||||
let blob = BlobObject {
|
||||
blobdir: context.get_blobdir(),
|
||||
name: format!("$BLOBDIR/{}", name),
|
||||
|
||||
13
src/chat.rs
13
src/chat.rs
@@ -337,7 +337,7 @@ impl ChatId {
|
||||
);
|
||||
/* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */
|
||||
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
let _chat = Chat::load_from_db(context, self).await?;
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
@@ -373,17 +373,6 @@ impl ChatId {
|
||||
let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10);
|
||||
job::add(context, j).await;
|
||||
|
||||
if chat.is_self_talk() {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_str(StockMessage::SelfDeletedMsgBody)
|
||||
.await
|
||||
.into(),
|
||||
);
|
||||
add_device_msg(&context, None, Some(&mut msg)).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -69,12 +69,8 @@ pub enum Config {
|
||||
#[strum(props(default = "0"))] // also change MediaQuality.default() on changes
|
||||
MediaQuality,
|
||||
|
||||
/// If set to "1", on the first time `start_io()` is called after configuring,
|
||||
/// the newest existing messages are fetched.
|
||||
/// Existing recipients are added to the contact database regardless of this setting.
|
||||
#[strum(props(default = "0"))]
|
||||
// disabled for now, we'll set this back to "1" at some point
|
||||
FetchExistingMsgs,
|
||||
#[strum(props(default = "1"))]
|
||||
FetchExisting,
|
||||
|
||||
#[strum(props(default = "0"))]
|
||||
KeyGenType,
|
||||
|
||||
@@ -217,32 +217,28 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
|
||||
progress!(ctx, 500);
|
||||
|
||||
let mut servers = param_autoconfig.unwrap_or_default();
|
||||
if !servers
|
||||
.iter()
|
||||
.any(|server| server.protocol == Protocol::IMAP)
|
||||
{
|
||||
servers.push(ServerParams {
|
||||
protocol: Protocol::IMAP,
|
||||
hostname: param.imap.server.clone(),
|
||||
port: param.imap.port,
|
||||
socket: param.imap.security,
|
||||
username: param.imap.user.clone(),
|
||||
})
|
||||
}
|
||||
if !servers
|
||||
.iter()
|
||||
.any(|server| server.protocol == Protocol::SMTP)
|
||||
{
|
||||
servers.push(ServerParams {
|
||||
protocol: Protocol::SMTP,
|
||||
hostname: param.smtp.server.clone(),
|
||||
port: param.smtp.port,
|
||||
socket: param.smtp.security,
|
||||
username: param.smtp.user.clone(),
|
||||
})
|
||||
}
|
||||
let servers = expand_param_vector(servers, ¶m.addr, ¶m_domain);
|
||||
let servers = expand_param_vector(
|
||||
param_autoconfig.unwrap_or_else(|| {
|
||||
vec![
|
||||
ServerParams {
|
||||
protocol: Protocol::IMAP,
|
||||
hostname: param.imap.server.clone(),
|
||||
port: param.imap.port,
|
||||
socket: param.imap.security,
|
||||
username: param.imap.user.clone(),
|
||||
},
|
||||
ServerParams {
|
||||
protocol: Protocol::SMTP,
|
||||
hostname: param.smtp.server.clone(),
|
||||
port: param.smtp.port,
|
||||
socket: param.smtp.security,
|
||||
username: param.smtp.user.clone(),
|
||||
},
|
||||
]
|
||||
}),
|
||||
¶m.addr,
|
||||
¶m_domain,
|
||||
);
|
||||
|
||||
progress!(ctx, 550);
|
||||
|
||||
@@ -564,9 +560,7 @@ async fn nicer_configuration_error(context: &Context, errors: Vec<ConfigurationE
|
||||
let first_err = if let Some(f) = errors.first() {
|
||||
f
|
||||
} else {
|
||||
// This means configuration failed but no errors have been captured. This should never
|
||||
// happen, but if it does, the user will see classic "Error: no error".
|
||||
return "no error".to_string();
|
||||
return "".to_string();
|
||||
};
|
||||
|
||||
if errors
|
||||
|
||||
@@ -205,11 +205,6 @@ pub const WORSE_IMAGE_SIZE: u32 = 640;
|
||||
// this value can be increased if the folder configuration is changed and must be redone on next program start
|
||||
pub const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3;
|
||||
|
||||
// if more recipients are needed in SMTP's `RCPT TO:` header, recipient-list is splitted to chunks.
|
||||
// this does not affect MIME'e `To:` header.
|
||||
// can be overwritten by the setting `max_smtp_rcpt_to` in provider-db.
|
||||
pub const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Display,
|
||||
|
||||
@@ -163,6 +163,10 @@ impl Context {
|
||||
/// Stops the IO scheduler.
|
||||
pub async fn stop_io(&self) {
|
||||
info!(self, "stopping IO");
|
||||
if !self.is_io_running().await {
|
||||
info!(self, "IO is not running");
|
||||
return;
|
||||
}
|
||||
|
||||
self.inner.stop_io().await;
|
||||
}
|
||||
@@ -479,19 +483,14 @@ impl InnerContext {
|
||||
}
|
||||
|
||||
async fn stop_io(&self) {
|
||||
if self.is_io_running().await {
|
||||
let token = {
|
||||
let lock = &*self.scheduler.read().await;
|
||||
lock.pre_stop().await
|
||||
};
|
||||
{
|
||||
let lock = &mut *self.scheduler.write().await;
|
||||
lock.stop(token).await;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ephemeral_task) = self.ephemeral_task.write().await.take() {
|
||||
ephemeral_task.cancel().await;
|
||||
assert!(self.is_io_running().await, "context is already stopped");
|
||||
let token = {
|
||||
let lock = &*self.scheduler.read().await;
|
||||
lock.pre_stop().await
|
||||
};
|
||||
{
|
||||
let lock = &mut *self.scheduler.write().await;
|
||||
lock.stop(token).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +387,6 @@ async fn add_parts(
|
||||
// this message is a classic email not a chat-message nor a reply to one
|
||||
match show_emails {
|
||||
ShowEmails::Off => {
|
||||
info!(context, "Classical email not shown (TRASH)");
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
allow_creation = false;
|
||||
}
|
||||
@@ -446,7 +445,10 @@ async fn add_parts(
|
||||
// it might also be blocked and displayed in the deaddrop as a result
|
||||
if chat_id.is_unset() && mime_parser.failure_report.is_some() {
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message belongs to an NDN (TRASH)",);
|
||||
info!(
|
||||
context,
|
||||
"Message belongs to an NDN and is not shown in a chat.",
|
||||
);
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
@@ -487,7 +489,7 @@ async fn add_parts(
|
||||
// check if the message belongs to a mailing list
|
||||
if mime_parser.is_mailinglist_message() {
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message belongs to a mailing list (TRASH)");
|
||||
info!(context, "Message belongs to a mailing list and is ignored.",);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,7 +533,6 @@ async fn add_parts(
|
||||
if chat_id.is_unset() {
|
||||
// maybe from_id is null or sth. else is suspicious, move message to trash
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
info!(context, "No chat id for incoming msg (TRASH)")
|
||||
}
|
||||
|
||||
// if the chat_id is blocked,
|
||||
@@ -641,14 +642,13 @@ async fn add_parts(
|
||||
}
|
||||
if chat_id.is_unset() {
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
info!(context, "No chat id for outgoing message (TRASH)")
|
||||
}
|
||||
}
|
||||
|
||||
if fetching_existing_messages && mime_parser.decrypting_failed {
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
// We are only gathering old messages on first start. We do not want to add loads of non-decryptable messages to the chats.
|
||||
info!(context, "Existing non-decipherable message. (TRASH)");
|
||||
info!(context, "Dropping existing non-decipherable message.");
|
||||
}
|
||||
|
||||
// Extract ephemeral timer from the message.
|
||||
@@ -772,6 +772,9 @@ async fn add_parts(
|
||||
|
||||
*sent_timestamp = std::cmp::min(*sent_timestamp, rcvd_timestamp);
|
||||
|
||||
// unarchive chat
|
||||
chat_id.unarchive(context).await?;
|
||||
|
||||
// if the mime-headers should be saved, find out its size
|
||||
// (the mime-header ends with an empty line)
|
||||
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await;
|
||||
@@ -893,10 +896,6 @@ async fn add_parts(
|
||||
*insert_msg_id = *id;
|
||||
}
|
||||
|
||||
if !is_hidden {
|
||||
chat_id.unarchive(context).await?;
|
||||
}
|
||||
|
||||
*hidden = is_hidden;
|
||||
created_db_entries.extend(ids.iter().map(|id| (chat_id, *id)));
|
||||
mime_parser.parts = new_parts;
|
||||
@@ -1080,24 +1079,37 @@ async fn create_or_lookup_group(
|
||||
set_better_msg(mime_parser, &better_msg);
|
||||
}
|
||||
|
||||
let grpid = try_getting_grpid(mime_parser);
|
||||
|
||||
if grpid.is_empty() {
|
||||
return create_or_lookup_adhoc_group(
|
||||
context,
|
||||
mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
info!(context, "could not create adhoc-group: {:?}", err);
|
||||
err
|
||||
});
|
||||
let mut grpid = "".to_string();
|
||||
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupId) {
|
||||
grpid = optional_field.clone();
|
||||
}
|
||||
|
||||
if grpid.is_empty() {
|
||||
if let Some(extracted_grpid) = mime_parser
|
||||
.get(HeaderDef::MessageId)
|
||||
.and_then(|value| dc_extract_grpid_from_rfc724_mid(&value))
|
||||
{
|
||||
grpid = extracted_grpid.to_string();
|
||||
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
|
||||
grpid = extracted_grpid.to_string();
|
||||
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References) {
|
||||
grpid = extracted_grpid.to_string();
|
||||
} else {
|
||||
return create_or_lookup_adhoc_group(
|
||||
context,
|
||||
mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
info!(context, "could not create adhoc-group: {:?}", err);
|
||||
err
|
||||
});
|
||||
}
|
||||
}
|
||||
// now we have a grpid that is non-empty
|
||||
// but we might not know about this group
|
||||
|
||||
@@ -1267,7 +1279,6 @@ async fn create_or_lookup_group(
|
||||
} else {
|
||||
// The message was decrypted successfully, but contains a late "quit" or otherwise
|
||||
// unwanted message.
|
||||
info!(context, "message belongs to unwanted group (TRASH)");
|
||||
return Ok((ChatId::new(DC_CHAT_ID_TRASH), chat_id_blocked));
|
||||
}
|
||||
}
|
||||
@@ -1367,27 +1378,6 @@ async fn create_or_lookup_group(
|
||||
Ok((chat_id, chat_id_blocked))
|
||||
}
|
||||
|
||||
fn try_getting_grpid(mime_parser: &MimeMessage) -> String {
|
||||
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupId) {
|
||||
return optional_field.clone();
|
||||
}
|
||||
|
||||
if let Some(extracted_grpid) = mime_parser
|
||||
.get(HeaderDef::MessageId)
|
||||
.and_then(|value| dc_extract_grpid_from_rfc724_mid(&value))
|
||||
{
|
||||
return extracted_grpid.to_string();
|
||||
}
|
||||
if !mime_parser.has_chat_version() {
|
||||
if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
|
||||
return extracted_grpid.to_string();
|
||||
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References) {
|
||||
return extracted_grpid.to_string();
|
||||
}
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
/// try extract a grpid from a message-id list header value
|
||||
fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<&str> {
|
||||
let header = mime_parser.get(headerdef)?;
|
||||
@@ -2228,7 +2218,7 @@ mod tests {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
// create one-to-one with bob, archive one-to-one
|
||||
let bob_id = Contact::create(&t.ctx, "bob", "bob@example.com")
|
||||
let bob_id = Contact::create(&t.ctx, "bob", "bob@exampel.org")
|
||||
.await
|
||||
.unwrap();
|
||||
let one2one_id = chat::create_by_contact_id(&t.ctx, bob_id).await.unwrap();
|
||||
|
||||
@@ -1174,31 +1174,22 @@ mod tests {
|
||||
assert_eq!(msgs.len(), 1);
|
||||
|
||||
// do not repeat the warning every day ...
|
||||
// (we test that for the 2 subsequent days, this may be the next month, so the result should be 1 or 2 device message)
|
||||
maybe_warn_on_outdated(
|
||||
&t.ctx,
|
||||
timestamp_now + (365 + 1) * 24 * 60 * 60,
|
||||
get_provider_update_timestamp(),
|
||||
)
|
||||
.await;
|
||||
maybe_warn_on_outdated(
|
||||
&t.ctx,
|
||||
timestamp_now + (365 + 2) * 24 * 60 * 60,
|
||||
get_provider_update_timestamp(),
|
||||
)
|
||||
.await;
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
let device_chat_id = chats.get_chat_id(0);
|
||||
let msgs = chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None).await;
|
||||
let test_len = msgs.len();
|
||||
assert!(test_len == 1 || test_len == 2);
|
||||
assert_eq!(msgs.len(), 1);
|
||||
|
||||
// ... but every month
|
||||
// (forward generous 33 days to avoid being in the same month as in the previous check)
|
||||
maybe_warn_on_outdated(
|
||||
&t.ctx,
|
||||
timestamp_now + (365 + 33) * 24 * 60 * 60,
|
||||
timestamp_now + (365 + 31) * 24 * 60 * 60,
|
||||
get_provider_update_timestamp(),
|
||||
)
|
||||
.await;
|
||||
@@ -1206,6 +1197,6 @@ mod tests {
|
||||
assert_eq!(chats.len(), 1);
|
||||
let device_chat_id = chats.get_chat_id(0);
|
||||
let msgs = chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None).await;
|
||||
assert_eq!(msgs.len(), test_len + 1);
|
||||
assert_eq!(msgs.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
112
src/imex.rs
112
src/imex.rs
@@ -31,7 +31,6 @@ use crate::param::*;
|
||||
use crate::pgp;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::stock::StockMessage;
|
||||
use ::pgp::types::KeyTrait;
|
||||
use async_tar::Archive;
|
||||
|
||||
// Name of the database file in the backup.
|
||||
@@ -79,7 +78,11 @@ pub enum ImexMode {
|
||||
///
|
||||
/// Only one import-/export-progress can run at the same time.
|
||||
/// To cancel an import-/export-progress, drop the future returned by this function.
|
||||
pub async fn imex(context: &Context, what: ImexMode, param1: impl AsRef<Path>) -> Result<()> {
|
||||
pub async fn imex(
|
||||
context: &Context,
|
||||
what: ImexMode,
|
||||
param1: Option<impl AsRef<Path>>,
|
||||
) -> Result<()> {
|
||||
let cancel = context.alloc_ongoing().await?;
|
||||
|
||||
let res = async {
|
||||
@@ -410,8 +413,6 @@ async fn set_self_key(
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!(context, "stored self key: {:?}", keypair.secret.key_id());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -438,11 +439,19 @@ pub fn normalize_setup_code(s: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
async fn imex_inner(context: &Context, what: ImexMode, path: impl AsRef<Path>) -> Result<()> {
|
||||
info!(context, "Import/export dir: {}", path.as_ref().display());
|
||||
ensure!(context.sql.is_open().await, "Database not opened.");
|
||||
async fn imex_inner(
|
||||
context: &Context,
|
||||
what: ImexMode,
|
||||
param: Option<impl AsRef<Path>>,
|
||||
) -> Result<()> {
|
||||
ensure!(param.is_some(), "No Import/export dir/file given.");
|
||||
|
||||
info!(context, "Import/export process started.");
|
||||
context.emit_event(EventType::ImexProgress(10));
|
||||
|
||||
ensure!(context.sql.is_open().await, "Database not opened.");
|
||||
|
||||
let path = param.ok_or_else(|| format_err!("Imex: Param was None"))?;
|
||||
if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys {
|
||||
// before we export anything, make sure the private key exists
|
||||
if e2ee::ensure_secret_key_exists(context).await.is_err() {
|
||||
@@ -494,10 +503,20 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
|
||||
);
|
||||
|
||||
let backup_file = File::open(backup_to_import).await?;
|
||||
let file_size = backup_file.metadata().await?.len();
|
||||
let archive = Archive::new(backup_file);
|
||||
|
||||
let mut entries = archive.entries()?;
|
||||
while let Some(file) = entries.next().await {
|
||||
let f = &mut file?;
|
||||
|
||||
let current_pos = f.raw_file_position();
|
||||
let progress = 1000 * current_pos / file_size;
|
||||
if progress > 10 && progress < 1000 {
|
||||
// We already emitted ImexProgress(10) above
|
||||
context.emit_event(EventType::ImexProgress(progress as usize));
|
||||
}
|
||||
|
||||
if f.path()?.file_name() == Some(OsStr::new(DBFILE_BACKUP_NAME)) {
|
||||
// async_tar can't unpack to a specified file name, so we just unpack to the blobdir and then move the unpacked file.
|
||||
f.unpack_in(context.get_blobdir()).await?;
|
||||
@@ -506,7 +525,6 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
|
||||
context.get_dbfile(),
|
||||
)
|
||||
.await?;
|
||||
context.emit_event(EventType::ImexProgress(400)); // Just guess the progress, we at least have the dbfile by now
|
||||
} else {
|
||||
// async_tar will unpack to blobdir/BLOBS_BACKUP_NAME, so we move the file afterwards.
|
||||
f.unpack_in(context.get_blobdir()).await?;
|
||||
@@ -706,11 +724,32 @@ async fn export_backup_inner(context: &Context, temp_path: &PathBuf) -> Result<(
|
||||
.append_path_with_name(context.get_dbfile(), DBFILE_BACKUP_NAME)
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::ImexProgress(500));
|
||||
let read_dir: Vec<_> = fs::read_dir(context.get_blobdir()).await?.collect().await;
|
||||
let count = read_dir.len();
|
||||
let mut written_files = 0;
|
||||
|
||||
builder
|
||||
.append_dir_all(BLOBS_BACKUP_NAME, context.get_blobdir())
|
||||
.await?;
|
||||
for entry in read_dir.into_iter() {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name();
|
||||
if !entry.file_type().await?.is_file() {
|
||||
warn!(
|
||||
context,
|
||||
"Export: Found dir entry {} that is not a file, ignoring",
|
||||
name.to_string_lossy()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let mut file = File::open(entry.path()).await?;
|
||||
let path_in_archive = PathBuf::from(BLOBS_BACKUP_NAME).join(name);
|
||||
builder.append_file(path_in_archive, &mut file).await?;
|
||||
|
||||
written_files += 1;
|
||||
let progress = 1000 * written_files / count;
|
||||
if progress > 10 && progress < 1000 {
|
||||
// We already emitted ImexProgress(10) above
|
||||
emit_event!(context, EventType::ImexProgress(progress));
|
||||
}
|
||||
}
|
||||
|
||||
builder.finish().await?;
|
||||
Ok(())
|
||||
@@ -866,12 +905,6 @@ async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
||||
continue;
|
||||
}
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
"considering key file: {}",
|
||||
path_plus_name.display()
|
||||
);
|
||||
|
||||
match dc_read_file(context, &path_plus_name).await {
|
||||
Ok(buf) => {
|
||||
let armored = std::string::String::from_utf8_lossy(&buf);
|
||||
@@ -961,7 +994,7 @@ where
|
||||
let any_key = key as &dyn Any;
|
||||
let kind = if any_key.downcast_ref::<SignedPublicKey>().is_some() {
|
||||
"public"
|
||||
} else if any_key.downcast_ref::<SignedSecretKey>().is_some() {
|
||||
} else if any_key.downcast_ref::<SignedPublicKey>().is_some() {
|
||||
"private"
|
||||
} else {
|
||||
"unknown"
|
||||
@@ -969,12 +1002,7 @@ where
|
||||
let id = id.map_or("default".into(), |i| i.to_string());
|
||||
dir.as_ref().join(format!("{}-key-{}.asc", kind, &id))
|
||||
};
|
||||
info!(
|
||||
context,
|
||||
"Exporting key {:?} to {}",
|
||||
key.key_id(),
|
||||
file_name.display()
|
||||
);
|
||||
info!(context, "Exporting key {}", file_name.display());
|
||||
dc_delete_file(context, &file_name).await;
|
||||
|
||||
let content = key.to_asc(None).into_bytes();
|
||||
@@ -1042,7 +1070,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_export_public_key_to_asc_file() {
|
||||
async fn test_export_key_to_asc_file() {
|
||||
let context = TestContext::new().await;
|
||||
let key = alice_keypair().public;
|
||||
let blobdir = "$BLOBDIR";
|
||||
@@ -1056,35 +1084,19 @@ mod tests {
|
||||
assert_eq!(bytes, key.to_asc(None).into_bytes());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_export_private_key_to_asc_file() {
|
||||
let context = TestContext::new().await;
|
||||
let key = alice_keypair().secret;
|
||||
let blobdir = "$BLOBDIR";
|
||||
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key)
|
||||
.await
|
||||
.is_ok());
|
||||
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||
let filename = format!("{}/private-key-default.asc", blobdir);
|
||||
let bytes = async_std::fs::read(&filename).await.unwrap();
|
||||
|
||||
assert_eq!(bytes, key.to_asc(None).into_bytes());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_export_and_import_key() {
|
||||
let context = TestContext::new().await;
|
||||
context.configure_alice().await;
|
||||
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||
if let Err(err) = imex(&context.ctx, ImexMode::ExportSelfKeys, blobdir).await {
|
||||
panic!("got error on export: {:?}", err);
|
||||
}
|
||||
let blobdir = "$BLOBDIR";
|
||||
assert!(imex(&context.ctx, ImexMode::ExportSelfKeys, Some(blobdir))
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
let context2 = TestContext::new().await;
|
||||
context2.configure_alice().await;
|
||||
if let Err(err) = imex(&context2.ctx, ImexMode::ImportSelfKeys, blobdir).await {
|
||||
panic!("got error on import: {:?}", err);
|
||||
}
|
||||
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||
assert!(imex(&context.ctx, ImexMode::ImportSelfKeys, Some(blobdir))
|
||||
.await
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -627,9 +627,6 @@ impl Job {
|
||||
/// Then, Fetch the last messages DC_FETCH_EXISTING_MSGS_COUNT emails from the server
|
||||
/// and show them in the chat list.
|
||||
async fn fetch_existing_msgs(&mut self, context: &Context, imap: &mut Imap) -> Status {
|
||||
if context.get_config_bool(Config::Bot).await {
|
||||
return Status::Finished(Ok(())); // Bots don't want those messages
|
||||
}
|
||||
if let Err(err) = imap.connect_configured(context).await {
|
||||
warn!(context, "could not connect: {:?}", err);
|
||||
return Status::RetryLater;
|
||||
@@ -639,7 +636,7 @@ impl Job {
|
||||
add_all_recipients_as_contacts(context, imap, Config::ConfiguredMvboxFolder).await;
|
||||
add_all_recipients_as_contacts(context, imap, Config::ConfiguredInboxFolder).await;
|
||||
|
||||
if context.get_config_bool(Config::FetchExistingMsgs).await {
|
||||
if context.get_config_bool(Config::FetchExisting).await {
|
||||
for config in &[
|
||||
Config::ConfiguredMvboxFolder,
|
||||
Config::ConfiguredInboxFolder,
|
||||
|
||||
@@ -1638,16 +1638,14 @@ pub(crate) async fn handle_ndn(
|
||||
context: &Context,
|
||||
failed: &FailureReport,
|
||||
error: Option<impl AsRef<str>>,
|
||||
) -> anyhow::Result<()> {
|
||||
) {
|
||||
if failed.rfc724_mid.is_empty() {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
// The NDN might be for a message-id that had attachments and was sent from a non-Delta Chat client.
|
||||
// In this case we need to mark multiple "msgids" as failed that all refer to the same message-id.
|
||||
let msgs: Vec<_> = context
|
||||
let res = context
|
||||
.sql
|
||||
.query_map(
|
||||
.query_row(
|
||||
concat!(
|
||||
"SELECT",
|
||||
" m.id AS msg_id,",
|
||||
@@ -1664,42 +1662,37 @@ pub(crate) async fn handle_ndn(
|
||||
row.get::<_, Chattype>("type")?,
|
||||
))
|
||||
},
|
||||
|rows| Ok(rows.collect::<Vec<_>>()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for (i, msg) in msgs.into_iter().enumerate() {
|
||||
let (msg_id, chat_id, chat_type) = msg?;
|
||||
set_msg_failed(context, msg_id, error.as_ref()).await;
|
||||
if i == 0 {
|
||||
// Add only one info msg for all failed messages
|
||||
ndn_maybe_add_info_msg(context, failed, chat_id, chat_type).await?;
|
||||
}
|
||||
.await;
|
||||
if let Err(ref err) = res {
|
||||
info!(context, "Failed to select NDN {:?}", err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
if let Ok((msg_id, chat_id, chat_type)) = res {
|
||||
set_msg_failed(context, msg_id, error).await;
|
||||
|
||||
async fn ndn_maybe_add_info_msg(
|
||||
context: &Context,
|
||||
failed: &FailureReport,
|
||||
chat_id: ChatId,
|
||||
chat_type: Chattype,
|
||||
) -> anyhow::Result<()> {
|
||||
if chat_type == Chattype::Group {
|
||||
if let Some(failed_recipient) = &failed.failed_recipient {
|
||||
let contact_id =
|
||||
Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown).await;
|
||||
let contact = Contact::load_from_db(context, contact_id).await?;
|
||||
// Tell the user which of the recipients failed if we know that (because in a group, this might otherwise be unclear)
|
||||
let text = context
|
||||
.stock_string_repl_str(StockMessage::FailedSendingTo, contact.get_display_name())
|
||||
.await;
|
||||
chat::add_info_msg(context, chat_id, text).await;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
if chat_type == Chattype::Group {
|
||||
if let Some(failed_recipient) = &failed.failed_recipient {
|
||||
let contact_id =
|
||||
Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown).await;
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
|
||||
// Tell the user which of the recipients failed if we know that (because in a group, this might otherwise be unclear)
|
||||
chat::add_info_msg(
|
||||
context,
|
||||
chat_id,
|
||||
context
|
||||
.stock_string_repl_str(
|
||||
StockMessage::FailedSendingTo,
|
||||
contact.get_display_name(),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
.await;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The number of messages assigned to real chat (!=deaddrop, !=trash)
|
||||
|
||||
@@ -1071,9 +1071,7 @@ impl MimeMessage {
|
||||
.iter()
|
||||
.find(|p| p.typ == Viewtype::Text)
|
||||
.map(|p| p.msg.clone());
|
||||
if let Err(e) = message::handle_ndn(context, failure_report, error).await {
|
||||
warn!(context, "Could not handle ndn: {}", e);
|
||||
}
|
||||
message::handle_ndn(context, failure_report, error).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2268,35 +2266,4 @@ From: alice <alice@example.org>
|
||||
);
|
||||
assert_eq!(message.parts[0].msg, "");
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn parse_quote_top_posting() {
|
||||
let context = TestContext::new().await;
|
||||
let raw = br##"Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
Subject: Re: top posting
|
||||
MIME-Version: 1.0
|
||||
In-Reply-To: <bar@example.org>
|
||||
Message-ID: <foo@example.org>
|
||||
To: bob <bob@example.org>
|
||||
From: alice <alice@example.org>
|
||||
|
||||
A reply.
|
||||
|
||||
On 2020-10-25, Bob wrote:
|
||||
> A quote.
|
||||
"##;
|
||||
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(message.get_subject(), Some("Re: top posting".to_string()));
|
||||
|
||||
assert_eq!(message.parts.len(), 1);
|
||||
assert_eq!(message.parts[0].typ, Viewtype::Text);
|
||||
assert_eq!(
|
||||
message.parts[0].param.get(Param::Quote).unwrap(),
|
||||
"A quote."
|
||||
);
|
||||
assert_eq!(message.parts[0].msg, "A reply.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ static P_AKTIVIX_ORG: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -47,7 +46,6 @@ static P_AOL: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -76,7 +74,6 @@ static P_ARCOR_DE: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -104,7 +101,6 @@ static P_AUTISTICI_ORG: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -132,7 +128,6 @@ static P_BLUEWIN_CH: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -146,21 +141,20 @@ static P_BUZON_UY: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
Server {
|
||||
protocol: IMAP,
|
||||
socket: STARTTLS,
|
||||
hostname: "mail.buzon.uy",
|
||||
hostname: "buzon.uy",
|
||||
port: 143,
|
||||
username_pattern: EMAIL,
|
||||
},
|
||||
Server {
|
||||
protocol: SMTP,
|
||||
socket: STARTTLS,
|
||||
hostname: "mail.buzon.uy",
|
||||
hostname: "buzon.uy",
|
||||
port: 587,
|
||||
username_pattern: EMAIL,
|
||||
},
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -188,7 +182,6 @@ static P_CHELLO_AT: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -201,7 +194,6 @@ static P_COMCAST: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -214,7 +206,6 @@ static P_DISMAIL_DE: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -227,7 +218,6 @@ static P_DISROOT: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -279,7 +269,6 @@ static P_DUBBY_ORG: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
},
|
||||
]),
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -296,7 +285,6 @@ static P_EXAMPLE_COM: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -311,7 +299,6 @@ static P_FASTMAIL: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -326,7 +313,6 @@ static P_FIREMAIL_DE: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -357,7 +343,6 @@ static P_FIVE_CHAT: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
},
|
||||
]),
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -385,7 +370,6 @@ static P_FREENET_DE: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -402,7 +386,6 @@ static P_GMAIL: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: Some(Oauth2Authorizer::Gmail),
|
||||
}
|
||||
});
|
||||
@@ -438,7 +421,6 @@ static P_GMX_NET: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -468,7 +450,6 @@ static P_HERMES_RADIO: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
},
|
||||
]),
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -483,7 +464,6 @@ static P_HEY_COM: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -497,7 +477,6 @@ static P_I_UA: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -526,7 +505,6 @@ static P_ICLOUD: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -539,7 +517,6 @@ static P_KOLST_COM: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -552,7 +529,6 @@ static P_KONTENT_COM: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -565,7 +541,6 @@ static P_MAIL_RU: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -578,7 +553,6 @@ static P_MAILBOX_ORG: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -601,10 +575,9 @@ static P_NAUTA_CU: Lazy<Provider> = Lazy::new(|| {
|
||||
ConfigDefault { key: Config::MvboxMove, value: "0" },
|
||||
ConfigDefault { key: Config::E2eeEnabled, value: "0" },
|
||||
ConfigDefault { key: Config::MediaQuality, value: "1" },
|
||||
ConfigDefault { key: Config::FetchExistingMsgs, value: "0" },
|
||||
ConfigDefault { key: Config::FetchExisting, value: "0" },
|
||||
]),
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: Some(20),
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -622,7 +595,6 @@ static P_OUTLOOK_COM: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -651,7 +623,6 @@ static P_POSTEO: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -666,7 +637,6 @@ static P_PROTONMAIL: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -680,7 +650,6 @@ static P_RISEUP_NET: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -693,7 +662,6 @@ static P_ROGERS_COM: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -706,7 +674,6 @@ static P_SYSTEMLI_ORG: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -721,7 +688,6 @@ static P_T_ONLINE: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -774,7 +740,6 @@ static P_TESTRUN: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
},
|
||||
]),
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -802,7 +767,6 @@ static P_TISCALI_IT: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -815,7 +779,6 @@ static P_UKR_NET: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -843,7 +806,6 @@ static P_UNDERNET_UY: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -856,7 +818,6 @@ static P_VFEMAIL: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
server: vec![],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -884,7 +845,6 @@ static P_VODAFONE_DE: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -902,7 +862,6 @@ static P_WEB_DE: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -920,7 +879,6 @@ static P_YAHOO: Lazy<Provider> = Lazy::new(|| {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
@@ -949,7 +907,6 @@ static P_YANDEX_RU: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: Some(Oauth2Authorizer::Yandex),
|
||||
});
|
||||
|
||||
@@ -977,7 +934,6 @@ static P_ZIGGO_NL: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: false,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
@@ -1153,4 +1109,4 @@ pub static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>> = Lazy:
|
||||
});
|
||||
|
||||
pub static PROVIDER_UPDATED: Lazy<chrono::NaiveDate> =
|
||||
Lazy::new(|| chrono::NaiveDate::from_ymd(2020, 10, 30));
|
||||
Lazy::new(|| chrono::NaiveDate::from_ymd(2020, 10, 17));
|
||||
|
||||
@@ -75,7 +75,6 @@ pub struct Provider {
|
||||
pub server: Vec<Server>,
|
||||
pub config_defaults: Option<Vec<ConfigDefault>>,
|
||||
pub strict_tls: bool,
|
||||
pub max_smtp_rcpt_to: Option<u16>,
|
||||
pub oauth2_authorizer: Option<Oauth2Authorizer>,
|
||||
}
|
||||
|
||||
|
||||
@@ -104,9 +104,6 @@ def process_data(data, file):
|
||||
strict_tls = data.get("strict_tls", False)
|
||||
strict_tls = "true" if strict_tls else "false"
|
||||
|
||||
max_smtp_rcpt_to = data.get("max_smtp_rcpt_to", 0)
|
||||
max_smtp_rcpt_to = "Some(" + str(max_smtp_rcpt_to) + ")" if max_smtp_rcpt_to != 0 else "None"
|
||||
|
||||
oauth2 = data.get("oauth2", "")
|
||||
oauth2 = "Some(Oauth2Authorizer::" + camel(oauth2) + ")" if oauth2 != "" else "None"
|
||||
|
||||
@@ -122,7 +119,6 @@ def process_data(data, file):
|
||||
provider += " server: vec![\n" + server + " ],\n"
|
||||
provider += " config_defaults: " + config_defaults + ",\n"
|
||||
provider += " strict_tls: " + strict_tls + ",\n"
|
||||
provider += " max_smtp_rcpt_to: " + max_smtp_rcpt_to + ",\n"
|
||||
provider += " oauth2_authorizer: " + oauth2 + ",\n"
|
||||
provider += "});\n\n"
|
||||
else:
|
||||
|
||||
@@ -71,7 +71,7 @@ pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool, Opti
|
||||
let lines = split_lines(&input);
|
||||
let (lines, is_forwarded) = skip_forward_header(&lines);
|
||||
|
||||
let (lines, mut top_quote) = remove_top_quote(lines);
|
||||
let (lines, top_quote) = remove_top_quote(lines);
|
||||
let original_lines = &lines;
|
||||
let lines = remove_message_footer(lines);
|
||||
|
||||
@@ -79,16 +79,12 @@ pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool, Opti
|
||||
render_message(lines, false)
|
||||
} else {
|
||||
let (lines, has_nonstandard_footer) = remove_nonstandard_footer(lines);
|
||||
let (lines, mut bottom_quote) = remove_bottom_quote(lines);
|
||||
|
||||
if top_quote.is_none() && bottom_quote.is_some() {
|
||||
std::mem::swap(&mut top_quote, &mut bottom_quote);
|
||||
}
|
||||
let (lines, has_bottom_quote) = remove_bottom_quote(lines);
|
||||
|
||||
if lines.iter().all(|it| it.trim().is_empty()) {
|
||||
render_message(original_lines, false)
|
||||
} else {
|
||||
render_message(lines, has_nonstandard_footer || bottom_quote.is_some())
|
||||
render_message(lines, has_nonstandard_footer || has_bottom_quote)
|
||||
}
|
||||
};
|
||||
(text, is_forwarded, top_quote)
|
||||
@@ -109,27 +105,16 @@ fn skip_forward_header<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
}
|
||||
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>) {
|
||||
let mut first_quoted_line = lines.len();
|
||||
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
||||
let mut last_quoted_line = None;
|
||||
for (l, line) in lines.iter().enumerate().rev() {
|
||||
if is_plain_quote(line) {
|
||||
if last_quoted_line.is_none() {
|
||||
first_quoted_line = l + 1;
|
||||
}
|
||||
last_quoted_line = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(mut l_last) = last_quoted_line {
|
||||
let quoted_text = lines[l_last..first_quoted_line]
|
||||
.iter()
|
||||
.map(|s| {
|
||||
s.strip_prefix(">")
|
||||
.map_or(*s, |u| u.strip_prefix(" ").unwrap_or(u))
|
||||
})
|
||||
.join("\n");
|
||||
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
|
||||
l_last -= 1
|
||||
}
|
||||
@@ -139,9 +124,9 @@ fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>)
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
(&lines[..l_last], Some(quoted_text))
|
||||
(&lines[..l_last], true)
|
||||
} else {
|
||||
(lines, None)
|
||||
(lines, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
use super::Smtp;
|
||||
use async_smtp::*;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::DEFAULT_MAX_SMTP_RCPT_TO;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::provider::get_provider_info;
|
||||
use itertools::Itertools;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -37,51 +34,37 @@ impl Smtp {
|
||||
) -> Result<()> {
|
||||
let message_len_bytes = message.len();
|
||||
|
||||
let mut chunk_size = DEFAULT_MAX_SMTP_RCPT_TO;
|
||||
if let Some(provider) = get_provider_info(
|
||||
&context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
let recipients_display = recipients.iter().map(|x| x.to_string()).join(",");
|
||||
|
||||
let envelope =
|
||||
Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?;
|
||||
let mail = SendableEmail::new(
|
||||
envelope,
|
||||
format!("{}", job_id), // only used for internal logging
|
||||
message,
|
||||
);
|
||||
|
||||
if let Some(ref mut transport) = self.transport {
|
||||
// The timeout is 1min + 3min per MB.
|
||||
let timeout = 60 + (180 * message_len_bytes / 1_000_000) as u64;
|
||||
transport
|
||||
.send_with_timeout(mail, Some(&Duration::from_secs(timeout)))
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
) {
|
||||
if let Some(max_smtp_rcpt_to) = provider.max_smtp_rcpt_to {
|
||||
chunk_size = max_smtp_rcpt_to as usize;
|
||||
}
|
||||
}
|
||||
.map_err(Error::SendError)?;
|
||||
|
||||
for recipients_chunk in recipients.chunks(chunk_size).into_iter() {
|
||||
let recipients = recipients_chunk.to_vec();
|
||||
let recipients_display = recipients.iter().map(|x| x.to_string()).join(",");
|
||||
context.emit_event(EventType::SmtpMessageSent(format!(
|
||||
"Message len={} was smtp-sent to {}",
|
||||
message_len_bytes, recipients_display
|
||||
)));
|
||||
self.last_success = Some(std::time::SystemTime::now());
|
||||
|
||||
let envelope =
|
||||
Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?;
|
||||
let mail = SendableEmail::new(
|
||||
envelope,
|
||||
format!("{}", job_id), // only used for internal logging
|
||||
&message,
|
||||
Ok(())
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"uh? SMTP has no transport, failed to send to {}", recipients_display
|
||||
);
|
||||
|
||||
if let Some(ref mut transport) = self.transport {
|
||||
// The timeout is 1min + 3min per MB.
|
||||
let timeout = 60 + (180 * message_len_bytes / 1_000_000) as u64;
|
||||
transport
|
||||
.send_with_timeout(mail, Some(&Duration::from_secs(timeout)))
|
||||
.await
|
||||
.map_err(Error::SendError)?;
|
||||
|
||||
context.emit_event(EventType::SmtpMessageSent(format!(
|
||||
"Message len={} was smtp-sent to {}",
|
||||
message_len_bytes, recipients_display
|
||||
)));
|
||||
self.last_success = Some(std::time::SystemTime::now());
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"uh? SMTP has no transport, failed to send to {}", recipients_display
|
||||
);
|
||||
return Err(Error::NoTransport);
|
||||
}
|
||||
Err(Error::NoTransport)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
32
src/stock.rs
32
src/stock.rs
@@ -244,10 +244,6 @@ pub enum StockMessage {
|
||||
// used in summaries, a noun, not a verb (not: "to reply")
|
||||
#[strum(props(fallback = "Reply"))]
|
||||
ReplyNoun = 90,
|
||||
|
||||
#[strum(props(fallback = "You deleted the \"Saved messages\" chat.\n\n\
|
||||
To use the \"Saved messages\" feature again, create a new chat with yourself."))]
|
||||
SelfDeletedMsgBody = 91,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -467,7 +463,6 @@ mod tests {
|
||||
|
||||
use crate::constants::DC_CONTACT_ID_SELF;
|
||||
|
||||
use crate::chat::Chat;
|
||||
use crate::chatlist::Chatlist;
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
@@ -659,31 +654,8 @@ mod tests {
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 2);
|
||||
|
||||
let chat0 = Chat::load_from_db(&t.ctx, chats.get_chat_id(0))
|
||||
.await
|
||||
.unwrap();
|
||||
let (self_talk_id, device_chat_id) = if chat0.is_self_talk() {
|
||||
(chats.get_chat_id(0), chats.get_chat_id(1))
|
||||
} else {
|
||||
(chats.get_chat_id(1), chats.get_chat_id(0))
|
||||
};
|
||||
|
||||
// delete self-talk first; this adds a message to device-chat about how self-talk can be restored
|
||||
let device_chat_msgs_before = chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None)
|
||||
.await
|
||||
.len();
|
||||
self_talk_id.delete(&t.ctx).await.ok();
|
||||
assert_eq!(
|
||||
chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None)
|
||||
.await
|
||||
.len(),
|
||||
device_chat_msgs_before + 1
|
||||
);
|
||||
|
||||
// delete device chat
|
||||
device_chat_id.delete(&t.ctx).await.ok();
|
||||
|
||||
// check, that the chatlist is empty
|
||||
chats.get_chat_id(0).delete(&t.ctx).await.ok();
|
||||
chats.get_chat_id(1).delete(&t.ctx).await.ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user