mirror of
https://github.com/chatmail/core.git
synced 2026-07-08 16:35:00 +03:00
Compare commits
43 Commits
1.77.0
...
futures-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7db65afd8 | ||
|
|
3ffc985968 | ||
|
|
369609b26c | ||
|
|
d033dcf395 | ||
|
|
5ef2a85c10 | ||
|
|
8c0bc9080c | ||
|
|
6bf2c5415f | ||
|
|
2f31033a88 | ||
|
|
ceaed0f552 | ||
|
|
e9963ecc0d | ||
|
|
9ef0b43c36 | ||
|
|
801d636eb5 | ||
|
|
dfbfd4fe74 | ||
|
|
7455989729 | ||
|
|
8b2b9e1093 | ||
|
|
a8cf05ea5d | ||
|
|
969508ae36 | ||
|
|
f581ecc805 | ||
|
|
a63464765c | ||
|
|
e9fe8ce118 | ||
|
|
1fa892c239 | ||
|
|
1afbbbc737 | ||
|
|
66c4de2607 | ||
|
|
213e67dea2 | ||
|
|
86c884fe1e | ||
|
|
a4d5c8cd2f | ||
|
|
330665afbe | ||
|
|
be1d87c3c3 | ||
|
|
14ab3c8651 | ||
|
|
9c04ed483e | ||
|
|
a8a5e184ab | ||
|
|
c571595980 | ||
|
|
80f2ccc9ed | ||
|
|
b2e9e57859 | ||
|
|
3c75b36148 | ||
|
|
bcd8e330cb | ||
|
|
f75f8ad76d | ||
|
|
574b78cf31 | ||
|
|
92f0e8472b | ||
|
|
69bd5d2ab0 | ||
|
|
6eaf04107d | ||
|
|
d80fdb20c7 | ||
|
|
f618c87ee5 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --tests --examples
|
||||
args: --workspace --tests --examples --benches
|
||||
|
||||
docs:
|
||||
name: Rust doc comments
|
||||
|
||||
21
.github/workflows/dependabot.yml
vendored
Normal file
21
.github/workflows/dependabot.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Dependabot auto-approve
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v1.1.1
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Approve a PR
|
||||
run: gh pr review --approve "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -29,3 +29,7 @@ deltachat-ffi/xml
|
||||
|
||||
coverage/
|
||||
.DS_Store
|
||||
.vscode/launch.json
|
||||
python/accounts.txt
|
||||
python/all-testaccounts.txt
|
||||
tmp/
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### API-Changes
|
||||
|
||||
- replaced stock string `DC_STR_ONE_MOMENT` by `DC_STR_NOT_CONNECTED` #3222
|
||||
|
||||
### Fixes
|
||||
|
||||
- Take `delete_device_after` into account when calculating ephemeral loop timeout #3211
|
||||
- Fix a bug where a blocked contact could send a contact request #3218
|
||||
- Make sure, videochat-room-names are always URL-safe #3231
|
||||
- Try removing account folder multiple times in case of failure #3229
|
||||
- Ignore messages from all spam folders if there are many #3246
|
||||
|
||||
### Changes
|
||||
|
||||
- Further improve finding the correct server after logging in #3208
|
||||
- `get_connectivity_html()` returns HTML as non-scalable #3213
|
||||
- add update-serial to `DC_EVENT_WEBXDC_STATUS_UPDATE` #3215
|
||||
- Speed up message receiving via IMAP a bit #3225
|
||||
- mark messages as seen on IMAP in batches #3223
|
||||
- remove Received: based draft detection heuristic #3230
|
||||
|
||||
|
||||
## 1.77.0
|
||||
|
||||
### API changes
|
||||
|
||||
@@ -4,10 +4,18 @@ include(GNUInstallDirs)
|
||||
|
||||
find_program(CARGO cargo)
|
||||
|
||||
if(APPLE)
|
||||
set(DYNAMIC_EXT "dylib")
|
||||
elseif(UNIX)
|
||||
set(DYNAMIC_EXT "so")
|
||||
else()
|
||||
set(DYNAMIC_EXT "dll")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"target/release/libdeltachat.a"
|
||||
"target/release/libdeltachat.so"
|
||||
"target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"target/release/pkgconfig/deltachat.pc"
|
||||
COMMAND
|
||||
PREFIX=${CMAKE_INSTALL_PREFIX}
|
||||
@@ -32,11 +40,11 @@ add_custom_target(
|
||||
ALL
|
||||
DEPENDS
|
||||
"target/release/libdeltachat.a"
|
||||
"target/release/libdeltachat.so"
|
||||
"target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"target/release/pkgconfig/deltachat.pc"
|
||||
)
|
||||
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "target/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "target/release/libdeltachat.so" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "target/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "target/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -224,7 +224,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"ouroboros",
|
||||
"pin-utils",
|
||||
"stop-token 0.2.0",
|
||||
"stop-token",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -1090,7 +1090,6 @@ dependencies = [
|
||||
"escaper",
|
||||
"fast-socks5",
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"humansize",
|
||||
"image",
|
||||
@@ -1123,7 +1122,6 @@ dependencies = [
|
||||
"sha-1 0.10.0",
|
||||
"sha2 0.10.2",
|
||||
"smallvec",
|
||||
"stop-token 0.7.0",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"surf",
|
||||
@@ -3609,18 +3607,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stop-token"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af91f480ee899ab2d9f8435bfdfc14d08a5754bd9d3fef1f1a1c23336aad6c8b"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"cfg-if 1.0.0",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "str-buf"
|
||||
version = "1.0.5"
|
||||
|
||||
@@ -62,7 +62,6 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
smallvec = "1"
|
||||
stop-token = "0.7"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
surf = { version = "2.3", default-features = false, features = ["h1-client"] }
|
||||
@@ -81,7 +80,6 @@ zip = { version = "0.6.2", default-features = false, features = ["deflate"] }
|
||||
ansi_term = "0.12.0"
|
||||
async-std = { version = "1", features = ["unstable", "attributes"] }
|
||||
criterion = { version = "0.3.4", features = ["async_std"] }
|
||||
futures-lite = "1.12"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -29,7 +29,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
|
||||
c.bench_function("chat::get_chat_msgs (load messages from 10 chats)", |b| {
|
||||
b.to_async(AsyncStdExecutor)
|
||||
.iter(|| get_chat_msgs_benchmark(black_box(&path.as_ref()), black_box(&chats)))
|
||||
.iter(|| get_chat_msgs_benchmark(black_box(path.as_ref()), black_box(&chats)))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
|
||||
@@ -5,7 +5,7 @@ use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
|
||||
async fn get_chat_list_benchmark(context: &Context) {
|
||||
Chatlist::try_load(&context, 0, None, None).await.unwrap();
|
||||
Chatlist::try_load(context, 0, None, None).await.unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
|
||||
@@ -31,7 +31,7 @@ Hello {i}",
|
||||
i = i,
|
||||
i_dec = i - 1,
|
||||
);
|
||||
dc_receive_imf(&context, black_box(imf_raw.as_bytes()), "INBOX", false)
|
||||
dc_receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -5606,11 +5606,16 @@ void dc_event_unref(dc_event_t* event);
|
||||
/**
|
||||
* webxdc status update received.
|
||||
* To get the received status update, use dc_get_webxdc_status_updates() with
|
||||
* `serial` set to the last known update.
|
||||
* `serial` set to the last known update
|
||||
* (in case of special bots, `status_update_serial` from `data2`
|
||||
* may help to calculate the last known update for dc_get_webxdc_status_updates();
|
||||
* UIs must not peek at this parameter to avoid races in the status replication
|
||||
* eg. when events arrive while initial updates are played back).
|
||||
*
|
||||
* To send status updates, use dc_send_webxdc_status_update().
|
||||
*
|
||||
* @param data1 (int) msg_id
|
||||
* @param data2 (int) 0
|
||||
* @param data2 (int) status_update_serial - must not be used by UI implementations.
|
||||
*/
|
||||
#define DC_EVENT_WEBXDC_STATUS_UPDATE 2120
|
||||
|
||||
@@ -6218,9 +6223,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced by the domain of the configured e-mail address.
|
||||
#define DC_STR_STORAGE_ON_DOMAIN 105
|
||||
|
||||
/// "One moment…"
|
||||
///
|
||||
/// Used in the connectivity view when some information are not yet there.
|
||||
/// @deprecated Deprecated 2022-04-16, this string is no longer needed.
|
||||
#define DC_STR_ONE_MOMENT 106
|
||||
|
||||
/// "Connected"
|
||||
@@ -6309,6 +6312,11 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced with the group name.
|
||||
#define DC_STR_SECURE_JOIN_GROUP_QR_DESC 120
|
||||
|
||||
/// "Not connected"
|
||||
///
|
||||
/// Used as status in the connectivity view.
|
||||
#define DC_STR_NOT_CONNECTED 121
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -503,7 +503,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::SecurejoinJoinerProgress { contact_id, .. } => {
|
||||
contact_id.to_u32() as libc::c_int
|
||||
}
|
||||
EventType::WebxdcStatusUpdate(msg_id) => msg_id.to_u32() as libc::c_int,
|
||||
EventType::WebxdcStatusUpdate { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,9 +535,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::ImexFileWritten(_)
|
||||
| EventType::MsgsNoticed(_)
|
||||
| EventType::ConnectivityChanged
|
||||
| EventType::SelfavatarChanged
|
||||
| EventType::WebxdcStatusUpdate(_)
|
||||
| EventType::ChatModified(_) => 0,
|
||||
| EventType::SelfavatarChanged => 0,
|
||||
EventType::ChatModified(_) => 0,
|
||||
EventType::MsgsChanged { msg_id, .. }
|
||||
| EventType::IncomingMsg { msg_id, .. }
|
||||
| EventType::MsgDelivered { msg_id, .. }
|
||||
@@ -546,6 +545,10 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
||||
EventType::SecurejoinInviterProgress { progress, .. }
|
||||
| EventType::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int,
|
||||
EventType::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int,
|
||||
EventType::WebxdcStatusUpdate {
|
||||
status_update_serial,
|
||||
..
|
||||
} => status_update_serial.to_u32() as libc::c_int,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +590,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
||||
| EventType::SecurejoinJoinerProgress { .. }
|
||||
| EventType::ConnectivityChanged
|
||||
| EventType::SelfavatarChanged
|
||||
| EventType::WebxdcStatusUpdate(_)
|
||||
| EventType::WebxdcStatusUpdate { .. }
|
||||
| EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(),
|
||||
EventType::ConfigureProgress { comment, .. } => {
|
||||
if let Some(comment) = comment {
|
||||
|
||||
@@ -54,12 +54,13 @@ and you won't get the update by `setUpdateListener()`.
|
||||
### setUpdateListener()
|
||||
|
||||
```js
|
||||
window.webxdc.setUpdateListener((update) => {}, serial);
|
||||
let promise = window.webxdc.setUpdateListener((update) => {}, serial);
|
||||
```
|
||||
|
||||
With `setUpdateListener()` you define a callback that receives the updates
|
||||
sent by `sendUpdate()`. The callback is called for updates sent by you or other peers.
|
||||
The `serial` specifies the last serial that you know about (defaults to 0).
|
||||
The returned promise resolves when the listener has processed all the update messages known at the time when `setUpdateListener` was called.
|
||||
|
||||
Each `update` which is passed to the callback comes with the following properties:
|
||||
|
||||
@@ -176,6 +177,9 @@ just clone and start adapting things to your need.
|
||||
|
||||
- older devices might not have the newest js features in their webview,
|
||||
you may want to transpile your code down to an older js version eg. with https://babeljs.io
|
||||
- viewport and scaling features are implementation specific,
|
||||
if you want to have an explicit behavior, you can add eg.
|
||||
`<meta name="viewport" content="initial-scale=1; user-scalable=no">` to your Webxdc
|
||||
- there are tons of ideas for enhancements of the API and the file format,
|
||||
eg. in the future, we will may define icon- and manifest-files,
|
||||
allow to aggregate the state or add metadata.
|
||||
|
||||
@@ -21,7 +21,6 @@ use deltachat::message::{self, Message, MessageState, MsgId, Viewtype};
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::EventType;
|
||||
use deltachat::{config, provider};
|
||||
use std::fs;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@@ -93,16 +92,13 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
println!("(8) Rest but server config reset.");
|
||||
}
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
}
|
||||
|
||||
async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<()> {
|
||||
let data = dc_read_file(context, filename).await?;
|
||||
|
||||
if let Err(err) = dc_receive_imf(context, &data, "import", false).await {
|
||||
if let Err(err) = dc_receive_imf(context, &data, false).await {
|
||||
println!("dc_receive_imf errored: {:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
@@ -164,10 +160,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
}
|
||||
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
|
||||
if read_cnt > 0 {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ if __name__ == "__main__":
|
||||
|
||||
print("running:", " ".join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so src/deltachat/*.dylib src/deltachat/*.dll" , shell=True)
|
||||
|
||||
if len(sys.argv) <= 1 or sys.argv[1] != "onlybuild":
|
||||
subprocess.check_call([
|
||||
|
||||
@@ -5,7 +5,7 @@ import calendar
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
import os
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, from_optional_dc_charpointer, iter_array
|
||||
from .capi import lib, ffi
|
||||
from . import const
|
||||
from .message import Message
|
||||
@@ -517,7 +517,7 @@ class Chat(object):
|
||||
lib.dc_array_get_timestamp(dc_array, i),
|
||||
timezone.utc
|
||||
),
|
||||
marker=from_dc_charpointer(lib.dc_array_get_marker(dc_array, i)),
|
||||
marker=from_optional_dc_charpointer(lib.dc_array_get_marker(dc_array, i)),
|
||||
)
|
||||
for i in range(lib.dc_array_get_cnt(dc_array))
|
||||
]
|
||||
|
||||
@@ -40,8 +40,6 @@ def dc_account_extra_configure(account):
|
||||
# We just deleted the folder, so we have to make DC forget about it, too
|
||||
if account.get_config("configured_sentbox_folder") == folder:
|
||||
account.set_config("configured_sentbox_folder", None)
|
||||
if account.get_config("configured_spam_folder") == folder:
|
||||
account.set_config("configured_spam_folder", None)
|
||||
|
||||
setattr(account, "direct_imap", imap)
|
||||
|
||||
@@ -230,7 +228,15 @@ class DirectImap:
|
||||
self.account.log("imap-direct: idle_check returned {!r}".format(res))
|
||||
return res
|
||||
|
||||
def idle_wait_for_seen(self):
|
||||
def idle_wait_for_new_message(self, terminate=False):
|
||||
while 1:
|
||||
for item in self.idle_check():
|
||||
if item[1] in (b'EXISTS', b'RECENT'):
|
||||
if terminate:
|
||||
self.idle_done()
|
||||
return item
|
||||
|
||||
def idle_wait_for_seen(self, terminate=False):
|
||||
""" Return first message with SEEN flag
|
||||
from a running idle-stream REtiurn.
|
||||
"""
|
||||
@@ -239,6 +245,8 @@ class DirectImap:
|
||||
if item[1] == FETCH:
|
||||
if item[2][0] == FLAGS:
|
||||
if SEEN in item[2][1]:
|
||||
if terminate:
|
||||
self.idle_done()
|
||||
return item[0]
|
||||
|
||||
def idle_done(self):
|
||||
|
||||
@@ -1013,7 +1013,7 @@ class TestOnlineAccount:
|
||||
assert ev.data1 == msg2.chat.id
|
||||
assert ev.data2 == 0
|
||||
|
||||
ac2.direct_imap.idle_check(terminate=True)
|
||||
ac2.direct_imap.idle_wait_for_new_message(terminate=True)
|
||||
lp.step("1")
|
||||
for i in range(2):
|
||||
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
|
||||
@@ -1044,8 +1044,7 @@ class TestOnlineAccount:
|
||||
|
||||
ac1.create_chat(ac2).send_text("Hello!")
|
||||
|
||||
# Wait for the message to arrive.
|
||||
ac2.direct_imap.idle_check(terminate=True)
|
||||
ac2.direct_imap.idle_wait_for_new_message(terminate=True)
|
||||
|
||||
# Emulate moving of the message to DeltaChat folder by Sieve rule.
|
||||
# mailcow server contains this rule by default.
|
||||
@@ -1059,8 +1058,7 @@ class TestOnlineAccount:
|
||||
# Accept the contact request.
|
||||
msg.chat.accept()
|
||||
ac2.mark_seen_messages([msg])
|
||||
ac2.direct_imap.idle_wait_for_seen()
|
||||
ac2.direct_imap.idle_done()
|
||||
ac2.direct_imap.idle_wait_for_seen(terminate=True)
|
||||
|
||||
fetch = list(ac2.direct_imap.conn.fetch("*", b'FLAGS').values())
|
||||
flags = fetch[-1][b'FLAGS']
|
||||
@@ -1437,6 +1435,7 @@ class TestOnlineAccount:
|
||||
ac1.direct_imap.create_folder("Drafts")
|
||||
ac1.direct_imap.create_folder("Sent")
|
||||
ac1.direct_imap.create_folder("Spam")
|
||||
ac1.direct_imap.create_folder("Junk")
|
||||
|
||||
acfactory.wait_configure_and_start_io()
|
||||
# Wait until each folder was selected once and we are IDLEing again:
|
||||
@@ -1470,6 +1469,15 @@ class TestOnlineAccount:
|
||||
|
||||
Unknown message in Spam
|
||||
""".format(ac1.get_config("configured_addr")))
|
||||
ac1.direct_imap.append("Junk", """
|
||||
From: unknown.address@junk.org
|
||||
Subject: subj
|
||||
To: {}
|
||||
Message-ID: <spam.message@junk.org>
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Unknown message in Junk
|
||||
""".format(ac1.get_config("configured_addr")))
|
||||
|
||||
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
||||
lp.sec("All prepared, now let DC find the message")
|
||||
@@ -2211,7 +2219,7 @@ class TestOnlineAccount:
|
||||
ac1.direct_imap.idle_start()
|
||||
ac2.create_chat(ac1).send_text("Hi")
|
||||
|
||||
ac1.direct_imap.idle_check(terminate=False)
|
||||
ac1.direct_imap.idle_wait_for_new_message(terminate=False)
|
||||
ac1.maybe_network()
|
||||
|
||||
ac1._evtracker.wait_for_all_work_done()
|
||||
@@ -2223,7 +2231,7 @@ class TestOnlineAccount:
|
||||
|
||||
ac2.create_chat(ac1).send_text("Hi 2")
|
||||
|
||||
ac1.direct_imap.idle_check(terminate=True)
|
||||
ac1.direct_imap.idle_wait_for_new_message(terminate=True)
|
||||
ac1.maybe_network()
|
||||
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_CONNECTED, const.DC_CONNECTIVITY_WORKING)
|
||||
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_WORKING, const.DC_CONNECTIVITY_CONNECTED)
|
||||
@@ -2248,7 +2256,7 @@ class TestOnlineAccount:
|
||||
ac1.direct_imap.idle_start()
|
||||
ac2.create_chat(ac1).send_text("Hi")
|
||||
|
||||
ac1.direct_imap.idle_check(terminate=True)
|
||||
ac1.direct_imap.idle_wait_for_new_message(terminate=True)
|
||||
ac1.maybe_network()
|
||||
|
||||
while 1:
|
||||
@@ -2438,25 +2446,23 @@ class TestOnlineAccount:
|
||||
|
||||
acfactory.wait_configure_and_start_io()
|
||||
|
||||
imap2 = ac2.direct_imap
|
||||
imap2.idle_start()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat1 = ac1.create_chat(ac2)
|
||||
ac2.create_chat(ac1)
|
||||
|
||||
lp.sec("ac1: send message to ac2")
|
||||
sent_msg = chat1.send_text("hello")
|
||||
imap2.idle_check(terminate=False)
|
||||
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg.text == "hello"
|
||||
|
||||
imap2.idle_check(terminate=True)
|
||||
lp.sec("ac2: wait for close/expunge on autodelete")
|
||||
ac2._evtracker.get_info_contains("close/expunge succeeded")
|
||||
|
||||
assert len(imap2.get_all_messages()) == 0
|
||||
lp.sec("ac2: check that message was autodeleted on server")
|
||||
assert len(ac2.direct_imap.get_all_messages()) == 0
|
||||
|
||||
# Mark deleted message as seen and check that read receipt arrives
|
||||
lp.sec("ac2: Mark deleted message as seen and check that read receipt arrives")
|
||||
msg.mark_seen()
|
||||
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
|
||||
assert ev.data1 == chat1.id
|
||||
@@ -2717,13 +2723,7 @@ class TestOnlineAccount:
|
||||
ac1.direct_imap.select_config_folder("inbox")
|
||||
ac1.direct_imap.idle_start()
|
||||
acfactory.get_accepted_chat(ac2, ac1).send_text("hello")
|
||||
while True:
|
||||
if len(ac1.direct_imap.idle_check(terminate=True)) > 1:
|
||||
# If length is 1, it's [(b'OK', b'Still here')]
|
||||
# Could happen on very slow network.
|
||||
#
|
||||
# More is usually [(1, b'EXISTS'), (1, b'RECENT')]
|
||||
break
|
||||
ac1.direct_imap.idle_wait_for_new_message(terminate=True)
|
||||
ac1.direct_imap.conn.move(["*"], folder) # "*" means "biggest UID in mailbox"
|
||||
|
||||
lp.sec("Everything prepared, now see if DeltaChat finds the message (" + variant + ")")
|
||||
|
||||
@@ -8,7 +8,7 @@ envlist =
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
pytest -n6 --reruns 2 --reruns-delay 5 -v -rsXx --ignored --strict-tls {posargs: tests examples}
|
||||
pytest -n1 --reruns 2 --reruns-delay 5 -v -rsXx --ignored --strict-tls {posargs: tests examples}
|
||||
python tests/package_wheels.py {toxworkdir}/wheelhouse
|
||||
passenv =
|
||||
TRAVIS
|
||||
|
||||
@@ -54,7 +54,11 @@ impl Accounts {
|
||||
ensure!(dir.exists().await, "directory does not exist");
|
||||
|
||||
let config_file = dir.join(CONFIG_NAME);
|
||||
ensure!(config_file.exists().await, "accounts.toml does not exist");
|
||||
ensure!(
|
||||
config_file.exists().await,
|
||||
"{:?} does not exist",
|
||||
config_file
|
||||
);
|
||||
|
||||
let config = Config::from_file(config_file)
|
||||
.await
|
||||
@@ -144,9 +148,27 @@ impl Accounts {
|
||||
drop(ctx);
|
||||
|
||||
if let Some(cfg) = self.config.get_account(id).await {
|
||||
fs::remove_dir_all(async_std::path::PathBuf::from(&cfg.dir))
|
||||
.await
|
||||
.context("failed to remove account data")?;
|
||||
// Spend up to 1 minute trying to remove the files.
|
||||
// Files may remain locked up to 30 seconds due to r2d2 bug:
|
||||
// https://github.com/sfackler/r2d2/issues/99
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
counter += 1;
|
||||
|
||||
if let Err(err) = fs::remove_dir_all(async_std::path::PathBuf::from(&cfg.dir))
|
||||
.await
|
||||
.context("failed to remove account data")
|
||||
{
|
||||
if counter > 60 {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// Wait 1 second and try again.
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.config.remove_account(id).await?;
|
||||
|
||||
|
||||
40
src/blob.rs
40
src/blob.rs
@@ -940,7 +940,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_recode_image() {
|
||||
async fn test_recode_image_1() {
|
||||
let bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
|
||||
// BALANCED_IMAGE_SIZE > 1000, the original image size, so the image is not scaled down:
|
||||
send_image_check_mediaquality(Some("0"), bytes, 1000, 1000, 0, 1000, 1000)
|
||||
@@ -957,7 +957,10 @@ mod tests {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_recode_image_2() {
|
||||
// The "-rotated" files are rotated by 270 degrees using the Exif metadata
|
||||
let bytes = include_bytes!("../test-data/image/rectangle2000x1800-rotated.jpg");
|
||||
let img_rotated = send_image_check_mediaquality(
|
||||
@@ -978,18 +981,24 @@ mod tests {
|
||||
.write_to(&mut buf, image::ImageFormat::Jpeg)
|
||||
.unwrap();
|
||||
let bytes = buf.into_inner();
|
||||
let img_rotated = send_image_check_mediaquality(
|
||||
Some("0"),
|
||||
&bytes,
|
||||
BALANCED_IMAGE_SIZE * 1800 / 2000,
|
||||
BALANCED_IMAGE_SIZE,
|
||||
0,
|
||||
BALANCED_IMAGE_SIZE * 1800 / 2000,
|
||||
BALANCED_IMAGE_SIZE,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_correct_rotation(&img_rotated);
|
||||
|
||||
// Do this in parallel to speed up the test a bit
|
||||
// (it still takes very long though)
|
||||
let bytes2 = bytes.clone();
|
||||
let join_handle = async_std::task::spawn(async move {
|
||||
let img_rotated = send_image_check_mediaquality(
|
||||
Some("0"),
|
||||
&bytes2,
|
||||
BALANCED_IMAGE_SIZE * 1800 / 2000,
|
||||
BALANCED_IMAGE_SIZE,
|
||||
0,
|
||||
BALANCED_IMAGE_SIZE * 1800 / 2000,
|
||||
BALANCED_IMAGE_SIZE,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_correct_rotation(&img_rotated);
|
||||
});
|
||||
|
||||
let img_rotated = send_image_check_mediaquality(
|
||||
Some("1"),
|
||||
@@ -1004,6 +1013,11 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_correct_rotation(&img_rotated);
|
||||
|
||||
join_handle.await;
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_recode_image_3() {
|
||||
let bytes = include_bytes!("../test-data/image/rectangle200x180-rotated.jpg");
|
||||
let img_rotated = send_image_check_mediaquality(Some("0"), bytes, 200, 180, 270, 180, 200)
|
||||
.await
|
||||
|
||||
105
src/chat.rs
105
src/chat.rs
@@ -29,7 +29,6 @@ use crate::dc_tools::{
|
||||
use crate::ephemeral::Timer as EphemeralTimer;
|
||||
use crate::events::EventType;
|
||||
use crate::html::new_html_mimepart;
|
||||
use crate::job::{self, Action};
|
||||
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
@@ -215,10 +214,7 @@ impl ChatId {
|
||||
}
|
||||
}
|
||||
};
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
@@ -493,10 +489,7 @@ impl ChatId {
|
||||
})
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -551,14 +544,10 @@ impl ChatId {
|
||||
.execute("DELETE FROM chats WHERE id=?;", paramsv![self])
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
|
||||
job::kill_action(context, Action::Housekeeping).await?;
|
||||
let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10);
|
||||
job::add(context, j).await?;
|
||||
context.set_config(Config::LastHousekeeping, None).await?;
|
||||
context.interrupt_inbox(InterruptInfo::new(false)).await;
|
||||
|
||||
if chat.is_self_talk() {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
@@ -583,9 +572,9 @@ impl ChatId {
|
||||
};
|
||||
|
||||
if changed {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: self,
|
||||
msg_id: if msg.is_some() {
|
||||
context.emit_msgs_changed(
|
||||
self,
|
||||
if msg.is_some() {
|
||||
match self.get_draft_msg_id(context).await? {
|
||||
Some(msg_id) => msg_id,
|
||||
None => MsgId::new(0),
|
||||
@@ -593,7 +582,7 @@ impl ChatId {
|
||||
} else {
|
||||
MsgId::new(0)
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1254,11 +1243,7 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
let from = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context("Cannot prepare message for sending, address is not configured.")?;
|
||||
|
||||
let from = context.get_configured_addr().await?;
|
||||
let new_rfc724_mid = {
|
||||
let grpid = match self.typ {
|
||||
Chattype::Group => Some(self.grpid.as_str()),
|
||||
@@ -1799,10 +1784,7 @@ pub async fn prepare_msg(context: &Context, chat_id: ChatId, msg: &mut Message)
|
||||
);
|
||||
|
||||
let msg_id = prepare_msg_common(context, chat_id, msg, MessageState::OutPreparing).await?;
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.emit_msgs_changed(msg.chat_id, msg.id);
|
||||
|
||||
Ok(msg_id)
|
||||
}
|
||||
@@ -1970,20 +1952,14 @@ pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message
|
||||
.await
|
||||
.context("failed to send message, queued for later sending")?;
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.emit_msgs_changed(msg.chat_id, msg.id);
|
||||
}
|
||||
Ok(msg.id)
|
||||
}
|
||||
|
||||
async fn send_msg_inner(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
|
||||
if prepare_send_msg(context, chat_id, msg).await?.is_some() {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.emit_msgs_changed(msg.chat_id, msg.id);
|
||||
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
context.emit_event(EventType::LocationChanged(Some(ContactId::SELF)));
|
||||
@@ -2047,10 +2023,7 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result<Option<
|
||||
|
||||
let mut recipients = mimefactory.recipients();
|
||||
|
||||
let from = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let from = context.get_configured_addr().await?;
|
||||
let lowercase_from = from.to_lowercase();
|
||||
|
||||
// Send BCC to self if it is enabled and we are not going to
|
||||
@@ -2574,10 +2547,7 @@ pub async fn create_group_chat(
|
||||
add_to_chat_contacts_table(context, chat_id, ContactId::SELF).await?;
|
||||
}
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
|
||||
if protect == ProtectionStatus::Protected {
|
||||
// this part is to stay compatible to verified groups,
|
||||
@@ -2631,10 +2601,7 @@ pub async fn create_broadcast_list(context: &Context) -> Result<ChatId> {
|
||||
.await?;
|
||||
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
@@ -2723,10 +2690,7 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
context.sync_qr_code_tokens(Some(chat_id)).await?;
|
||||
context.send_sync_msg().await?;
|
||||
}
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let self_addr = context.get_configured_addr().await.unwrap_or_default();
|
||||
if addr_cmp(contact.get_addr(), &self_addr) {
|
||||
// ourself is added using ContactId::SELF, do not add this address explicitly.
|
||||
// if SELF is not in the group, members cannot be added at all.
|
||||
@@ -3000,10 +2964,7 @@ pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -
|
||||
msg.param.set(Param::Arg, &chat.name);
|
||||
}
|
||||
msg.id = send_msg(context, chat_id, &mut msg).await?;
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.emit_msgs_changed(chat_id, msg.id);
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
success = true;
|
||||
@@ -3065,10 +3026,7 @@ pub async fn set_chat_profile_image(
|
||||
chat.update_param(context).await?;
|
||||
if chat.is_promoted() && !chat.is_mailing_list() {
|
||||
msg.id = send_msg(context, chat_id, &mut msg).await?;
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.emit_msgs_changed(chat_id, msg.id);
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
Ok(())
|
||||
@@ -3163,10 +3121,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
|
||||
}
|
||||
}
|
||||
for (chat_id, msg_id) in created_chats.iter().zip(created_msgs.iter()) {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: *chat_id,
|
||||
msg_id: *msg_id,
|
||||
});
|
||||
context.emit_msgs_changed(*chat_id, *msg_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -3306,9 +3261,9 @@ pub async fn add_device_msg_with_importance(
|
||||
|
||||
if !msg_id.is_unset() {
|
||||
if important {
|
||||
context.emit_event(EventType::IncomingMsg { chat_id, msg_id });
|
||||
context.emit_incoming_msg(chat_id, msg_id);
|
||||
} else {
|
||||
context.emit_event(EventType::MsgsChanged { chat_id, msg_id });
|
||||
context.emit_msgs_changed(chat_id, msg_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3403,7 +3358,8 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
).await?;
|
||||
|
||||
let msg_id = MsgId::new(row_id.try_into()?);
|
||||
context.emit_event(EventType::MsgsChanged { chat_id, msg_id });
|
||||
context.emit_msgs_changed(chat_id, msg_id);
|
||||
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
@@ -4236,7 +4192,6 @@ mod tests {
|
||||
num
|
||||
)
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -4686,9 +4641,7 @@ mod tests {
|
||||
assert_eq!(msg.match_indices("Gr.").count(), 1);
|
||||
|
||||
// Bob receives this message, he may detect group by `References:`- or `Chat-Group:`-header
|
||||
dc_receive_imf(&bob, msg.as_bytes(), "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&bob, msg.as_bytes(), false).await.unwrap();
|
||||
let msg = bob.get_last_msg().await;
|
||||
|
||||
let bob_chat = Chat::load_from_db(&bob, msg.chat_id).await?;
|
||||
@@ -4707,9 +4660,7 @@ mod tests {
|
||||
assert_eq!(msg.match_indices("Chat-").count(), 0);
|
||||
|
||||
// Alice receives this message - she can still detect the group by the `References:`-header
|
||||
dc_receive_imf(&alice, msg.as_bytes(), "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&alice, msg.as_bytes(), false).await.unwrap();
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.chat_id, alice_chat_id);
|
||||
assert_eq!(msg.text, Some("ho!".to_string()));
|
||||
@@ -4734,7 +4685,6 @@ mod tests {
|
||||
Date: Fri, 23 Apr 2021 10:00:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -4782,7 +4732,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 19:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -4830,7 +4779,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 19:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -4877,7 +4825,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 19:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -503,7 +503,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello foo\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -564,7 +563,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 22:38:57 +0000\n\
|
||||
\n\
|
||||
hello foo\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
//! # Key-value configuration management.
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
use strum::{EnumProperty, IntoEnumIterator};
|
||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::ChatId;
|
||||
use crate::constants::DC_VERSION_STR;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::{dc_get_abs_path, improve_single_line_input};
|
||||
use crate::events::EventType;
|
||||
use crate::message::MsgId;
|
||||
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
|
||||
@@ -131,7 +129,6 @@ pub enum Config {
|
||||
ConfiguredInboxFolder,
|
||||
ConfiguredMvboxFolder,
|
||||
ConfiguredSentboxFolder,
|
||||
ConfiguredSpamFolder,
|
||||
ConfiguredTimestamp,
|
||||
ConfiguredProvider,
|
||||
Configured,
|
||||
@@ -229,6 +226,12 @@ impl Context {
|
||||
Ok(self.get_config_int(key).await? != 0)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_configured_addr(&self) -> Result<String> {
|
||||
self.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context("no address configured")
|
||||
}
|
||||
|
||||
pub(crate) async fn should_watch_mvbox(&self) -> Result<bool> {
|
||||
Ok(self.get_config_bool(Config::MvboxMove).await?
|
||||
|| self.get_config_bool(Config::OnlyFetchMvbox).await?)
|
||||
@@ -293,11 +296,8 @@ impl Context {
|
||||
}
|
||||
Config::DeleteDeviceAfter => {
|
||||
let ret = self.sql.set_raw_config(key, value).await;
|
||||
// Force chatlist reload to delete old messages immediately.
|
||||
self.emit_event(EventType::MsgsChanged {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
// Interrupt ephemeral loop to delete old messages immediately.
|
||||
self.interrupt_ephemeral_task().await;
|
||||
ret?
|
||||
}
|
||||
Config::Displayname => {
|
||||
|
||||
@@ -85,7 +85,7 @@ impl Context {
|
||||
async fn inner_configure(&self) -> Result<()> {
|
||||
info!(self, "Configure ...");
|
||||
|
||||
let mut param = LoginParam::from_database(self, "").await?;
|
||||
let mut param = LoginParam::load_candidate_params(self).await?;
|
||||
let success = configure(self, &mut param).await;
|
||||
self.set_config(Config::NotifyAboutWrongPw, None).await?;
|
||||
|
||||
@@ -454,7 +454,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
|
||||
progress!(ctx, 910);
|
||||
// the trailing underscore is correct
|
||||
param.save_to_database(ctx, "configured_").await?;
|
||||
param.save_as_configured_params(ctx).await?;
|
||||
ctx.set_config(Config::ConfiguredTimestamp, Some(&time().to_string()))
|
||||
.await?;
|
||||
|
||||
@@ -699,11 +699,11 @@ pub enum Error {
|
||||
error: quick_xml::Error,
|
||||
},
|
||||
|
||||
#[error("Failed to get URL: {0}")]
|
||||
ReadUrl(#[from] self::read_url::Error),
|
||||
|
||||
#[error("Number of redirection is exceeded")]
|
||||
Redirection,
|
||||
|
||||
#[error("{0:#}")]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,20 +1,40 @@
|
||||
use crate::context::Context;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("URL request error")]
|
||||
GetError(surf::Error),
|
||||
}
|
||||
use anyhow::format_err;
|
||||
use anyhow::Context as _;
|
||||
|
||||
pub async fn read_url(context: &Context, url: &str) -> Result<String, Error> {
|
||||
info!(context, "Requesting URL {}", url);
|
||||
|
||||
match surf::get(url).recv_string().await {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => {
|
||||
info!(context, "Can\'t read URL {}: {}", url, err);
|
||||
|
||||
Err(Error::GetError(err))
|
||||
pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||
match read_url_inner(context, url).await {
|
||||
Ok(s) => {
|
||||
info!(context, "Successfully read url {}", url);
|
||||
Ok(s)
|
||||
}
|
||||
Err(e) => {
|
||||
info!(context, "Can't read URL {}: {:#}", url, e);
|
||||
Err(format_err!("Can't read URL {}: {:#}", url, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_url_inner(context: &Context, mut url: &str) -> anyhow::Result<String> {
|
||||
let mut _temp; // For the borrow checker
|
||||
|
||||
// Follow up to 10 http-redirects
|
||||
for _i in 0..10 {
|
||||
let mut response = surf::get(url).send().await.map_err(|e| e.into_inner())?;
|
||||
if response.status().is_redirection() {
|
||||
_temp = response
|
||||
.header("location")
|
||||
.context("Redirection doesn't have a target location")?
|
||||
.last()
|
||||
.to_string();
|
||||
info!(context, "Following redirect to {}", _temp);
|
||||
url = &_temp;
|
||||
continue;
|
||||
}
|
||||
|
||||
return response.body_string().await.map_err(|e| e.into_inner());
|
||||
}
|
||||
|
||||
Err(format_err!("Followed 10 redirections"))
|
||||
}
|
||||
|
||||
@@ -296,10 +296,7 @@ impl Contact {
|
||||
.await?;
|
||||
if contact_id == ContactId::SELF {
|
||||
contact.name = stock_str::self_msg(context).await;
|
||||
contact.addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
contact.addr = context.get_configured_addr().await.unwrap_or_default();
|
||||
contact.status = context
|
||||
.get_config(Config::Selfstatus)
|
||||
.await?
|
||||
@@ -398,10 +395,8 @@ impl Contact {
|
||||
|
||||
let addr_normalized = addr_normalize(addr);
|
||||
|
||||
if let Some(addr_self) = context.get_config(Config::ConfiguredAddr).await? {
|
||||
if addr_cmp(addr_normalized, &addr_self) {
|
||||
return Ok(Some(ContactId::SELF));
|
||||
}
|
||||
if context.is_self_addr(addr_normalized).await? {
|
||||
return Ok(Some(ContactId::SELF));
|
||||
}
|
||||
let id = context
|
||||
.sql
|
||||
@@ -452,11 +447,10 @@ impl Contact {
|
||||
ensure!(origin != Origin::Unknown, "Missing valid origin");
|
||||
|
||||
let addr = addr_normalize(addr).to_string();
|
||||
let addr_self = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
// during configuration process some chats are added and addr
|
||||
// might not be configured yet
|
||||
let addr_self = context.get_configured_addr().await.unwrap_or_default();
|
||||
if addr_cmp(&addr, &addr_self) {
|
||||
return Ok((ContactId::SELF, sth_modified));
|
||||
}
|
||||
@@ -692,11 +686,7 @@ impl Contact {
|
||||
listflags: u32,
|
||||
query: Option<impl AsRef<str>>,
|
||||
) -> Result<Vec<ContactId>> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let self_addr = context.get_configured_addr().await.unwrap_or_default();
|
||||
let mut add_self = false;
|
||||
let mut ret = Vec::new();
|
||||
let flag_verified_only = (listflags & DC_GCL_VERIFIED_ONLY) != 0;
|
||||
@@ -870,7 +860,7 @@ impl Contact {
|
||||
|
||||
let mut ret = String::new();
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
|
||||
let loginparam = LoginParam::from_database(context, "configured_").await?;
|
||||
let loginparam = LoginParam::load_configured_params(context).await?;
|
||||
let peerstate = Peerstate::from_addr(context, &contact.addr).await?;
|
||||
|
||||
if let Some(peerstate) = peerstate.filter(|peerstate| {
|
||||
@@ -2163,7 +2153,7 @@ Chat-Version: 1.0
|
||||
Date: Sun, 22 Mar 2020 22:37:55 +0000
|
||||
|
||||
Hi."#;
|
||||
dc_receive_imf(&alice, mime, "Inbox", false).await?;
|
||||
dc_receive_imf(&alice, mime, false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
|
||||
let timestamp = msg.get_timestamp();
|
||||
|
||||
@@ -195,6 +195,11 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(false) = self.is_configured().await {
|
||||
warn!(self, "can not start io on a context that is not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let l = &mut *self.inner.scheduler.write().await;
|
||||
if let Err(err) = l.start(self.clone()).await {
|
||||
@@ -236,6 +241,24 @@ impl Context {
|
||||
});
|
||||
}
|
||||
|
||||
/// Emits a generic MsgsChanged event (without chat or message id)
|
||||
pub fn emit_msgs_changed_without_ids(&self) {
|
||||
self.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
}
|
||||
|
||||
/// Emits a MsgsChanged event with specified chat and message ids
|
||||
pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
|
||||
self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
|
||||
}
|
||||
|
||||
/// Emits an IncomingMsg event with specified chat and message ids
|
||||
pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
|
||||
self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
|
||||
}
|
||||
|
||||
/// Returns a receiver for emitted events.
|
||||
///
|
||||
/// Multiple emitters can be created, but note that in this case each emitted event will
|
||||
@@ -311,8 +334,8 @@ impl Context {
|
||||
|
||||
pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
|
||||
let unset = "0";
|
||||
let l = LoginParam::from_database(self, "").await?;
|
||||
let l2 = LoginParam::from_database(self, "configured_").await?;
|
||||
let l = LoginParam::load_candidate_params(self).await?;
|
||||
let l2 = LoginParam::load_configured_params(self).await?;
|
||||
let displayname = self.get_config(Config::Displayname).await?;
|
||||
let chats = get_chat_cnt(self).await? as usize;
|
||||
let unblocked_msgs = message::get_unblocked_msg_cnt(self).await as usize;
|
||||
@@ -603,11 +626,6 @@ impl Context {
|
||||
Ok(mvbox.as_deref() == Some(folder_name))
|
||||
}
|
||||
|
||||
pub async fn is_spam_folder(&self, folder_name: &str) -> Result<bool> {
|
||||
let spam = self.get_config(Config::ConfiguredSpamFolder).await?;
|
||||
Ok(spam.as_deref() == Some(folder_name))
|
||||
}
|
||||
|
||||
pub(crate) fn derive_blobdir(dbfile: &PathBuf) -> PathBuf {
|
||||
let mut blob_fname = OsString::new();
|
||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
@@ -709,9 +727,7 @@ mod tests {
|
||||
dc_create_outgoing_rfc724_mid(None, contact.get_addr())
|
||||
);
|
||||
println!("{}", msg);
|
||||
dc_receive_imf(t, msg.as_bytes(), "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(t, msg.as_bytes(), false).await.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::download::DownloadState;
|
||||
use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
|
||||
use crate::events::EventType;
|
||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||
use crate::job::{self, Action};
|
||||
use crate::imap::markseen_on_imap_table;
|
||||
use crate::location;
|
||||
use crate::log::LogExt;
|
||||
use crate::message::{
|
||||
@@ -70,7 +70,6 @@ struct AddedParts {
|
||||
pub async fn dc_receive_imf(
|
||||
context: &Context,
|
||||
imf_raw: &[u8],
|
||||
server_folder: &str,
|
||||
seen: bool,
|
||||
) -> Result<Option<ReceivedMsg>> {
|
||||
let mail = parse_mail(imf_raw).context("can't parse mail")?;
|
||||
@@ -79,16 +78,7 @@ pub async fn dc_receive_imf(
|
||||
.get_header_value(HeaderDef::MessageId)
|
||||
.and_then(|msgid| parse_message_id(&msgid).ok())
|
||||
.unwrap_or_else(dc_create_id);
|
||||
dc_receive_imf_inner(
|
||||
context,
|
||||
&rfc724_mid,
|
||||
imf_raw,
|
||||
server_folder,
|
||||
seen,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
dc_receive_imf_inner(context, &rfc724_mid, imf_raw, seen, None, false).await
|
||||
}
|
||||
|
||||
/// Receive a message and add it to the database.
|
||||
@@ -109,15 +99,11 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
context: &Context,
|
||||
rfc724_mid: &str,
|
||||
imf_raw: &[u8],
|
||||
server_folder: &str,
|
||||
seen: bool,
|
||||
is_partial_download: Option<u32>,
|
||||
fetching_existing_messages: bool,
|
||||
) -> Result<Option<ReceivedMsg>> {
|
||||
info!(
|
||||
context,
|
||||
"Receiving message, folder={}, seen={}...", server_folder, seen
|
||||
);
|
||||
info!(context, "Receiving message, seen={}...", seen);
|
||||
|
||||
if std::env::var(crate::DCC_MIME_DEBUG).unwrap_or_default() == "2" {
|
||||
info!(context, "dc_receive_imf: incoming message mime-body:");
|
||||
@@ -206,7 +192,6 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
&mut mime_parser,
|
||||
imf_raw,
|
||||
incoming,
|
||||
server_folder,
|
||||
&to_ids,
|
||||
rfc724_mid,
|
||||
sent_timestamp,
|
||||
@@ -339,32 +324,20 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
.await?;
|
||||
} else if !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version() {
|
||||
// This is a Delta Chat MDN. Mark as read.
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(
|
||||
Action::MarkseenMsgOnImap,
|
||||
insert_msg_id.to_u32(),
|
||||
Params::new(),
|
||||
0,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
markseen_on_imap_table(context, rfc724_mid).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if replace_partial_download {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id,
|
||||
});
|
||||
context.emit_msgs_changed(chat_id, MsgId::new(0));
|
||||
} else if !chat_id.is_trash() {
|
||||
let fresh = added_parts.received_msg.state == MessageState::InFresh;
|
||||
for msg_id in added_parts.created_db_entries {
|
||||
let event = if incoming && added_parts.received_msg.state == MessageState::InFresh {
|
||||
EventType::IncomingMsg { msg_id, chat_id }
|
||||
if incoming && fresh {
|
||||
context.emit_incoming_msg(chat_id, msg_id);
|
||||
} else {
|
||||
EventType::MsgsChanged { msg_id, chat_id }
|
||||
context.emit_msgs_changed(chat_id, msg_id);
|
||||
};
|
||||
context.emit_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +400,6 @@ async fn add_parts(
|
||||
mime_parser: &mut MimeMessage,
|
||||
imf_raw: &[u8],
|
||||
incoming: bool,
|
||||
server_folder: &str,
|
||||
to_ids: &[ContactId],
|
||||
rfc724_mid: &str,
|
||||
sent_timestamp: i64,
|
||||
@@ -669,7 +641,12 @@ async fn add_parts(
|
||||
let create_blocked = if from_id == ContactId::SELF {
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Request
|
||||
let contact = Contact::load_from_db(context, from_id).await?;
|
||||
if contact.is_blocked() {
|
||||
Blocked::Yes
|
||||
} else {
|
||||
Blocked::Request
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(chat) = test_normal_chat {
|
||||
@@ -741,25 +718,12 @@ async fn add_parts(
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
|
||||
// If the message is outgoing AND there is no Received header AND it's not in the sentbox,
|
||||
// then ignore the email.
|
||||
//
|
||||
// We only apply this heuristic to classical emails, as it is not reliable (some servers
|
||||
// such as systemli.org in June 2021 remove their own Received headers on incoming mails)
|
||||
// and we know Delta Chat never stores drafts on IMAP servers.
|
||||
let is_draft = !context.is_sentbox(server_folder).await?
|
||||
&& mime_parser.get_header(HeaderDef::Received).is_none()
|
||||
&& mime_parser.get_header(HeaderDef::ChatVersion).is_none();
|
||||
// Mozilla Thunderbird does not set \Draft flag on "Templates", but sets
|
||||
// X-Mozilla-Draft-Info header, which can be used to detect both drafts and templates
|
||||
// created by Thunderbird.
|
||||
//
|
||||
// This check is not necessary now, but may become useful if the `Received:` header check
|
||||
// is removed completely later.
|
||||
let is_draft = is_draft
|
||||
|| mime_parser
|
||||
.get_header(HeaderDef::XMozillaDraftInfo)
|
||||
.is_some();
|
||||
let is_draft = mime_parser
|
||||
.get_header(HeaderDef::XMozillaDraftInfo)
|
||||
.is_some();
|
||||
|
||||
if is_draft {
|
||||
// Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
|
||||
@@ -1387,7 +1351,7 @@ async fn is_probably_private_reply(
|
||||
}
|
||||
|
||||
/// This function tries to extract the group-id from the message and returns the corresponding
|
||||
/// chat_id. If the chat does not exist, it is created. If there is no group-id and there are more
|
||||
/// chat_id. If the chat does not exist, it is created. If there is no group-id and there are more
|
||||
/// than two members, a new ad hoc group is created.
|
||||
///
|
||||
/// On success the function returns the found/created (chat_id, chat_blocked) tuple.
|
||||
@@ -1451,10 +1415,7 @@ async fn create_or_lookup_group(
|
||||
ProtectionStatus::Unprotected
|
||||
};
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context("no address configured")?;
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
if chat_id.is_none()
|
||||
&& !mime_parser.is_mailinglist_message()
|
||||
&& !grpid.is_empty()
|
||||
@@ -1550,11 +1511,7 @@ async fn apply_group_changes(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context("no address configured")?;
|
||||
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
let mut recreate_member_list = false;
|
||||
let mut send_event_chat_modified = false;
|
||||
|
||||
@@ -1998,12 +1955,7 @@ async fn create_adhoc_group(
|
||||
/// are hidden in BCC. This group ID is sent by DC in the messages sent to this chat,
|
||||
/// so having the same ID prevents group split.
|
||||
async fn create_adhoc_grp_id(context: &Context, member_ids: &[ContactId]) -> Result<String> {
|
||||
let member_cs = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_else(|| "no-self".to_string())
|
||||
.to_lowercase();
|
||||
|
||||
let member_cs = context.get_configured_addr().await?.to_lowercase();
|
||||
let query = format!(
|
||||
"SELECT addr FROM contacts WHERE id IN({}) AND id!=?",
|
||||
sql::repeat_vars(member_ids.len())?
|
||||
@@ -2307,6 +2259,7 @@ mod tests {
|
||||
use crate::chat::{get_chat_msgs, ChatItem, ChatVisibility};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::constants::DC_GCL_NO_SPECIALS;
|
||||
use crate::imap::prefetch_should_download;
|
||||
use crate::message::Message;
|
||||
use crate::test_utils::{get_chat_msg, TestContext, TestContextManager};
|
||||
|
||||
@@ -2393,17 +2346,17 @@ mod tests {
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
dc_receive_imf(&t, MSGRMSG, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, MSGRMSG, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
dc_receive_imf(&t, ONETOONE_NOREPLY_MAIL, "INBOX", false)
|
||||
dc_receive_imf(&t, ONETOONE_NOREPLY_MAIL, false)
|
||||
.await
|
||||
.unwrap();
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
@@ -2412,7 +2365,7 @@ mod tests {
|
||||
async fn test_adhoc_group_show_accepted_contact_unknown() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("1")).await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, false).await.unwrap();
|
||||
|
||||
// adhoc-group with unknown contacts with show_emails=accepted is ignored for unknown contacts
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
@@ -2424,7 +2377,7 @@ mod tests {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("1")).await.unwrap();
|
||||
Contact::create(&t, "Bob", "bob@example.com").await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, false).await.unwrap();
|
||||
|
||||
// adhoc-group with known contacts with show_emails=accepted is still ignored for known contacts
|
||||
// (and existent chat is required)
|
||||
@@ -2438,7 +2391,7 @@ mod tests {
|
||||
t.set_config(Config::ShowEmails, Some("1")).await.unwrap();
|
||||
|
||||
// accept Bob by accepting a delta-message from Bob
|
||||
dc_receive_imf(&t, MSGRMSG, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, MSGRMSG, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0).unwrap();
|
||||
@@ -2459,7 +2412,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// receive a non-delta-message from Bob, shows up because of the show_emails setting
|
||||
dc_receive_imf(&t, ONETOONE_NOREPLY_MAIL, "INBOX", false)
|
||||
dc_receive_imf(&t, ONETOONE_NOREPLY_MAIL, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -2472,7 +2425,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// let Bob create an adhoc-group by a non-delta-message, shows up because of the show_emails setting
|
||||
dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 2);
|
||||
let chat_id = chats.get_chat_id(0).unwrap();
|
||||
@@ -2486,7 +2439,7 @@ mod tests {
|
||||
async fn test_adhoc_group_show_all() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, GRP_MAIL, false).await.unwrap();
|
||||
|
||||
// adhoc-group with unknown contacts with show_emails=all will show up in a single chat
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
@@ -2558,7 +2511,6 @@ mod tests {
|
||||
group.grpid, group.grpid
|
||||
)
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -2603,7 +2555,6 @@ mod tests {
|
||||
group.grpid
|
||||
)
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -2645,7 +2596,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -2674,7 +2624,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
).await.unwrap();
|
||||
assert_eq!(
|
||||
@@ -2715,7 +2664,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -2759,7 +2707,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -2879,7 +2826,6 @@ mod tests {
|
||||
self_addr, foreign_addr, rfc724_mid_outgoing
|
||||
)
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -2890,7 +2836,7 @@ mod tests {
|
||||
|
||||
// Check that the ndn would be downloaded:
|
||||
let headers = mailparse::parse_mail(raw_ndn).unwrap().headers;
|
||||
assert!(crate::imap::prefetch_should_download(
|
||||
assert!(prefetch_should_download(
|
||||
&t,
|
||||
&headers,
|
||||
"some-other-message-id",
|
||||
@@ -2900,7 +2846,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap());
|
||||
|
||||
dc_receive_imf(&t, raw_ndn, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, raw_ndn, false).await.unwrap();
|
||||
let msg = Message::load_from_db(&t, msg_id).await.unwrap();
|
||||
|
||||
assert_eq!(msg.state, MessageState::OutFailed);
|
||||
@@ -2927,7 +2873,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -2936,7 +2881,7 @@ mod tests {
|
||||
let msg_id = chats.get_msg_id(0)?.unwrap();
|
||||
|
||||
let raw = include_bytes!("../test-data/message/gmail_ndn_group.eml");
|
||||
dc_receive_imf(&t, raw, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, raw, false).await?;
|
||||
|
||||
let msg = Message::load_from_db(&t, msg_id).await?;
|
||||
|
||||
@@ -2963,9 +2908,7 @@ mod tests {
|
||||
.set_config(Config::ShowEmails, Some("2"))
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(context, imf_raw, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(context, imf_raw, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(context, 0, None, None).await.unwrap();
|
||||
let msg_id = chats.get_msg_id(0).unwrap().unwrap();
|
||||
Message::load_from_db(context, msg_id).await.unwrap()
|
||||
@@ -3009,7 +2952,7 @@ mod tests {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.ctx.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
dc_receive_imf(&t.ctx, GH_MAILINGLIST, "INBOX", false).await?;
|
||||
dc_receive_imf(&t.ctx, GH_MAILINGLIST, false).await?;
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
@@ -3023,7 +2966,7 @@ mod tests {
|
||||
assert_eq!(chat.name, "deltachat/deltachat-core-rust");
|
||||
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await?.len(), 1);
|
||||
|
||||
dc_receive_imf(&t.ctx, GH_MAILINGLIST2.as_bytes(), "INBOX", false).await?;
|
||||
dc_receive_imf(&t.ctx, GH_MAILINGLIST2.as_bytes(), false).await?;
|
||||
|
||||
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await?;
|
||||
assert!(!chat.can_send(&t.ctx).await?);
|
||||
@@ -3078,9 +3021,7 @@ mod tests {
|
||||
.set_config(Config::ShowEmails, Some("2"))
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
let chat_id = chats.get_chat_id(0).unwrap();
|
||||
chat_id.accept(&t).await.unwrap();
|
||||
@@ -3111,7 +3052,7 @@ mod tests {
|
||||
Hello mailinglist!\r\n"
|
||||
));
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", false).await?;
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, false).await?;
|
||||
|
||||
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await?;
|
||||
assert!(chat.can_send(&t.ctx).await?);
|
||||
@@ -3123,9 +3064,7 @@ Hello mailinglist!\r\n"
|
||||
async fn test_other_device_writes_to_mailinglist() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
dc_receive_imf(&t, DC_MAILINGLIST, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t, DC_MAILINGLIST, false).await.unwrap();
|
||||
let first_msg = t.get_last_msg().await;
|
||||
let first_chat = Chat::load_from_db(&t, first_msg.chat_id).await?;
|
||||
assert_eq!(
|
||||
@@ -3159,7 +3098,6 @@ Hello mailinglist!\r\n"
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
body 4\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3180,9 +3118,7 @@ Hello mailinglist!\r\n"
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0).unwrap();
|
||||
@@ -3195,7 +3131,7 @@ Hello mailinglist!\r\n"
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0); // Test that the message disappeared
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", false)
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -3213,9 +3149,7 @@ Hello mailinglist!\r\n"
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
|
||||
dc_receive_imf(&t, DC_MAILINGLIST, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t, DC_MAILINGLIST, false).await.unwrap();
|
||||
let blocked = Contact::get_all_blocked(&t).await.unwrap();
|
||||
assert_eq!(blocked.len(), 0);
|
||||
|
||||
@@ -3235,7 +3169,7 @@ Hello mailinglist!\r\n"
|
||||
let blocked = Contact::get_all_blocked(&t).await.unwrap();
|
||||
assert_eq!(blocked.len(), 0);
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", false)
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
let msg = t.get_last_msg().await;
|
||||
@@ -3251,9 +3185,7 @@ Hello mailinglist!\r\n"
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat_id = msg.get_chat_id();
|
||||
@@ -3266,7 +3198,7 @@ Hello mailinglist!\r\n"
|
||||
let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.unwrap();
|
||||
assert_eq!(msgs.len(), 1); // ...and contains 1 message
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", false)
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -3286,9 +3218,7 @@ Hello mailinglist!\r\n"
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST, false).await.unwrap();
|
||||
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat_id = msg.get_chat_id();
|
||||
@@ -3298,7 +3228,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(chats.len(), 1); // Test that the message is shown
|
||||
assert!(!chat_id.is_special());
|
||||
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", false)
|
||||
dc_receive_imf(&t.ctx, DC_MAILINGLIST2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -3325,7 +3255,6 @@ Hello mailinglist!\r\n"
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3357,7 +3286,6 @@ Hello mailinglist!\r\n"
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3386,7 +3314,6 @@ Hello mailinglist!\r\n"
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3410,7 +3337,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_dhl.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3436,7 +3362,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_dpd.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3462,7 +3387,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_xt_local_microsoft.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -3474,7 +3398,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_xt_local_spiegel.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -3494,7 +3417,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_xing.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -3516,7 +3438,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_ttline.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -3543,7 +3464,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_with_mimepart_footer.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3573,7 +3493,6 @@ Hello mailinglist!\r\n"
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_with_mimepart_footer_signed.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3599,9 +3518,7 @@ Hello mailinglist!\r\n"
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
|
||||
dc_receive_imf(&t, GH_MAILINGLIST, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t, GH_MAILINGLIST, false).await.unwrap();
|
||||
|
||||
let chat_id = t.get_last_msg().await.chat_id;
|
||||
chat_id.accept(&t).await.unwrap();
|
||||
@@ -3609,9 +3526,7 @@ Hello mailinglist!\r\n"
|
||||
assert!(chat.can_send(&t).await.unwrap());
|
||||
|
||||
let imf_raw = format!("In-Reply-To: 3333@example.org\n{}", GH_MAILINGLIST2);
|
||||
dc_receive_imf(&t, imf_raw.as_bytes(), "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&t, imf_raw.as_bytes(), false).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
t.get_last_msg().await.in_reply_to.unwrap(),
|
||||
@@ -3678,7 +3593,6 @@ YEAAAAAA!.
|
||||
addr
|
||||
)
|
||||
.as_bytes(),
|
||||
"Sent",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3735,7 +3649,6 @@ YEAAAAAA!.
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/many_images_amazon_via_apple_mail.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3768,7 +3681,6 @@ YEAAAAAA!.
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello foo\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3786,7 +3698,6 @@ YEAAAAAA!.
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
reply foo\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3835,7 +3746,6 @@ YEAAAAAA!.
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello foo\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3854,7 +3764,6 @@ YEAAAAAA!.
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
classic reply\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3881,7 +3790,6 @@ YEAAAAAA!.
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
chat reply\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3909,7 +3817,6 @@ YEAAAAAA!.
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
private reply\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4024,7 +3931,7 @@ YEAAAAAA!.
|
||||
.set_config(Config::ShowEmails, Some("2"))
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&alice, claire_request.as_bytes(), "INBOX", false)
|
||||
dc_receive_imf(&alice, claire_request.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -4050,7 +3957,7 @@ YEAAAAAA!.
|
||||
.set_config(Config::ShowEmails, Some("2"))
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&claire, claire_request.as_bytes(), "INBOX", false)
|
||||
dc_receive_imf(&claire, claire_request.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -4086,7 +3993,7 @@ YEAAAAAA!.
|
||||
|
||||
// Check that Alice gets the message in the same chat.
|
||||
let request = alice.get_last_msg().await;
|
||||
dc_receive_imf(&alice, reply, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&alice, reply, false).await.unwrap();
|
||||
let answer = alice.get_last_msg().await;
|
||||
assert_eq!(answer.get_subject(), "Re: i have a question");
|
||||
assert!(answer.get_text().unwrap().contains("the version is 1.0"));
|
||||
@@ -4109,9 +4016,7 @@ YEAAAAAA!.
|
||||
|
||||
// Check that Claire also gets the message in the same chat.
|
||||
let request = claire.get_last_msg().await;
|
||||
dc_receive_imf(&claire, reply, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(&claire, reply, false).await.unwrap();
|
||||
let answer = claire.get_last_msg().await;
|
||||
assert_eq!(answer.get_subject(), "Re: i have a question");
|
||||
assert!(answer.get_text().unwrap().contains("the version is 1.0"));
|
||||
@@ -4179,7 +4084,6 @@ YEAAAAAA!.
|
||||
Message-ID: <4444@example.org>\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4206,7 +4110,6 @@ YEAAAAAA!.
|
||||
In-Reply-To: <4444@example.org\n\
|
||||
\n\
|
||||
Reply\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4234,7 +4137,6 @@ To: <me@other.maildomain.com>
|
||||
From: <alice@example.org>
|
||||
|
||||
Message content",
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4264,7 +4166,6 @@ To: <bob@example.org>
|
||||
From: <alice@example.org>
|
||||
|
||||
Message content",
|
||||
"Sent",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4313,18 +4214,18 @@ Message content
|
||||
--
|
||||
Second signature";
|
||||
|
||||
dc_receive_imf(&alice, first_message, "Inbox", false).await?;
|
||||
dc_receive_imf(&alice, first_message, false).await?;
|
||||
let contact = Contact::load_from_db(&alice, bob_contact_id).await?;
|
||||
assert_eq!(contact.get_status(), "First signature");
|
||||
assert_eq!(contact.get_display_name(), "Bob1");
|
||||
|
||||
dc_receive_imf(&alice, second_message, "Inbox", false).await?;
|
||||
dc_receive_imf(&alice, second_message, false).await?;
|
||||
let contact = Contact::load_from_db(&alice, bob_contact_id).await?;
|
||||
assert_eq!(contact.get_status(), "Second signature");
|
||||
assert_eq!(contact.get_display_name(), "Bob2");
|
||||
|
||||
// Duplicate message, should be ignored
|
||||
dc_receive_imf(&alice, first_message, "Inbox", false).await?;
|
||||
dc_receive_imf(&alice, first_message, false).await?;
|
||||
|
||||
// No change because last message is duplicate of the first.
|
||||
let contact = Contact::load_from_db(&alice, bob_contact_id).await?;
|
||||
@@ -4364,7 +4265,6 @@ Message-ID: <Gr.eJ_llQIXf0K.buxmrnMmG0Y@gmx.de>"
|
||||
}
|
||||
)
|
||||
.as_bytes(),
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4406,7 +4306,6 @@ Private reply"#,
|
||||
}
|
||||
)
|
||||
.as_bytes(),
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4457,7 +4356,6 @@ Message-ID: <Gr.iy1KCE2y65_.mH2TM52miv9@testrun.org>"
|
||||
}
|
||||
)
|
||||
.as_bytes(),
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4504,7 +4402,6 @@ Sent with my Delta Chat Messenger: https://delta.chat
|
||||
}
|
||||
)
|
||||
.as_bytes(),
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4547,7 +4444,6 @@ Message-ID: <Gr.eJ_llQIXf0K.buxmrnMmG0Y@gmx.de>"
|
||||
}
|
||||
)
|
||||
.as_bytes(),
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4586,7 +4482,6 @@ Outgoing reply to all"#,
|
||||
}
|
||||
)
|
||||
.as_bytes(),
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4610,7 +4505,6 @@ MIME-Version: 1.0
|
||||
In-Reply-To: <outgoing@testrun.org>
|
||||
|
||||
Reply to all"#,
|
||||
"Inbox",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -4653,15 +4547,15 @@ Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
Second thread."#;
|
||||
|
||||
// Alice receives two classic emails from Claire.
|
||||
dc_receive_imf(&alice, first_thread_mime, "Inbox", false).await?;
|
||||
dc_receive_imf(&alice, first_thread_mime, false).await?;
|
||||
let alice_first_msg = alice.get_last_msg().await;
|
||||
dc_receive_imf(&alice, second_thread_mime, "Inbox", false).await?;
|
||||
dc_receive_imf(&alice, second_thread_mime, false).await?;
|
||||
let alice_second_msg = alice.get_last_msg().await;
|
||||
|
||||
// Bob receives the same two emails.
|
||||
dc_receive_imf(&bob, first_thread_mime, "Inbox", false).await?;
|
||||
dc_receive_imf(&bob, first_thread_mime, false).await?;
|
||||
let bob_first_msg = bob.get_last_msg().await;
|
||||
dc_receive_imf(&bob, second_thread_mime, "Inbox", false).await?;
|
||||
dc_receive_imf(&bob, second_thread_mime, false).await?;
|
||||
let bob_second_msg = bob.get_last_msg().await;
|
||||
|
||||
// Messages go to separate chats both for Alice and Bob.
|
||||
@@ -4714,7 +4608,7 @@ Second thread."#;
|
||||
let mdn_body = rendered_mdn.message;
|
||||
|
||||
// Alice receives the read receipt.
|
||||
dc_receive_imf(&alice, mdn_body.as_bytes(), "INBOX", false).await?;
|
||||
dc_receive_imf(&alice, mdn_body.as_bytes(), false).await?;
|
||||
|
||||
// Chat should not pop up in the chatlist.
|
||||
let chats = Chatlist::try_load(&alice, 0, None, None).await?;
|
||||
@@ -4731,7 +4625,6 @@ Second thread."#;
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/gmx-forward.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -4748,7 +4641,7 @@ Second thread."#;
|
||||
async fn test_incoming_contact_request() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
dc_receive_imf(&t, MSGRMSG, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, MSGRMSG, false).await?;
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat = chat::Chat::load_from_db(&t, msg.chat_id).await?;
|
||||
assert!(chat.is_contact_request());
|
||||
@@ -4781,7 +4674,7 @@ From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
dc_receive_imf(&t, mime, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, mime, false).await?;
|
||||
let first = t.get_last_msg().await;
|
||||
let mime = br#"Subject: Second
|
||||
Message-ID: second@example.net
|
||||
@@ -4790,7 +4683,7 @@ From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
dc_receive_imf(&t, mime, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, mime, false).await?;
|
||||
let second = t.get_last_msg().await;
|
||||
let mime = br#"Subject: Third
|
||||
Message-ID: third@example.net
|
||||
@@ -4799,7 +4692,7 @@ From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
dc_receive_imf(&t, mime, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, mime, false).await?;
|
||||
let third = t.get_last_msg().await;
|
||||
|
||||
let mime = br#"Subject: Message with references.
|
||||
@@ -4850,7 +4743,7 @@ Message with references."#;
|
||||
|
||||
// Alice sends a message to Bob using Thunderbird.
|
||||
let raw = include_bytes!("../test-data/message/rfc1847_encapsulation.eml");
|
||||
dc_receive_imf(&bob, raw, "INBOX", false).await?;
|
||||
dc_receive_imf(&bob, raw, false).await?;
|
||||
|
||||
let msg = bob.get_last_msg().await;
|
||||
assert!(msg.get_showpadlock());
|
||||
@@ -4865,7 +4758,7 @@ Message with references."#;
|
||||
let mime = include_bytes!("../test-data/message/invalid_email_to.eml");
|
||||
|
||||
// dc_receive_imf should not fail on this mail with invalid To: field
|
||||
dc_receive_imf(&alice, mime, "Inbox", false).await?;
|
||||
dc_receive_imf(&alice, mime, false).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4891,7 +4784,6 @@ From: <alice@example.org>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hi, I created a group"#,
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -4916,7 +4808,6 @@ To: holger <alice@example.org>
|
||||
|
||||
Reply from different address
|
||||
",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -5116,4 +5007,70 @@ Reply from different address
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_no_private_reply_to_blocked_account() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
// =============== Bob creates a group ===============
|
||||
let group_id =
|
||||
chat::create_group_chat(&bob, ProtectionStatus::Unprotected, "Group").await?;
|
||||
chat::add_to_chat_contacts_table(
|
||||
&bob,
|
||||
group_id,
|
||||
bob.add_or_lookup_contact(&alice).await.id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// =============== Bob sends the first message to the group ===============
|
||||
let sent = bob.send_text(group_id, "Hello all!").await;
|
||||
alice.recv_msg(&sent).await;
|
||||
|
||||
let chats = Chatlist::try_load(&bob, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
// =============== Bob blocks Alice ================
|
||||
Contact::block(&bob, bob.add_or_lookup_contact(&alice).await.id).await?;
|
||||
|
||||
// =============== Alice replies private to Bob ==============
|
||||
let received = alice.get_last_msg().await;
|
||||
assert_eq!(received.text, Some("Hello all!".to_string()));
|
||||
|
||||
let received_group = Chat::load_from_db(&alice, received.chat_id).await?;
|
||||
assert_eq!(received_group.typ, Chattype::Group);
|
||||
|
||||
let mut msg_out = Message::new(Viewtype::Text);
|
||||
msg_out.set_text(Some("Private reply".to_string()));
|
||||
msg_out.set_quote(&alice, Some(&received)).await?;
|
||||
|
||||
let alice_bob_chat = alice.create_chat(&bob).await;
|
||||
let sent2 = alice.send_msg(alice_bob_chat.id, &mut msg_out).await;
|
||||
bob.recv_msg(&sent2).await;
|
||||
|
||||
// ========= check that no contact request was created ============
|
||||
let chats = Chatlist::try_load(&bob, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0).unwrap();
|
||||
let chat = Chat::load_from_db(&bob, chat_id).await.unwrap();
|
||||
|
||||
// since only chat is a group, no new open chat has been created
|
||||
assert_eq!(chat.typ, Chattype::Group);
|
||||
let received = bob.get_last_msg().await;
|
||||
assert_eq!(received.text, Some("Hello all!".to_string()));
|
||||
|
||||
// =============== Bob unblocks Alice ================
|
||||
// test if the blocked chat is restored correctly
|
||||
Contact::unblock(&bob, bob.add_or_lookup_contact(&alice).await.id).await?;
|
||||
let chats = Chatlist::try_load(&bob, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 2);
|
||||
let chat_id = chats.get_chat_id(0).unwrap();
|
||||
let chat = Chat::load_from_db(&bob, chat_id).await.unwrap();
|
||||
assert_eq!(chat.typ, Chattype::Single);
|
||||
let received = bob.get_last_msg().await;
|
||||
assert_eq!(received.text, Some("Private reply".to_string()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(crate) fn dc_gm2local_offset() -> i64 {
|
||||
// `last_smeared_timestamp` is again in sync with the normal time.
|
||||
// - however, we do not do all this for the far future,
|
||||
// but at max `MAX_SECONDS_TO_LEND_FROM_FUTURE`
|
||||
const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 5;
|
||||
pub(crate) const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 5;
|
||||
|
||||
/// Returns the current smeared timestamp,
|
||||
///
|
||||
@@ -214,7 +214,10 @@ pub(crate) fn dc_create_id() -> String {
|
||||
rng.fill(&mut arr[..]);
|
||||
|
||||
// Take 11 base64 characters containing 66 random bits.
|
||||
base64::encode(&arr).chars().take(11).collect()
|
||||
base64::encode_config(&arr, base64::URL_SAFE)
|
||||
.chars()
|
||||
.take(11)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Function generates a Message-ID that can be used for a new outgoing message.
|
||||
@@ -700,7 +703,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
async fn check_parse_receive_headers_integration(raw: &[u8], expected: &str) {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
dc_receive_imf(&t, raw, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&t, raw, false).await.unwrap();
|
||||
let msg = t.get_last_msg().await;
|
||||
let msg_info = get_msg_info(&t, msg.id).await.unwrap();
|
||||
|
||||
@@ -762,6 +765,15 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
assert_eq!(buf.len(), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_create_id_invalid_chars() {
|
||||
for _ in 1..1000 {
|
||||
let buf = dc_create_id();
|
||||
assert!(!buf.contains('/')); // `/` must not be used to be URL-safe
|
||||
assert!(!buf.contains('.')); // `.` is used as a delimiter when extracting grpid from Message-ID
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_extract_grpid_from_rfc724_mid() {
|
||||
// Should return None if we pass invalid mid
|
||||
|
||||
@@ -346,7 +346,6 @@ mod tests {
|
||||
&t,
|
||||
"Mr.12345678901@example.com",
|
||||
header.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
Some(100000),
|
||||
false,
|
||||
@@ -364,7 +363,6 @@ mod tests {
|
||||
&t,
|
||||
"Mr.12345678901@example.com",
|
||||
format!("{}\n\n100k text...", header).as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
None,
|
||||
false,
|
||||
@@ -400,7 +398,6 @@ mod tests {
|
||||
Message-ID: <first@example.org>\n\
|
||||
Date: Sun, 14 Nov 2021 00:10:00 +0000\
|
||||
Content-Type: text/plain",
|
||||
"INBOX",
|
||||
false,
|
||||
Some(100000),
|
||||
false,
|
||||
|
||||
18
src/e2ee.rs
18
src/e2ee.rs
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
use anyhow::{format_err, Context as _, Result};
|
||||
use mailparse::ParsedMail;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
@@ -28,13 +28,7 @@ impl EncryptHelper {
|
||||
let prefer_encrypt =
|
||||
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await?)
|
||||
.unwrap_or_default();
|
||||
let addr = match context.get_config(Config::ConfiguredAddr).await? {
|
||||
None => {
|
||||
bail!("addr not configured!");
|
||||
}
|
||||
Some(addr) => addr,
|
||||
};
|
||||
|
||||
let addr = context.get_configured_addr().await?;
|
||||
let public_key = SignedPublicKey::load_self(context).await?;
|
||||
|
||||
Ok(EncryptHelper {
|
||||
@@ -387,13 +381,7 @@ fn contains_report(mail: &ParsedMail<'_>) -> bool {
|
||||
/// [Config::ConfiguredAddr] is configured, this address is returned.
|
||||
// TODO, remove this once deltachat::key::Key no longer exists.
|
||||
pub async fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context(concat!(
|
||||
"Failed to get self address, ",
|
||||
"cannot ensure secret key if not configured."
|
||||
))?;
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
SignedPublicKey::load_self(context).await?;
|
||||
Ok(self_addr)
|
||||
}
|
||||
|
||||
193
src/ephemeral.rs
193
src/ephemeral.rs
@@ -399,37 +399,90 @@ WHERE
|
||||
}
|
||||
|
||||
if updated {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn ephemeral_loop(context: &Context, interrupt_receiver: Receiver<()>) {
|
||||
loop {
|
||||
let ephemeral_timestamp: Option<i64> = match context
|
||||
/// Calculates the next timestamp when a message will be deleted due to
|
||||
/// `delete_device_after` setting being set.
|
||||
async fn next_delete_device_after_timestamp(context: &Context) -> Result<Option<i64>> {
|
||||
if let Some(delete_device_after) = context.get_config_delete_device_after().await? {
|
||||
let self_chat_id = ChatId::lookup_by_contact(context, ContactId::SELF)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let device_chat_id = ChatId::lookup_by_contact(context, ContactId::DEVICE)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let oldest_message_timestamp: Option<i64> = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
r#"
|
||||
SELECT min(ephemeral_timestamp)
|
||||
SELECT min(timestamp)
|
||||
FROM msgs
|
||||
WHERE ephemeral_timestamp != 0
|
||||
WHERE chat_id > ?
|
||||
AND chat_id != ?
|
||||
AND chat_id != ?;
|
||||
"#,
|
||||
paramsv![DC_CHAT_ID_TRASH], // Trash contains already deleted messages, skip them
|
||||
paramsv![DC_CHAT_ID_TRASH, self_chat_id, device_chat_id],
|
||||
)
|
||||
.await
|
||||
{
|
||||
.await?;
|
||||
|
||||
Ok(oldest_message_timestamp.map(|x| x.saturating_add(delete_device_after)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates next timestamp when expiration of some message will happen.
|
||||
///
|
||||
/// Expiration can happen either because user has set `delete_device_after` setting or because the
|
||||
/// message itself has an ephemeral timer.
|
||||
async fn next_expiration_timestamp(context: &Context) -> Option<i64> {
|
||||
let ephemeral_timestamp: Option<i64> = match context
|
||||
.sql
|
||||
.query_get_value(
|
||||
r#"
|
||||
SELECT min(ephemeral_timestamp)
|
||||
FROM msgs
|
||||
WHERE ephemeral_timestamp != 0
|
||||
AND chat_id != ?;
|
||||
"#,
|
||||
paramsv![DC_CHAT_ID_TRASH], // Trash contains already deleted messages, skip them
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
warn!(context, "Can't calculate next ephemeral timeout: {}", err);
|
||||
None
|
||||
}
|
||||
Ok(ephemeral_timestamp) => ephemeral_timestamp,
|
||||
};
|
||||
|
||||
let delete_device_after_timestamp: Option<i64> =
|
||||
match next_delete_device_after_timestamp(context).await {
|
||||
Err(err) => {
|
||||
warn!(context, "Can't calculate next ephemeral timeout: {}", err);
|
||||
warn!(
|
||||
context,
|
||||
"Can't calculate timestamp of the next message expiration: {}", err
|
||||
);
|
||||
None
|
||||
}
|
||||
Ok(ephemeral_timestamp) => ephemeral_timestamp,
|
||||
Ok(timestamp) => timestamp,
|
||||
};
|
||||
|
||||
ephemeral_timestamp
|
||||
.into_iter()
|
||||
.chain(delete_device_after_timestamp.into_iter())
|
||||
.min()
|
||||
}
|
||||
|
||||
pub(crate) async fn ephemeral_loop(context: &Context, interrupt_receiver: Receiver<()>) {
|
||||
loop {
|
||||
let ephemeral_timestamp = next_expiration_timestamp(context).await;
|
||||
|
||||
let now = SystemTime::now();
|
||||
let until = if let Some(ephemeral_timestamp) = ephemeral_timestamp {
|
||||
UNIX_EPOCH
|
||||
@@ -524,6 +577,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::dc_tools::MAX_SECONDS_TO_LEND_FROM_FUTURE;
|
||||
use crate::download::DownloadState;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::{
|
||||
@@ -812,44 +866,106 @@ mod tests {
|
||||
#[async_std::test]
|
||||
async fn test_ephemeral_delete_msgs() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat = t.get_self_chat().await;
|
||||
let self_chat = t.get_self_chat().await;
|
||||
|
||||
t.send_text(chat.id, "Saved message, which we delete manually")
|
||||
assert_eq!(next_expiration_timestamp(&t).await, None);
|
||||
|
||||
t.send_text(self_chat.id, "Saved message, which we delete manually")
|
||||
.await;
|
||||
let msg = t.get_last_msg_in(chat.id).await;
|
||||
let msg = t.get_last_msg_in(self_chat.id).await;
|
||||
msg.id.delete_from_db(&t).await?;
|
||||
check_msg_was_deleted(&t, &chat, msg.id).await;
|
||||
check_msg_is_deleted(&t, &self_chat, msg.id).await;
|
||||
|
||||
chat.id
|
||||
.set_ephemeral_timer(&t, Timer::Enabled { duration: 60 })
|
||||
self_chat
|
||||
.id
|
||||
.set_ephemeral_timer(&t, Timer::Enabled { duration: 3600 })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send a saved message which will be deleted after 3600s
|
||||
let now = time();
|
||||
let msg = t
|
||||
.send_text(chat.id, "Saved message, disappearing after 60s")
|
||||
.await;
|
||||
let msg = t.send_text(self_chat.id, "Message text").await;
|
||||
|
||||
delete_expired_messages(&t, now + 59).await?;
|
||||
let loaded = Message::load_from_db(&t, msg.sender_msg_id).await?;
|
||||
assert_eq!(
|
||||
loaded.text.unwrap(),
|
||||
"Saved message, disappearing after 60s"
|
||||
);
|
||||
assert_eq!(loaded.chat_id, chat.id);
|
||||
check_msg_will_be_deleted(&t, msg.sender_msg_id, &self_chat, now + 3599, time() + 3601)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
delete_expired_messages(&t, time() + 61).await?;
|
||||
let loaded = Message::load_from_db(&t, msg.sender_msg_id).await?;
|
||||
assert_eq!(loaded.text.unwrap(), "");
|
||||
assert_eq!(loaded.chat_id, DC_CHAT_ID_TRASH);
|
||||
// Set DeleteDeviceAfter to 1800s. Thend send a saved message which will
|
||||
// still be deleted after 3600s because DeleteDeviceAfter doesn't apply to saved messages.
|
||||
t.set_config(Config::DeleteDeviceAfter, Some("1800"))
|
||||
.await?;
|
||||
|
||||
// Check that the msg was deleted locally.
|
||||
check_msg_was_deleted(&t, &chat, msg.sender_msg_id).await;
|
||||
let now = time();
|
||||
let msg = t.send_text(self_chat.id, "Message text").await;
|
||||
|
||||
check_msg_will_be_deleted(&t, msg.sender_msg_id, &self_chat, now + 3559, time() + 3601)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send a message to Bob which will be deleted after 1800s because of DeleteDeviceAfter.
|
||||
let bob_chat = t.create_chat_with_contact("", "bob@example.net").await;
|
||||
let now = time();
|
||||
let msg = t.send_text(bob_chat.id, "Message text").await;
|
||||
|
||||
check_msg_will_be_deleted(
|
||||
&t,
|
||||
msg.sender_msg_id,
|
||||
&bob_chat,
|
||||
now + 1799,
|
||||
// The message may appear to be sent MAX_SECONDS_TO_LEND_FROM_FUTURE later and
|
||||
// therefore be deleted MAX_SECONDS_TO_LEND_FROM_FUTURE later.
|
||||
time() + 1801 + MAX_SECONDS_TO_LEND_FROM_FUTURE,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Enable ephemeral messages with Bob -> message will be deleted after 60s.
|
||||
// This tests that the message is deleted at min(ephemeral deletion time, DeleteDeviceAfter deletion time).
|
||||
bob_chat
|
||||
.id
|
||||
.set_ephemeral_timer(&t, Timer::Enabled { duration: 60 })
|
||||
.await?;
|
||||
|
||||
let now = time();
|
||||
let msg = t.send_text(bob_chat.id, "Message text").await;
|
||||
|
||||
check_msg_will_be_deleted(&t, msg.sender_msg_id, &bob_chat, now + 59, time() + 61)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_msg_was_deleted(t: &TestContext, chat: &Chat, msg_id: MsgId) {
|
||||
async fn check_msg_will_be_deleted(
|
||||
t: &TestContext,
|
||||
msg_id: MsgId,
|
||||
chat: &Chat,
|
||||
not_deleted_at: i64,
|
||||
deleted_at: i64,
|
||||
) -> Result<()> {
|
||||
let next_expiration = next_expiration_timestamp(t).await.unwrap();
|
||||
|
||||
assert!(next_expiration > not_deleted_at);
|
||||
delete_expired_messages(t, not_deleted_at).await?;
|
||||
|
||||
let loaded = Message::load_from_db(t, msg_id).await?;
|
||||
assert_eq!(loaded.text.unwrap(), "Message text");
|
||||
assert_eq!(loaded.chat_id, chat.id);
|
||||
|
||||
assert!(next_expiration < deleted_at);
|
||||
delete_expired_messages(t, deleted_at).await?;
|
||||
|
||||
let loaded = Message::load_from_db(t, msg_id).await?;
|
||||
assert_eq!(loaded.text.unwrap(), "");
|
||||
assert_eq!(loaded.chat_id, DC_CHAT_ID_TRASH);
|
||||
|
||||
// Check that the msg was deleted locally.
|
||||
check_msg_is_deleted(t, chat, msg_id).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_msg_is_deleted(t: &TestContext, chat: &Chat, msg_id: MsgId) {
|
||||
let chat_items = chat::get_chat_msgs(t, chat.id, 0, None).await.unwrap();
|
||||
// Check that the chat is empty except for possibly info messages:
|
||||
for item in &chat_items {
|
||||
@@ -999,7 +1115,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 00:10:00 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -1020,7 +1135,6 @@ mod tests {
|
||||
Ephemeral-Timer: 60\n\
|
||||
\n\
|
||||
second message\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -1057,7 +1171,6 @@ mod tests {
|
||||
In-Reply-To: <first@example.com>\n\
|
||||
\n\
|
||||
> hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::chat::ChatId;
|
||||
use crate::contact::ContactId;
|
||||
use crate::ephemeral::Timer as EphemeralTimer;
|
||||
use crate::message::MsgId;
|
||||
use crate::webxdc::StatusUpdateSerial;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Events {
|
||||
@@ -335,5 +336,8 @@ pub enum EventType {
|
||||
SelfavatarChanged,
|
||||
|
||||
#[strum(props(id = "2120"))]
|
||||
WebxdcStatusUpdate(MsgId),
|
||||
WebxdcStatusUpdate {
|
||||
msg_id: MsgId,
|
||||
status_update_serial: StatusUpdateSerial,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
.create_chat_with_contact("", "sender@testrun.org")
|
||||
.await;
|
||||
let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
|
||||
dc_receive_imf(&alice, raw, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&alice, raw, false).await.unwrap();
|
||||
let msg = alice.get_last_msg_in(chat.get_id()).await;
|
||||
assert_ne!(msg.get_from_id(), ContactId::SELF);
|
||||
assert_eq!(msg.is_dc_message, MessengerMessage::No);
|
||||
@@ -489,7 +489,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
.create_chat_with_contact("", "sender@testrun.org")
|
||||
.await;
|
||||
let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
|
||||
dc_receive_imf(&alice, raw, "INBOX", false).await.unwrap();
|
||||
dc_receive_imf(&alice, raw, false).await.unwrap();
|
||||
let msg = alice.get_last_msg_in(chat.get_id()).await;
|
||||
|
||||
// forward the message to saved-messages,
|
||||
@@ -555,7 +555,6 @@ test some special html-characters as < > and & but also " and &#x
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/cp1252-html.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
291
src/imap.rs
291
src/imap.rs
@@ -7,6 +7,7 @@ use std::{
|
||||
cmp,
|
||||
cmp::max,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
iter::Peekable,
|
||||
};
|
||||
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
@@ -31,14 +32,13 @@ use crate::dc_receive_imf::{
|
||||
use crate::dc_tools::dc_create_id;
|
||||
use crate::events::EventType;
|
||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||
use crate::job::{self, Action};
|
||||
use crate::job;
|
||||
use crate::login_param::{
|
||||
CertificateChecks, LoginParam, ServerAddress, ServerLoginParam, Socks5Config,
|
||||
};
|
||||
use crate::message::{self, Message, MessageState, MessengerMessage, MsgId, Viewtype};
|
||||
use crate::mimeparser;
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::param::Params;
|
||||
use crate::provider::Socket;
|
||||
use crate::scheduler::connectivity::ConnectivityStore;
|
||||
use crate::scheduler::InterruptInfo;
|
||||
@@ -131,7 +131,7 @@ impl FolderMeaning {
|
||||
fn to_config(self) -> Option<Config> {
|
||||
match self {
|
||||
FolderMeaning::Unknown => None,
|
||||
FolderMeaning::Spam => Some(Config::ConfiguredSpamFolder),
|
||||
FolderMeaning::Spam => None,
|
||||
FolderMeaning::Sent => Some(Config::ConfiguredSentboxFolder),
|
||||
FolderMeaning::Drafts => None,
|
||||
FolderMeaning::Other => None,
|
||||
@@ -165,6 +165,67 @@ struct ImapConfig {
|
||||
pub can_condstore: bool,
|
||||
}
|
||||
|
||||
struct UidGrouper<T: Iterator<Item = (i64, u32, String)>> {
|
||||
inner: Peekable<T>,
|
||||
}
|
||||
|
||||
impl<T, I> From<I> for UidGrouper<T>
|
||||
where
|
||||
T: Iterator<Item = (i64, u32, String)>,
|
||||
I: IntoIterator<IntoIter = T>,
|
||||
{
|
||||
fn from(inner: I) -> Self {
|
||||
Self {
|
||||
inner: inner.into_iter().peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Iterator<Item = (i64, u32, String)>> Iterator for UidGrouper<T> {
|
||||
// Tuple of folder, row IDs, and UID range as a string.
|
||||
type Item = (String, Vec<i64>, String);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (_, _, folder) = self.inner.peek().cloned()?;
|
||||
|
||||
let mut uid_set = String::new();
|
||||
let mut rowid_set = Vec::new();
|
||||
|
||||
while uid_set.len() < 1000 {
|
||||
// Construct a new range.
|
||||
if let Some((start_rowid, start_uid, _)) = self
|
||||
.inner
|
||||
.next_if(|(_, _, start_folder)| start_folder == &folder)
|
||||
{
|
||||
rowid_set.push(start_rowid);
|
||||
let mut end_uid = start_uid;
|
||||
|
||||
while let Some((next_rowid, next_uid, _)) =
|
||||
self.inner.next_if(|(_, next_uid, next_folder)| {
|
||||
next_folder == &folder && *next_uid == end_uid + 1
|
||||
})
|
||||
{
|
||||
end_uid = next_uid;
|
||||
rowid_set.push(next_rowid);
|
||||
}
|
||||
|
||||
let uid_range = UidRange {
|
||||
start: start_uid,
|
||||
end: end_uid,
|
||||
};
|
||||
if !uid_set.is_empty() {
|
||||
uid_set.push(',');
|
||||
}
|
||||
uid_set.push_str(&uid_range.to_string());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Some((folder, rowid_set, uid_set))
|
||||
}
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
/// Creates new disconnected IMAP client using the specific login parameters.
|
||||
///
|
||||
@@ -224,7 +285,7 @@ impl Imap {
|
||||
bail!("IMAP Connect without configured params");
|
||||
}
|
||||
|
||||
let param = LoginParam::from_database(context, "configured_").await?;
|
||||
let param = LoginParam::load_configured_params(context).await?;
|
||||
// the trailing underscore is correct
|
||||
|
||||
let imap = Self::new(
|
||||
@@ -451,16 +512,29 @@ impl Imap {
|
||||
///
|
||||
/// Prefetches headers and downloads new message from the folder, moves messages away from the
|
||||
/// folder and deletes messages in the folder.
|
||||
pub async fn fetch_move_delete(&mut self, context: &Context, watch_folder: &str) -> Result<()> {
|
||||
pub async fn fetch_move_delete(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
watch_folder: &str,
|
||||
is_spam_folder: bool,
|
||||
) -> Result<()> {
|
||||
if !context.sql.is_open().await {
|
||||
// probably shutdown
|
||||
bail!("IMAP operation attempted while it is torn down");
|
||||
}
|
||||
self.prepare(context).await?;
|
||||
|
||||
self.fetch_new_messages(context, watch_folder, false)
|
||||
let msgs_fetched = self
|
||||
.fetch_new_messages(context, watch_folder, is_spam_folder, false)
|
||||
.await
|
||||
.context("fetch_new_messages")?;
|
||||
if msgs_fetched && context.get_config_delete_device_after().await?.is_some() {
|
||||
// New messages were fetched and shall be deleted later, restart ephemeral loop.
|
||||
// Note that the `Config::DeleteDeviceAfter` timer starts as soon as the messages are
|
||||
// fetched while the per-chat ephemeral timers start as soon as the messages are marked
|
||||
// as noticed.
|
||||
context.interrupt_ephemeral_task().await;
|
||||
}
|
||||
|
||||
self.move_delete_messages(context, watch_folder)
|
||||
.await
|
||||
@@ -658,13 +732,17 @@ impl Imap {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Fetches new messages.
|
||||
///
|
||||
/// Returns true if at least one message was fetched.
|
||||
pub(crate) async fn fetch_new_messages(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
is_spam_folder: bool,
|
||||
fetch_existing_msgs: bool,
|
||||
) -> Result<bool> {
|
||||
if should_ignore_folder(context, folder).await? {
|
||||
if should_ignore_folder(context, folder, is_spam_folder).await? {
|
||||
info!(context, "Not fetching from {}", folder);
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -707,7 +785,7 @@ impl Imap {
|
||||
// Get the Message-ID or generate a fake one to identify the message in the database.
|
||||
let message_id = prefetch_get_message_id(&headers).unwrap_or_else(dc_create_id);
|
||||
|
||||
let target = match target_folder(context, folder, &headers).await? {
|
||||
let target = match target_folder(context, folder, is_spam_folder, &headers).await? {
|
||||
Some(config) => match context.get_config(config).await? {
|
||||
Some(target) => target,
|
||||
None => folder.to_string(),
|
||||
@@ -738,7 +816,7 @@ impl Imap {
|
||||
// If the sender is known, the message will be moved to the Inbox or Mvbox
|
||||
// and then we download the message from there.
|
||||
// Also see `spam_target_folder()`.
|
||||
&& !context.is_spam_folder(folder).await?
|
||||
&& !is_spam_folder
|
||||
&& prefetch_should_download(
|
||||
context,
|
||||
&headers,
|
||||
@@ -933,7 +1011,7 @@ impl Imap {
|
||||
///
|
||||
/// This is the only place where messages are moved or deleted on the IMAP server.
|
||||
async fn move_delete_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
|
||||
let mut rows = context
|
||||
let rows = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, uid, target FROM imap
|
||||
@@ -949,48 +1027,12 @@ impl Imap {
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.peekable();
|
||||
.await?;
|
||||
|
||||
self.prepare(context).await?;
|
||||
self.select_folder(context, Some(folder)).await?;
|
||||
|
||||
while let Some((_, _, target)) = rows.peek().cloned() {
|
||||
// Construct next request for the target folder.
|
||||
let mut uid_set = String::new();
|
||||
let mut rowid_set = Vec::new();
|
||||
|
||||
while uid_set.len() < 1000 {
|
||||
// Construct a new range.
|
||||
if let Some((start_rowid, start_uid, _)) =
|
||||
rows.next_if(|(_, _, start_target)| start_target == &target)
|
||||
{
|
||||
rowid_set.push(start_rowid);
|
||||
let mut end_uid = start_uid;
|
||||
|
||||
while let Some((next_rowid, next_uid, _)) =
|
||||
rows.next_if(|(_, next_uid, next_target)| {
|
||||
next_target == &target && *next_uid == end_uid + 1
|
||||
})
|
||||
{
|
||||
end_uid = next_uid;
|
||||
rowid_set.push(next_rowid);
|
||||
}
|
||||
|
||||
let uid_range = UidRange {
|
||||
start: start_uid,
|
||||
end: end_uid,
|
||||
};
|
||||
if !uid_set.is_empty() {
|
||||
uid_set.push(',');
|
||||
}
|
||||
uid_set.push_str(&uid_range.to_string());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (target, rowid_set, uid_set) in UidGrouper::from(rows) {
|
||||
// Empty target folder name means messages should be deleted.
|
||||
if target.is_empty() {
|
||||
self.delete_message_batch(context, &uid_set, rowid_set)
|
||||
@@ -1017,6 +1059,62 @@ impl Imap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores pending `\Seen` flags for messages in `imap_markseen` table.
|
||||
pub(crate) async fn store_seen_flags_on_imap(&mut self, context: &Context) -> Result<()> {
|
||||
self.prepare(context).await?;
|
||||
|
||||
let rows = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT imap.id, uid, folder FROM imap, imap_markseen
|
||||
WHERE imap.id = imap_markseen.id AND target = folder
|
||||
ORDER BY folder, uid",
|
||||
[],
|
||||
|row| {
|
||||
let rowid: i64 = row.get(0)?;
|
||||
let uid: u32 = row.get(1)?;
|
||||
let folder: String = row.get(2)?;
|
||||
Ok((rowid, uid, folder))
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for (folder, rowid_set, uid_set) in UidGrouper::from(rows) {
|
||||
self.select_folder(context, Some(&folder))
|
||||
.await
|
||||
.context("failed to select folder")?;
|
||||
|
||||
if let Err(err) = self.add_flag_finalized_with_set(&uid_set, "\\Seen").await {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark messages {} in folder {} as seen, will retry later: {}.",
|
||||
uid_set,
|
||||
folder,
|
||||
err
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Marked messages {} in folder {} as seen.", uid_set, folder
|
||||
);
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
format!(
|
||||
"DELETE FROM imap_markseen WHERE id IN ({})",
|
||||
sql::repeat_vars(rowid_set.len())?
|
||||
),
|
||||
rusqlite::params_from_iter(rowid_set),
|
||||
)
|
||||
.await
|
||||
.context("cannot remove messages marked as seen from imap_markseen table")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Synchronizes `\Seen` flags using `CONDSTORE` extension.
|
||||
pub(crate) async fn sync_seen_flags(&mut self, context: &Context, folder: &str) -> Result<()> {
|
||||
if !self.config.can_condstore {
|
||||
@@ -1108,11 +1206,7 @@ impl Imap {
|
||||
.session
|
||||
.as_mut()
|
||||
.context("IMAP No Connection established")?;
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context("not configured")?;
|
||||
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
let search_command = format!("FROM \"{}\"", self_addr);
|
||||
let uids = session
|
||||
.uid_search(search_command)
|
||||
@@ -1262,8 +1356,6 @@ impl Imap {
|
||||
}
|
||||
};
|
||||
|
||||
let folder = folder.to_string();
|
||||
|
||||
while let Some(Ok(msg)) = msgs.next().await {
|
||||
let server_uid = msg.uid.unwrap_or_default();
|
||||
|
||||
@@ -1297,7 +1389,6 @@ impl Imap {
|
||||
|
||||
// XXX put flags into a set and pass them to dc_receive_imf
|
||||
let context = context.clone();
|
||||
let folder = folder.clone();
|
||||
|
||||
// safe, as we checked above that there is a body.
|
||||
let body = body
|
||||
@@ -1318,7 +1409,6 @@ impl Imap {
|
||||
&context,
|
||||
rfc724_mid,
|
||||
body,
|
||||
&folder,
|
||||
is_seen,
|
||||
partial,
|
||||
fetching_existing_messages,
|
||||
@@ -1357,11 +1447,6 @@ impl Imap {
|
||||
/// the flag, or other imap-errors, returns true as well.
|
||||
///
|
||||
/// Returning error means that the operation can be retried.
|
||||
async fn add_flag_finalized(&mut self, server_uid: u32, flag: &str) -> Result<()> {
|
||||
let s = server_uid.to_string();
|
||||
self.add_flag_finalized_with_set(&s, flag).await
|
||||
}
|
||||
|
||||
async fn add_flag_finalized_with_set(&mut self, uid_set: &str, flag: &str) -> Result<()> {
|
||||
if self.should_reconnect() {
|
||||
bail!("Can't set flag, should reconnect");
|
||||
@@ -1379,7 +1464,7 @@ impl Imap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn prepare_imap_operation_on_msg(
|
||||
pub(crate) async fn prepare_imap_operation_on_msg(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
@@ -1419,32 +1504,6 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn set_seen(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
uid: u32,
|
||||
) -> ImapActionResult {
|
||||
if let Some(imapresult) = self
|
||||
.prepare_imap_operation_on_msg(context, folder, uid)
|
||||
.await
|
||||
{
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
info!(context, "Marking message {}/{} as seen...", folder, uid,);
|
||||
|
||||
if let Err(err) = self.add_flag_finalized(uid, "\\Seen").await {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark message {} in folder {} as seen, ignoring: {}.", uid, folder, err
|
||||
);
|
||||
ImapActionResult::Failed
|
||||
} else {
|
||||
ImapActionResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ensure_configured_folders(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
@@ -1677,13 +1736,14 @@ async fn spam_target_folder(
|
||||
pub async fn target_folder(
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
is_spam_folder: bool,
|
||||
headers: &[mailparse::MailHeader<'_>],
|
||||
) -> Result<Option<Config>> {
|
||||
if context.is_mvbox(folder).await? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if context.is_spam_folder(folder).await? {
|
||||
if is_spam_folder {
|
||||
spam_target_folder(context, headers).await
|
||||
} else if needs_move_to_mvbox(context, headers).await? {
|
||||
Ok(Some(Config::ConfiguredMvboxFolder))
|
||||
@@ -1875,13 +1935,11 @@ pub(crate) async fn prefetch_should_download(
|
||||
mut flags: impl Iterator<Item = Flag<'_>>,
|
||||
show_emails: ShowEmails,
|
||||
) -> Result<bool> {
|
||||
if let Some(msg_id) = message::rfc724_mid_exists(context, message_id).await? {
|
||||
// We know the Message-ID already, it must be a Bcc: to self.
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(Action::MarkseenMsgOnImap, msg_id.to_u32(), Params::new(), 0),
|
||||
)
|
||||
.await?;
|
||||
if message::rfc724_mid_exists(context, message_id)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
markseen_on_imap_table(context, message_id).await?;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -2015,6 +2073,22 @@ async fn mark_seen_by_uid(
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule marking the message as Seen on IMAP by adding all known IMAP messages corresponding to
|
||||
/// the given Message-ID to `imap_markseen` table.
|
||||
pub(crate) async fn markseen_on_imap_table(context: &Context, message_id: &str) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT OR IGNORE INTO imap_markseen (id)
|
||||
SELECT id FROM imap WHERE rfc724_mid=?",
|
||||
paramsv![message_id],
|
||||
)
|
||||
.await?;
|
||||
context.interrupt_inbox(InterruptInfo::new(false)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// uid_next is the next unique identifier value from the last time we fetched a folder
|
||||
/// See <https://tools.ietf.org/html/rfc3501#section-2.3.1.1>
|
||||
/// This function is used to update our uid_next after fetching messages.
|
||||
@@ -2115,7 +2189,11 @@ pub async fn get_config_last_seen_uid(context: &Context, folder: &str) -> Result
|
||||
///
|
||||
/// This caters for the [`Config::OnlyFetchMvbox`] setting which means mails from folders
|
||||
/// not explicitly watched should not be fetched.
|
||||
async fn should_ignore_folder(context: &Context, folder: &str) -> Result<bool> {
|
||||
async fn should_ignore_folder(
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
is_spam_folder: bool,
|
||||
) -> Result<bool> {
|
||||
if !context.get_config_bool(Config::OnlyFetchMvbox).await? {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -2123,7 +2201,7 @@ async fn should_ignore_folder(context: &Context, folder: &str) -> Result<bool> {
|
||||
// Still respect the SentboxWatch setting.
|
||||
return Ok(!context.get_config_bool(Config::SentboxWatch).await?);
|
||||
}
|
||||
Ok(!(context.is_mvbox(folder).await? || context.is_spam_folder(folder).await?))
|
||||
Ok(!(context.is_mvbox(folder).await? || is_spam_folder))
|
||||
}
|
||||
|
||||
/// Builds a list of sequence/uid sets. The returned sets have each no more than around 1000
|
||||
@@ -2299,9 +2377,6 @@ mod tests {
|
||||
folder, mvbox_move, chat_msg, accepted_chat, outgoing, setupmessage);
|
||||
|
||||
let t = TestContext::new_alice().await;
|
||||
t.ctx
|
||||
.set_config(Config::ConfiguredSpamFolder, Some("Spam"))
|
||||
.await?;
|
||||
t.ctx
|
||||
.set_config(Config::ConfiguredMvboxFolder, Some("DeltaChat"))
|
||||
.await?;
|
||||
@@ -2343,11 +2418,13 @@ mod tests {
|
||||
|
||||
let (headers, _) = mailparse::parse_headers(bytes)?;
|
||||
|
||||
let actual = if let Some(config) = target_folder(&t, folder, &headers).await? {
|
||||
t.get_config(config).await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let is_spam_folder = folder == "Spam";
|
||||
let actual =
|
||||
if let Some(config) = target_folder(&t, folder, is_spam_folder, &headers).await? {
|
||||
t.get_config(config).await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let expected = if expected_destination == folder {
|
||||
None
|
||||
|
||||
@@ -157,7 +157,10 @@ impl Imap {
|
||||
// in anything. If so, we behave as if IDLE had data but
|
||||
// will have already fetched the messages so perform_*_fetch
|
||||
// will not find any new.
|
||||
match self.fetch_new_messages(context, &watch_folder, false).await {
|
||||
match self
|
||||
.fetch_new_messages(context, &watch_folder, false, false)
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
info!(context, "fetch_new_messages returned {:?}", res);
|
||||
if res {
|
||||
|
||||
@@ -60,6 +60,9 @@ impl Imap {
|
||||
let is_drafts = folder_meaning == FolderMeaning::Drafts
|
||||
|| (folder_meaning == FolderMeaning::Unknown
|
||||
&& folder_name_meaning == FolderMeaning::Drafts);
|
||||
let is_spam_folder = folder_meaning == FolderMeaning::Spam
|
||||
|| (folder_meaning == FolderMeaning::Unknown
|
||||
&& folder_name_meaning == FolderMeaning::Spam);
|
||||
|
||||
// Don't scan folders that are watched anyway
|
||||
if !watched_folders.contains(&folder.name().to_string()) && !is_drafts {
|
||||
@@ -67,7 +70,7 @@ impl Imap {
|
||||
self.server_sent_unsolicited_exists(context)?;
|
||||
|
||||
loop {
|
||||
self.fetch_move_delete(context, folder.name())
|
||||
self.fetch_move_delete(context, folder.name(), is_spam_folder)
|
||||
.await
|
||||
.ok_or_log_msg(context, "Can't fetch new msgs in scanned folder");
|
||||
|
||||
@@ -79,16 +82,15 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
// We iterate over both folder meanings to make sure that if e.g. the "Sent" folder was deleted,
|
||||
// `ConfiguredSentboxFolder` is set to `None`:
|
||||
for config in &[
|
||||
Config::ConfiguredSentboxFolder,
|
||||
Config::ConfiguredSpamFolder,
|
||||
] {
|
||||
context
|
||||
.set_config(*config, folder_configs.get(config).map(|s| s.as_str()))
|
||||
.await?;
|
||||
}
|
||||
// Set the `ConfiguredSentboxFolder` or set it to `None` if the folder was deleted.
|
||||
context
|
||||
.set_config(
|
||||
Config::ConfiguredSentboxFolder,
|
||||
folder_configs
|
||||
.get(&Config::ConfiguredSentboxFolder)
|
||||
.map(|s| s.as_str()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
last_scan.replace(Instant::now());
|
||||
Ok(true)
|
||||
|
||||
@@ -351,9 +351,8 @@ async fn set_self_key(
|
||||
}
|
||||
};
|
||||
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).await?;
|
||||
ensure!(self_addr.is_some(), "Missing self addr");
|
||||
let addr = EmailAddress::new(&self_addr.unwrap_or_default())?;
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
let addr = EmailAddress::new(&self_addr)?;
|
||||
let keypair = pgp::KeyPair {
|
||||
addr,
|
||||
public: public_key,
|
||||
|
||||
139
src/job.rs
139
src/job.rs
@@ -4,7 +4,7 @@
|
||||
//! and job types.
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
use anyhow::{format_err, Context as _, Result};
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
@@ -13,9 +13,8 @@ use crate::contact::{normalize_name, Contact, ContactId, Modifier, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::events::EventType;
|
||||
use crate::imap::{Imap, ImapActionResult};
|
||||
use crate::imap::Imap;
|
||||
use crate::location;
|
||||
use crate::log::LogExt;
|
||||
use crate::message::{Message, MsgId};
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::param::{Param, Params};
|
||||
@@ -32,7 +31,6 @@ const JOB_RETRIES: u32 = 17;
|
||||
)]
|
||||
#[repr(u32)]
|
||||
pub(crate) enum Thread {
|
||||
Unknown = 0,
|
||||
Imap = 100,
|
||||
Smtp = 5000,
|
||||
}
|
||||
@@ -60,12 +58,6 @@ macro_rules! job_try {
|
||||
};
|
||||
}
|
||||
|
||||
impl Default for Thread {
|
||||
fn default() -> Self {
|
||||
Thread::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Display,
|
||||
@@ -81,12 +73,8 @@ impl Default for Thread {
|
||||
)]
|
||||
#[repr(u32)]
|
||||
pub enum Action {
|
||||
Unknown = 0,
|
||||
|
||||
// Jobs in the INBOX-thread, range from DC_IMAP_THREAD..DC_IMAP_THREAD+999
|
||||
Housekeeping = 105, // low priority ...
|
||||
FetchExistingMsgs = 110,
|
||||
MarkseenMsgOnImap = 130,
|
||||
|
||||
// this is user initiated so it should have a fairly high priority
|
||||
UpdateRecentQuota = 140,
|
||||
@@ -107,23 +95,13 @@ pub enum Action {
|
||||
SendMdn = 5010,
|
||||
}
|
||||
|
||||
impl Default for Action {
|
||||
fn default() -> Self {
|
||||
Action::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Action> for Thread {
|
||||
fn from(action: Action) -> Thread {
|
||||
use Action::*;
|
||||
|
||||
match action {
|
||||
Unknown => Thread::Unknown,
|
||||
|
||||
Housekeeping => Thread::Imap,
|
||||
FetchExistingMsgs => Thread::Imap,
|
||||
ResyncFolders => Thread::Imap,
|
||||
MarkseenMsgOnImap => Thread::Imap,
|
||||
UpdateRecentQuota => Thread::Imap,
|
||||
DownloadMsg => Thread::Imap,
|
||||
|
||||
@@ -357,7 +335,7 @@ impl Job {
|
||||
Config::ConfiguredSentboxFolder,
|
||||
] {
|
||||
if let Some(folder) = job_try!(context.get_config(*config).await) {
|
||||
if let Err(e) = imap.fetch_new_messages(context, &folder, true).await {
|
||||
if let Err(e) = imap.fetch_new_messages(context, &folder, false, true).await {
|
||||
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
|
||||
warn!(context, "Could not fetch messages, retrying: {:#}", e);
|
||||
return Status::RetryLater;
|
||||
@@ -403,67 +381,6 @@ impl Job {
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn markseen_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status {
|
||||
if let Err(err) = imap.prepare(context).await {
|
||||
warn!(context, "could not connect: {:?}", err);
|
||||
return Status::RetryLater;
|
||||
}
|
||||
|
||||
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
|
||||
let row = job_try!(
|
||||
context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT uid, folder FROM imap
|
||||
WHERE rfc724_mid=? AND folder=target
|
||||
ORDER BY uid ASC
|
||||
LIMIT 1",
|
||||
paramsv![msg.rfc724_mid],
|
||||
|row| {
|
||||
let uid: u32 = row.get(0)?;
|
||||
let folder: String = row.get(1)?;
|
||||
Ok((uid, folder))
|
||||
}
|
||||
)
|
||||
.await
|
||||
);
|
||||
if let Some((server_uid, server_folder)) = row {
|
||||
let result = imap.set_seen(context, &server_folder, server_uid).await;
|
||||
match result {
|
||||
ImapActionResult::RetryLater => return Status::RetryLater,
|
||||
ImapActionResult::Success | ImapActionResult::Failed => {}
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Can't mark the message {} as seen on IMAP because there is no known UID",
|
||||
msg.rfc724_mid
|
||||
);
|
||||
}
|
||||
|
||||
// XXX we send MDN even in case of failure to mark the messages as seen, e.g. if it was
|
||||
// already deleted on the server by another device. The job will not be retried so locally
|
||||
// there is no risk of double-sending MDNs.
|
||||
//
|
||||
// Read receipts for system messages are never sent. These messages have no place to
|
||||
// display received read receipt anyway. And since their text is locally generated,
|
||||
// quoting them is dangerous as it may contain contact names. E.g., for original message
|
||||
// "Group left by me", a read receipt will quote "Group left by <name>", and the name can
|
||||
// be a display name stored in address book rather than the name sent in the From field by
|
||||
// the user.
|
||||
if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() && !msg.is_system_message() {
|
||||
let mdns_enabled = job_try!(context.get_config_bool(Config::MdnsEnabled).await);
|
||||
if mdns_enabled {
|
||||
if let Err(err) = send_mdn(context, &msg).await {
|
||||
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||
return Status::Finished(Err(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete all pending jobs with the given action.
|
||||
@@ -653,19 +570,13 @@ async fn perform_job_action(
|
||||
);
|
||||
|
||||
let try_res = match job.action {
|
||||
Action::Unknown => Status::Finished(Err(format_err!("Unknown job id found"))),
|
||||
Action::SendMdn => job.send_mdn(context, connection.smtp()).await,
|
||||
Action::MaybeSendLocations => location::job_maybe_send_locations(context, job).await,
|
||||
Action::MaybeSendLocationsEnded => {
|
||||
location::job_maybe_send_locations_ended(context, job).await
|
||||
}
|
||||
Action::ResyncFolders => job.resync_folders(context, connection.inbox()).await,
|
||||
Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await,
|
||||
Action::FetchExistingMsgs => job.fetch_existing_msgs(context, connection.inbox()).await,
|
||||
Action::Housekeeping => {
|
||||
sql::housekeeping(context).await.ok_or_log(context);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
Action::UpdateRecentQuota => match context.update_recent_quota(connection.inbox()).await {
|
||||
Ok(status) => status,
|
||||
Err(err) => Status::Finished(Err(err)),
|
||||
@@ -698,13 +609,13 @@ fn get_backoff_time_offset(tries: u32, action: Action) -> i64 {
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_mdn(context: &Context, msg: &Message) -> Result<()> {
|
||||
pub(crate) async fn send_mdn(context: &Context, msg_id: MsgId, from_id: ContactId) -> Result<()> {
|
||||
let mut param = Params::new();
|
||||
param.set(Param::MsgId, msg.id.to_u32().to_string());
|
||||
param.set(Param::MsgId, msg_id.to_u32().to_string());
|
||||
|
||||
add(
|
||||
context,
|
||||
Job::new(Action::SendMdn, msg.from_id.to_u32(), param, 0),
|
||||
Job::new(Action::SendMdn, from_id.to_u32(), param, 0),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -729,10 +640,7 @@ pub async fn add(context: &Context, job: Job) -> Result<()> {
|
||||
|
||||
if delay_seconds == 0 {
|
||||
match action {
|
||||
Action::Unknown => unreachable!(),
|
||||
Action::Housekeeping
|
||||
| Action::ResyncFolders
|
||||
| Action::MarkseenMsgOnImap
|
||||
Action::ResyncFolders
|
||||
| Action::FetchExistingMsgs
|
||||
| Action::UpdateRecentQuota
|
||||
| Action::DownloadMsg => {
|
||||
@@ -748,18 +656,6 @@ pub async fn add(context: &Context, job: Job) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_housekeeping_job(context: &Context) -> Result<Option<Job>> {
|
||||
let last_time = context.get_config_i64(Config::LastHousekeeping).await?;
|
||||
|
||||
let next_time = last_time + (60 * 60 * 24);
|
||||
if next_time <= time() {
|
||||
kill_action(context, Action::Housekeeping).await?;
|
||||
Ok(Some(Job::new(Action::Housekeeping, 0, Params::new(), 0)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load jobs from the database.
|
||||
///
|
||||
/// Load jobs for this "[Thread]", i.e. either load SMTP jobs or load
|
||||
@@ -803,7 +699,7 @@ LIMIT 1;
|
||||
params = paramsv![thread_i];
|
||||
};
|
||||
|
||||
let job = loop {
|
||||
loop {
|
||||
let job_res = context
|
||||
.sql
|
||||
.query_row_optional(query, params.clone(), |row| {
|
||||
@@ -822,7 +718,7 @@ LIMIT 1;
|
||||
.await;
|
||||
|
||||
match job_res {
|
||||
Ok(job) => break job,
|
||||
Ok(job) => return Ok(job),
|
||||
Err(err) => {
|
||||
// Remove invalid job from the DB
|
||||
info!(context, "cleaning up job, because of {}", err);
|
||||
@@ -840,20 +736,6 @@ LIMIT 1;
|
||||
.with_context(|| format!("Failed to delete invalid job {}", id))?;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match thread {
|
||||
Thread::Unknown => {
|
||||
bail!("unknown thread for job")
|
||||
}
|
||||
Thread::Imap => {
|
||||
if let Some(job) = job {
|
||||
Ok(Some(job))
|
||||
} else {
|
||||
Ok(load_housekeeping_job(context).await?)
|
||||
}
|
||||
}
|
||||
Thread::Smtp => Ok(job),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,8 +783,7 @@ mod tests {
|
||||
&InterruptInfo::new(false),
|
||||
)
|
||||
.await?;
|
||||
// The housekeeping job should be loaded as we didn't run housekeeping in the last day:
|
||||
assert_eq!(jobs.unwrap().action, Action::Housekeeping);
|
||||
assert!(jobs.is_none());
|
||||
|
||||
insert_job(&t, 1, true).await;
|
||||
let jobs = load_next(
|
||||
|
||||
@@ -198,10 +198,7 @@ impl DcSecretKey for SignedSecretKey {
|
||||
}
|
||||
|
||||
async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
||||
let addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context("no address configured")?;
|
||||
let addr = context.get_configured_addr().await?;
|
||||
let addr = EmailAddress::new(&addr)?;
|
||||
let _guard = context.generating_key_mutex.lock().await;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use bitflags::bitflags;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat::{self, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::contact::ContactId;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
@@ -424,10 +423,7 @@ pub async fn delete_all(context: &Context) -> Result<()> {
|
||||
pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)> {
|
||||
let mut last_added_location_id = 0;
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||
|
||||
@@ -139,8 +139,18 @@ pub struct LoginParam {
|
||||
}
|
||||
|
||||
impl LoginParam {
|
||||
/// Load entered (candidate) account settings
|
||||
pub async fn load_candidate_params(context: &Context) -> Result<Self> {
|
||||
LoginParam::from_database(context, "").await
|
||||
}
|
||||
|
||||
/// Load configured (working) account settings
|
||||
pub async fn load_configured_params(context: &Context) -> Result<Self> {
|
||||
LoginParam::from_database(context, "configured_").await
|
||||
}
|
||||
|
||||
/// Read the login parameters from the database.
|
||||
pub async fn from_database(context: &Context, prefix: impl AsRef<str>) -> Result<Self> {
|
||||
async fn from_database(context: &Context, prefix: impl AsRef<str>) -> Result<Self> {
|
||||
let prefix = prefix.as_ref();
|
||||
let sql = &context.sql;
|
||||
|
||||
@@ -242,8 +252,8 @@ impl LoginParam {
|
||||
}
|
||||
|
||||
/// Save this loginparam to the database.
|
||||
pub async fn save_to_database(&self, context: &Context, prefix: impl AsRef<str>) -> Result<()> {
|
||||
let prefix = prefix.as_ref();
|
||||
pub async fn save_as_configured_params(&self, context: &Context) -> Result<()> {
|
||||
let prefix = "configured_";
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
@@ -438,8 +448,8 @@ mod tests {
|
||||
socks5_config: None,
|
||||
};
|
||||
|
||||
param.save_to_database(&t, "foobar_").await?;
|
||||
let loaded = LoginParam::from_database(&t, "foobar_").await?;
|
||||
param.save_as_configured_params(&t).await?;
|
||||
let loaded = LoginParam::load_configured_params(&t).await?;
|
||||
|
||||
assert_eq!(param, loaded);
|
||||
Ok(())
|
||||
|
||||
@@ -9,6 +9,7 @@ use rusqlite::types::ValueRef;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::chat::{self, Chat, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_DESIRED_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
|
||||
};
|
||||
@@ -21,7 +22,8 @@ use crate::dc_tools::{
|
||||
use crate::download::DownloadState;
|
||||
use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
|
||||
use crate::events::EventType;
|
||||
use crate::job::{self, Action};
|
||||
use crate::imap::markseen_on_imap_table;
|
||||
use crate::job;
|
||||
use crate::log::LogExt;
|
||||
use crate::mimeparser::{parse_message_id, FailureReport, SystemMessage};
|
||||
use crate::param::{Param, Params};
|
||||
@@ -1252,19 +1254,13 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
}
|
||||
|
||||
if !msg_ids.is_empty() {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
job::kill_action(context, Action::Housekeeping).await?;
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(Action::Housekeeping, 0, Params::new(), 10),
|
||||
)
|
||||
.await?;
|
||||
context.emit_msgs_changed_without_ids();
|
||||
|
||||
// Run housekeeping to delete unused blobs.
|
||||
context.set_config(Config::LastHousekeeping, None).await?;
|
||||
}
|
||||
|
||||
// Interrupt Inbox loop to start message deletion.
|
||||
// Interrupt Inbox loop to start message deletion and run housekeeping.
|
||||
context.interrupt_inbox(InterruptInfo::new(false)).await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1294,6 +1290,9 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
|
||||
m.chat_id AS chat_id,
|
||||
m.state AS state,
|
||||
m.ephemeral_timer AS ephemeral_timer,
|
||||
m.param AS param,
|
||||
m.from_id AS from_id,
|
||||
m.rfc724_mid AS rfc724_mid,
|
||||
c.blocked AS blocked
|
||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
|
||||
WHERE m.id IN ({}) AND m.chat_id>9",
|
||||
@@ -1304,12 +1303,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
|
||||
let id: MsgId = row.get("id")?;
|
||||
let chat_id: ChatId = row.get("chat_id")?;
|
||||
let state: MessageState = row.get("state")?;
|
||||
let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
|
||||
let from_id: ContactId = row.get("from_id")?;
|
||||
let rfc724_mid: String = row.get("rfc724_mid")?;
|
||||
let blocked: Option<Blocked> = row.get("blocked")?;
|
||||
let ephemeral_timer: EphemeralTimer = row.get("ephemeral_timer")?;
|
||||
Ok((
|
||||
id,
|
||||
chat_id,
|
||||
state,
|
||||
param,
|
||||
from_id,
|
||||
rfc724_mid,
|
||||
blocked.unwrap_or_default(),
|
||||
ephemeral_timer,
|
||||
))
|
||||
@@ -1318,30 +1323,52 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
|
||||
)
|
||||
.await?;
|
||||
|
||||
if msgs
|
||||
.iter()
|
||||
.any(|(_id, _chat_id, _state, _blocked, ephemeral_timer)| {
|
||||
if msgs.iter().any(
|
||||
|(_id, _chat_id, _state, _param, _from_id, _rfc724_mid, _blocked, ephemeral_timer)| {
|
||||
*ephemeral_timer != EphemeralTimer::Disabled
|
||||
})
|
||||
{
|
||||
},
|
||||
) {
|
||||
start_ephemeral_timers_msgids(context, &msg_ids)
|
||||
.await
|
||||
.context("failed to start ephemeral timers")?;
|
||||
}
|
||||
|
||||
let mut updated_chat_ids = BTreeSet::new();
|
||||
for (id, curr_chat_id, curr_state, curr_blocked, _curr_ephemeral_timer) in msgs.into_iter() {
|
||||
for (
|
||||
id,
|
||||
curr_chat_id,
|
||||
curr_state,
|
||||
curr_param,
|
||||
curr_from_id,
|
||||
curr_rfc724_mid,
|
||||
curr_blocked,
|
||||
_curr_ephemeral_timer,
|
||||
) in msgs.into_iter()
|
||||
{
|
||||
if curr_blocked == Blocked::Not
|
||||
&& (curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed)
|
||||
{
|
||||
update_msg_state(context, id, MessageState::InSeen).await?;
|
||||
info!(context, "Seen message {}.", id);
|
||||
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0),
|
||||
)
|
||||
.await?;
|
||||
markseen_on_imap_table(context, &curr_rfc724_mid).await?;
|
||||
|
||||
// Read receipts for system messages are never sent. These messages have no place to
|
||||
// display received read receipt anyway. And since their text is locally generated,
|
||||
// quoting them is dangerous as it may contain contact names. E.g., for original message
|
||||
// "Group left by me", a read receipt will quote "Group left by <name>", and the name can
|
||||
// be a display name stored in address book rather than the name sent in the From field by
|
||||
// the user.
|
||||
if curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
|
||||
&& curr_param.get_cmd() == SystemMessage::Unknown
|
||||
{
|
||||
let mdns_enabled = context.get_config_bool(Config::MdnsEnabled).await?;
|
||||
if mdns_enabled {
|
||||
if let Err(err) = job::send_mdn(context, id, curr_from_id).await {
|
||||
warn!(context, "could not send out mdn for {}: {}", id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
updated_chat_ids.insert(curr_chat_id);
|
||||
}
|
||||
}
|
||||
@@ -2017,7 +2044,6 @@ mod tests {
|
||||
Date: Fri, 29 Jan 2021 21:37:55 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -2226,7 +2252,6 @@ mod tests {
|
||||
Date: Fri, 29 Jan 2021 21:37:55 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -2244,7 +2269,6 @@ mod tests {
|
||||
Date: Fri, 29 Jan 2021 21:37:55 +0000\n\
|
||||
\n\
|
||||
hello again\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -133,11 +133,7 @@ impl<'a> MimeFactory<'a> {
|
||||
) -> Result<MimeFactory<'a>> {
|
||||
let chat = Chat::load_from_db(context, msg.chat_id).await?;
|
||||
|
||||
let from_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let from_addr = context.get_configured_addr().await?;
|
||||
let config_displayname = context
|
||||
.get_config(Config::Displayname)
|
||||
.await?
|
||||
@@ -237,10 +233,7 @@ impl<'a> MimeFactory<'a> {
|
||||
ensure!(!msg.chat_id.is_special(), "Invalid chat id");
|
||||
|
||||
let contact = Contact::load_from_db(context, msg.from_id).await?;
|
||||
let from_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let from_addr = context.get_configured_addr().await?;
|
||||
let from_displayname = context
|
||||
.get_config(Config::Displayname)
|
||||
.await?
|
||||
@@ -278,11 +271,7 @@ impl<'a> MimeFactory<'a> {
|
||||
&self,
|
||||
context: &Context,
|
||||
) -> Result<Vec<(Option<Peerstate>, &str)>> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.context("not configured")?;
|
||||
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
let mut res = Vec::new();
|
||||
for (_, addr) in self
|
||||
.recipients
|
||||
@@ -1672,7 +1661,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -1765,7 +1753,6 @@ mod tests {
|
||||
t.get_last_msg().await.rfc724_mid
|
||||
)
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -1876,7 +1863,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||
\n\
|
||||
Some other, completely unrelated content\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -1901,9 +1887,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dc_receive_imf(context, imf_raw, "INBOX", false)
|
||||
.await
|
||||
.unwrap();
|
||||
dc_receive_imf(context, imf_raw, false).await.unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(context, 0, None, None).await.unwrap();
|
||||
|
||||
|
||||
@@ -2915,7 +2915,6 @@ On 2020-10-25, Bob wrote:
|
||||
dc_receive_imf(
|
||||
&t.ctx,
|
||||
include_bytes!("../test-data/message/subj_with_multimedia_msg.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
@@ -3064,7 +3063,7 @@ Subject: ...
|
||||
|
||||
Some quote.
|
||||
"###;
|
||||
dc_receive_imf(&t, raw, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, raw, false).await?;
|
||||
|
||||
// Delta Chat generates In-Reply-To with a starting tab when Message-ID is too long.
|
||||
let raw = br###"In-Reply-To:
|
||||
@@ -3081,7 +3080,7 @@ Subject: ...
|
||||
Some reply
|
||||
"###;
|
||||
|
||||
dc_receive_imf(&t, raw, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, raw, false).await?;
|
||||
|
||||
let msg = t.get_last_msg().await;
|
||||
assert_eq!(msg.get_text().unwrap(), "Some reply");
|
||||
@@ -3109,13 +3108,13 @@ Message.
|
||||
"###;
|
||||
|
||||
// Bob receives message.
|
||||
dc_receive_imf(&bob, raw, "INBOX", false).await?;
|
||||
dc_receive_imf(&bob, raw, false).await?;
|
||||
let msg = bob.get_last_msg().await;
|
||||
// Message is incoming.
|
||||
assert!(msg.param.get_bool(Param::WantsMdn).unwrap());
|
||||
|
||||
// Alice receives copy-to-self.
|
||||
dc_receive_imf(&alice, raw, "INBOX", false).await?;
|
||||
dc_receive_imf(&alice, raw, false).await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
// Message is outgoing, don't send read receipt to self.
|
||||
assert!(msg.param.get_bool(Param::WantsMdn).is_none());
|
||||
@@ -3141,7 +3140,6 @@ Message.
|
||||
\n\
|
||||
hello\n"
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -3179,7 +3177,6 @@ Message.
|
||||
\n\
|
||||
--SNIPP--"
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -3202,7 +3199,7 @@ Message.
|
||||
|
||||
let original =
|
||||
include_bytes!("../test-data/message/ms_exchange_report_original_message.eml");
|
||||
dc_receive_imf(&t, original, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, original, false).await?;
|
||||
let original_msg_id = t.get_last_msg().await.id;
|
||||
|
||||
// 1. Test mimeparser directly
|
||||
@@ -3217,7 +3214,7 @@ Message.
|
||||
assert!(mimeparser.mdn_reports[0].additional_message_ids.is_empty());
|
||||
|
||||
// 2. Test that marking the original msg as read works
|
||||
dc_receive_imf(&t, mdn, "INBOX", false).await?;
|
||||
dc_receive_imf(&t, mdn, false).await?;
|
||||
|
||||
assert_eq!(
|
||||
original_msg_id.get_state(&t).await?,
|
||||
|
||||
@@ -479,7 +479,7 @@ mod tests {
|
||||
let mut keyring = Keyring::new();
|
||||
keyring.add(KEYS.alice_public.clone());
|
||||
keyring.add(KEYS.bob_public.clone());
|
||||
futures_lite::future::block_on(pk_encrypt(
|
||||
async_std::task::block_on(pk_encrypt(
|
||||
CLEARTEXT,
|
||||
keyring,
|
||||
Some(KEYS.alice_secret.clone()),
|
||||
@@ -492,7 +492,7 @@ mod tests {
|
||||
let mut keyring = Keyring::new();
|
||||
keyring.add(KEYS.alice_public.clone());
|
||||
keyring.add(KEYS.bob_public.clone());
|
||||
futures_lite::future::block_on(pk_encrypt(CLEARTEXT, keyring, None)).unwrap()
|
||||
async_std::task::block_on(pk_encrypt(CLEARTEXT, keyring, None)).unwrap()
|
||||
});
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -8,11 +8,13 @@ use async_std::{
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::maybe_add_time_based_warnings;
|
||||
use crate::dc_tools::time;
|
||||
use crate::ephemeral::{self, delete_expired_imap_messages};
|
||||
use crate::imap::Imap;
|
||||
use crate::job::{self, Thread};
|
||||
use crate::log::LogExt;
|
||||
use crate::smtp::{send_smtp_messages, Smtp};
|
||||
use crate::sql;
|
||||
|
||||
use self::connectivity::ConnectivityStore;
|
||||
|
||||
@@ -85,8 +87,6 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
|
||||
.expect("inbox loop, missing started receiver");
|
||||
let ctx = ctx1;
|
||||
|
||||
// track number of continously executed jobs
|
||||
let mut jobs_loaded = 0;
|
||||
let mut info = InterruptInfo::default();
|
||||
loop {
|
||||
let job = match job::load_next(&ctx, Thread::Imap, &info).await {
|
||||
@@ -98,22 +98,26 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
|
||||
};
|
||||
|
||||
match job {
|
||||
Some(job) if jobs_loaded <= 20 => {
|
||||
jobs_loaded += 1;
|
||||
Some(job) => {
|
||||
job::perform_job(&ctx, job::Connection::Inbox(&mut connection), job).await;
|
||||
info = Default::default();
|
||||
}
|
||||
Some(job) => {
|
||||
// Let the fetch run, but return back to the job afterwards.
|
||||
jobs_loaded = 0;
|
||||
info!(ctx, "postponing imap-job {} to run fetch...", job);
|
||||
fetch(&ctx, &mut connection).await;
|
||||
}
|
||||
None => {
|
||||
jobs_loaded = 0;
|
||||
|
||||
maybe_add_time_based_warnings(&ctx).await;
|
||||
|
||||
match ctx.get_config_i64(Config::LastHousekeeping).await {
|
||||
Ok(last_housekeeping_time) => {
|
||||
let next_housekeeping_time =
|
||||
last_housekeeping_time.saturating_add(60 * 60 * 24);
|
||||
if next_housekeeping_time <= time() {
|
||||
sql::housekeeping(&ctx).await.ok_or_log(&ctx);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(ctx, "Failed to get last housekeeping time: {}", err);
|
||||
}
|
||||
};
|
||||
|
||||
info = fetch_idle(&ctx, &mut connection, Config::ConfiguredInboxFolder).await;
|
||||
}
|
||||
}
|
||||
@@ -133,32 +137,6 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
|
||||
.expect("inbox loop, missing shutdown receiver");
|
||||
}
|
||||
|
||||
async fn fetch(ctx: &Context, connection: &mut Imap) {
|
||||
match ctx.get_config(Config::ConfiguredInboxFolder).await {
|
||||
Ok(Some(watch_folder)) => {
|
||||
if let Err(err) = connection.prepare(ctx).await {
|
||||
warn!(ctx, "Could not connect: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch
|
||||
if let Err(err) = connection.fetch_move_delete(ctx, &watch_folder).await {
|
||||
connection.trigger_reconnect(ctx).await;
|
||||
warn!(ctx, "{:#}", err);
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
info!(ctx, "Can not fetch inbox folder, not set");
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
ctx,
|
||||
"Can not fetch inbox folder, failed to get config: {:?}", err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> InterruptInfo {
|
||||
match ctx.get_config(folder).await {
|
||||
Ok(Some(watch_folder)) => {
|
||||
@@ -168,7 +146,30 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> Int
|
||||
return connection.fake_idle(ctx, Some(watch_folder)).await;
|
||||
}
|
||||
|
||||
// Mark expired messages for deletion.
|
||||
if folder == Config::ConfiguredInboxFolder {
|
||||
if let Err(err) = connection
|
||||
.store_seen_flags_on_imap(ctx)
|
||||
.await
|
||||
.context("store_seen_flags_on_imap failed")
|
||||
{
|
||||
warn!(ctx, "{:#}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the watched folder.
|
||||
if let Err(err) = connection
|
||||
.fetch_move_delete(ctx, &watch_folder, false)
|
||||
.await
|
||||
{
|
||||
connection.trigger_reconnect(ctx).await;
|
||||
warn!(ctx, "{:#}", err);
|
||||
return InterruptInfo::new(false);
|
||||
}
|
||||
|
||||
// Mark expired messages for deletion. Marked messages will be deleted from the server
|
||||
// on the next iteration of `fetch_move_delete`. `delete_expired_imap_messages` is not
|
||||
// called right before `fetch_move_delete` because it is not well optimized and would
|
||||
// otherwise slow down message fetching.
|
||||
if let Err(err) = delete_expired_imap_messages(ctx)
|
||||
.await
|
||||
.context("delete_expired_imap_messages failed")
|
||||
@@ -176,13 +177,6 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> Int
|
||||
warn!(ctx, "{:#}", err);
|
||||
}
|
||||
|
||||
// Fetch the watched folder.
|
||||
if let Err(err) = connection.fetch_move_delete(ctx, &watch_folder).await {
|
||||
connection.trigger_reconnect(ctx).await;
|
||||
warn!(ctx, "{:#}", err);
|
||||
return InterruptInfo::new(false);
|
||||
}
|
||||
|
||||
// Scan additional folders only after finishing fetching the watched folder.
|
||||
//
|
||||
// On iOS the application has strictly limited time to work in background, so we may not
|
||||
@@ -202,7 +196,10 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> Int
|
||||
// In most cases this will select the watched folder and return because there are
|
||||
// no new messages. We want to select the watched folder anyway before going IDLE
|
||||
// there, so this does not take additional protocol round-trip.
|
||||
if let Err(err) = connection.fetch_move_delete(ctx, &watch_folder).await {
|
||||
if let Err(err) = connection
|
||||
.fetch_move_delete(ctx, &watch_folder, false)
|
||||
.await
|
||||
{
|
||||
connection.trigger_reconnect(ctx).await;
|
||||
warn!(ctx, "{:#}", err);
|
||||
return InterruptInfo::new(false);
|
||||
|
||||
@@ -304,7 +304,7 @@ impl Context {
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="initial-scale=1.0" />
|
||||
<meta name="viewport" content="initial-scale=1.0; user-scalable=no" />
|
||||
<style>
|
||||
ul {
|
||||
list-style-type: none;
|
||||
@@ -449,13 +449,7 @@ impl Context {
|
||||
// [======67%===== ]
|
||||
// =============================================================================================
|
||||
|
||||
let domain = dc_tools::EmailAddress::new(
|
||||
&self
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await?
|
||||
.unwrap_or_default(),
|
||||
)?
|
||||
.domain;
|
||||
let domain = dc_tools::EmailAddress::new(&self.get_configured_addr().await?)?.domain;
|
||||
ret += &format!(
|
||||
"<h3>{}</h3><ul>",
|
||||
stock_str::storage_on_domain(self, domain).await
|
||||
@@ -544,7 +538,7 @@ impl Context {
|
||||
self.schedule_quota_update().await?;
|
||||
}
|
||||
} else {
|
||||
ret += &format!("<li>{}</li>", stock_str::one_moment(self).await);
|
||||
ret += &format!("<li>{}</li>", stock_str::not_connected(self).await);
|
||||
self.schedule_quota_update().await?;
|
||||
}
|
||||
ret += "</ul>";
|
||||
|
||||
@@ -66,19 +66,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group: Option<ChatId>) -> R
|
||||
.is_none();
|
||||
let invitenumber = token::lookup_or_new(context, Namespace::InviteNumber, group).await;
|
||||
let auth = token::lookup_or_new(context, Namespace::Auth, group).await;
|
||||
let self_addr = match context.get_config(Config::ConfiguredAddr).await {
|
||||
Ok(Some(addr)) => addr,
|
||||
Ok(None) => {
|
||||
bail!("Not configured, cannot generate QR code.");
|
||||
}
|
||||
Err(err) => {
|
||||
bail!(
|
||||
"Unable to retrieve configuration, cannot generate QR code: {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let self_addr = context.get_configured_addr().await?;
|
||||
let self_name = context
|
||||
.get_config(Config::Displayname)
|
||||
.await?
|
||||
|
||||
@@ -87,7 +87,7 @@ impl Smtp {
|
||||
}
|
||||
|
||||
self.connectivity.set_connecting(context).await;
|
||||
let lp = LoginParam::from_database(context, "configured_").await?;
|
||||
let lp = LoginParam::load_configured_params(context).await?;
|
||||
self.connect(
|
||||
context,
|
||||
&lp.smtp,
|
||||
|
||||
@@ -181,6 +181,7 @@ impl Sql {
|
||||
PRAGMA secure_delete=on;
|
||||
PRAGMA busy_timeout = {};
|
||||
PRAGMA temp_store=memory; -- Avoid SQLITE_IOERR_GETTEMPPATH errors on Android
|
||||
PRAGMA foreign_keys=on;
|
||||
",
|
||||
Duration::from_secs(10).as_millis()
|
||||
))?;
|
||||
|
||||
@@ -395,7 +395,7 @@ UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
|
||||
|
||||
if dbversion < 71 {
|
||||
info!(context, "[migration] v71");
|
||||
if let Some(addr) = context.get_config(Config::ConfiguredAddr).await? {
|
||||
if let Ok(addr) = context.get_configured_addr().await {
|
||||
if let Ok(domain) = addr.parse::<EmailAddress>().map(|email| email.domain) {
|
||||
context
|
||||
.set_config(
|
||||
@@ -613,6 +613,17 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
|
||||
sql.execute_migration("DROP TABLE IF EXISTS backup_blobs;", 88)
|
||||
.await?;
|
||||
}
|
||||
if dbversion < 89 {
|
||||
info!(context, "[migration] v89");
|
||||
sql.execute_migration(
|
||||
r#"CREATE TABLE imap_markseen (
|
||||
id INTEGER,
|
||||
FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
|
||||
);"#,
|
||||
89,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
recalc_fingerprints,
|
||||
|
||||
@@ -287,9 +287,6 @@ pub enum StockMessage {
|
||||
#[strum(props(fallback = "Storage on %1$s"))]
|
||||
StorageOnDomain = 105,
|
||||
|
||||
#[strum(props(fallback = "One moment…"))]
|
||||
OneMoment = 106,
|
||||
|
||||
#[strum(props(fallback = "Connected"))]
|
||||
Connected = 107,
|
||||
|
||||
@@ -332,6 +329,9 @@ pub enum StockMessage {
|
||||
|
||||
#[strum(props(fallback = "Scan to join group %1$s"))]
|
||||
SecureJoinGroupQRDescription = 120,
|
||||
|
||||
#[strum(props(fallback = "Not connected"))]
|
||||
NotConnected = 121,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -1009,9 +1009,9 @@ pub(crate) async fn storage_on_domain(context: &Context, domain: impl AsRef<str>
|
||||
.replace1(domain)
|
||||
}
|
||||
|
||||
/// Stock string: `One moment…`.
|
||||
pub(crate) async fn one_moment(context: &Context) -> String {
|
||||
translated(context, StockMessage::OneMoment).await
|
||||
/// Stock string: `Not connected`.
|
||||
pub(crate) async fn not_connected(context: &Context) -> String {
|
||||
translated(context, StockMessage::NotConnected).await
|
||||
}
|
||||
|
||||
/// Stock string: `Connected`.
|
||||
|
||||
@@ -364,7 +364,7 @@ impl TestContext {
|
||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n"
|
||||
.to_owned()
|
||||
+ msg.payload();
|
||||
dc_receive_imf(&self.ctx, received_msg.as_bytes(), "INBOX", false)
|
||||
dc_receive_imf(&self.ctx, received_msg.as_bytes(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -407,12 +407,7 @@ impl TestContext {
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
let addr = other
|
||||
.ctx
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let addr = other.ctx.get_configured_addr().await.unwrap();
|
||||
// MailinglistAddress is the lowest allowed origin, we'd prefer to not modify the
|
||||
// origin when creating this contact.
|
||||
let (contact_id, modified) =
|
||||
|
||||
@@ -99,7 +99,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 23:37:57 +0000\n\
|
||||
\n\
|
||||
second message\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -113,7 +112,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 22:37:57 +0000\n\
|
||||
\n\
|
||||
first message\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -143,7 +141,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 01:00:00 +0000\n\
|
||||
\n\
|
||||
first message\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -163,7 +160,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 03:00:00 +0000\n\
|
||||
\n\
|
||||
third message\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -179,7 +175,6 @@ mod tests {
|
||||
Date: Sun, 22 Mar 2021 02:00:00 +0000\n\
|
||||
\n\
|
||||
second message\n",
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -239,10 +239,7 @@ impl Context {
|
||||
{
|
||||
instance.param.set(Param::WebxdcSummary, summary);
|
||||
instance.update_param(self).await;
|
||||
self.emit_event(EventType::MsgsChanged {
|
||||
chat_id: instance.chat_id,
|
||||
msg_id: instance.id,
|
||||
});
|
||||
self.emit_msgs_changed(instance.chat_id, instance.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,9 +251,14 @@ impl Context {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.emit_event(EventType::WebxdcStatusUpdate(instance.id));
|
||||
let status_update_serial = StatusUpdateSerial(u32::try_from(rowid)?);
|
||||
|
||||
Ok(StatusUpdateSerial(u32::try_from(rowid)?))
|
||||
self.emit_event(EventType::WebxdcStatusUpdate {
|
||||
msg_id: instance.id,
|
||||
status_update_serial,
|
||||
});
|
||||
|
||||
Ok(status_update_serial)
|
||||
}
|
||||
|
||||
/// Sends a status update for an webxdc instance.
|
||||
@@ -769,7 +771,6 @@ mod tests {
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/webxdc_good_extension.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -780,7 +781,6 @@ mod tests {
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/webxdc_bad_extension.eml"),
|
||||
"INBOX",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -1016,7 +1016,10 @@ mod tests {
|
||||
.get_matching(|evt| matches!(evt, EventType::WebxdcStatusUpdate { .. }))
|
||||
.await;
|
||||
match event {
|
||||
EventType::WebxdcStatusUpdate(msg_id) => {
|
||||
EventType::WebxdcStatusUpdate {
|
||||
msg_id,
|
||||
status_update_serial: _,
|
||||
} => {
|
||||
assert_eq!(msg_id, instance_id);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
||||
Reference in New Issue
Block a user