mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 05:22:14 +03:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0bfb555dd | ||
|
|
6ffaa38b37 | ||
|
|
339d46ecf0 | ||
|
|
5399c9151d | ||
|
|
53cd633e8d | ||
|
|
ade39fe026 | ||
|
|
b8dad1dbaf | ||
|
|
72d503fa32 | ||
|
|
223aeb7b1a | ||
|
|
b315c6f6d5 | ||
|
|
481276cf46 | ||
|
|
faab61b0d4 | ||
|
|
20bf41b4e6 | ||
|
|
5a5b80c960 | ||
|
|
ac245a6cb2 | ||
|
|
126beb62f3 | ||
|
|
d79e4a6571 | ||
|
|
cadc0b2c00 | ||
|
|
87071e6d4b | ||
|
|
057b004553 | ||
|
|
4071fe53a0 | ||
|
|
c3062976c0 | ||
|
|
85efc0ea26 | ||
|
|
4ef80aaea5 | ||
|
|
0f86800f5d | ||
|
|
9c2035538c | ||
|
|
0276938975 | ||
|
|
ee44a162b6 | ||
|
|
c09a83df2b | ||
|
|
8729d2c4aa | ||
|
|
fdf3397437 | ||
|
|
49fc72fa42 |
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.50.0
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.50.0
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
@@ -68,12 +68,23 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version, same as in `rust-toolchain` file.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.50.0
|
||||
python: 3.6
|
||||
rust: 1.54.0
|
||||
python: 3.9
|
||||
- os: windows-latest
|
||||
rust: 1.50.0
|
||||
rust: 1.54.0
|
||||
python: false # Python bindings compilation on Windows is not supported.
|
||||
|
||||
# Minimum Supported Rust Version = 1.48.0
|
||||
# This is the Debian "bullseye" release version of Rust.
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.48.0
|
||||
python: 3.7
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## 1.59.0
|
||||
|
||||
### Added
|
||||
- add quota information to `dc_get_connectivity_html()`
|
||||
|
||||
### Changes
|
||||
- refactorings #2592 #2570 #2581
|
||||
- add 'device chat about' to now existing status #2613
|
||||
- update provider database #2608
|
||||
|
||||
### Fixes
|
||||
- provider database supports socket=PLAIN and dotless domains now #2604 #2608
|
||||
- add migrated accounts to events emitter #2607
|
||||
- fix forwarding quote-only mails #2600
|
||||
- do not set WantsMdn param for outgoing messages #2603
|
||||
- set timestamps for system messages #2593
|
||||
- do not treat gmail labels as folders #2587
|
||||
- avoid timing problems in `dc_maybe_network_lost()` #2551
|
||||
- only set smtp to "connected" if the last message was actually sent #2541
|
||||
|
||||
|
||||
## 1.58.0
|
||||
|
||||
### Fixes
|
||||
|
||||
- move WAL file together with database
|
||||
and avoid using data if the database was not closed correctly before #2583
|
||||
|
||||
|
||||
## 1.57.0
|
||||
|
||||
### API Changes
|
||||
|
||||
97
Cargo.lock
generated
97
Cargo.lock
generated
@@ -1,5 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.15.1"
|
||||
@@ -236,8 +238,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbb2df4b37a99456360a9ab475b723e3a499d51e060ab1bdd8d7565d23dcb74b"
|
||||
source = "git+https://github.com/async-email/async-imap#4ce7da455618c387b87b2905a80935107bc69afc"
|
||||
dependencies = [
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
@@ -248,7 +249,7 @@ dependencies = [
|
||||
"imap-proto",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"nom 5.1.2",
|
||||
"nom 6.2.1",
|
||||
"pin-utils",
|
||||
"rental",
|
||||
"stop-token",
|
||||
@@ -516,9 +517,21 @@ checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.19.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@@ -1107,7 +1120,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.57.0"
|
||||
version = "1.59.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1132,6 +1145,7 @@ dependencies = [
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"humansize",
|
||||
"image",
|
||||
"indexmap",
|
||||
"itertools 0.10.1",
|
||||
@@ -1185,7 +1199,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.57.0"
|
||||
version = "1.59.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
@@ -1584,6 +1598,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.16"
|
||||
@@ -1914,6 +1934,12 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
@@ -1959,11 +1985,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "imap-proto"
|
||||
version = "0.11.0"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3091b99ee5b80f9b010eb6f962af9495ad06561bf662126b077e8ca30e463182"
|
||||
checksum = "3ad9b46a79efb6078e578ae04e51463d7c3e8767864687f7e63095b3cbefafbb"
|
||||
dependencies = [
|
||||
"nom 5.1.2",
|
||||
"nom 6.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2120,9 +2146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2341,6 +2367,19 @@ dependencies = [
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "6.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"funty",
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
@@ -2872,6 +2911,12 @@ dependencies = [
|
||||
"rusqlite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.2.1"
|
||||
@@ -3290,9 +3335,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3309,9 +3354,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.126"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
||||
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3649,6 +3694,12 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
@@ -3683,18 +3734,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
|
||||
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
|
||||
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4164,6 +4215,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "1.1.0"
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.57.0"
|
||||
version = "1.59.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
@@ -16,7 +16,7 @@ deltachat_derive = { path = "./deltachat_derive" }
|
||||
|
||||
ansi_term = { version = "0.12.1", optional = true }
|
||||
anyhow = "1.0.42"
|
||||
async-imap = "0.5.0"
|
||||
async-imap = { git = "https://github.com/async-email/async-imap" }
|
||||
async-native-tls = { version = "0.3.3" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", rev="c8800625f7cf29f437143ac7e720ac2730a0962f" }
|
||||
async-std-resolver = "0.20.3"
|
||||
@@ -25,7 +25,7 @@ async-tar = "0.3.0"
|
||||
async-trait = "0.1.50"
|
||||
backtrace = "0.3.59"
|
||||
base64 = "0.13"
|
||||
bitflags = "1.1.0"
|
||||
bitflags = "1.3.1"
|
||||
byteorder = "1.3.1"
|
||||
chrono = "0.4.6"
|
||||
dirs = { version = "3.0.2", optional=true }
|
||||
@@ -39,7 +39,7 @@ indexmap = "1.7.0"
|
||||
itertools = "0.10.1"
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2.97"
|
||||
libc = "0.2.98"
|
||||
log = {version = "0.4.8", optional = true }
|
||||
mailparse = "0.13.5"
|
||||
native-tls = "0.2.3"
|
||||
@@ -68,10 +68,11 @@ stop-token = "0.2.0"
|
||||
strum = "0.21.0"
|
||||
strum_macros = "0.21.1"
|
||||
surf = { version = "2.0.0-alpha.4", default-features = false, features = ["h1-client"] }
|
||||
thiserror = "1.0.25"
|
||||
thiserror = "1.0.26"
|
||||
toml = "0.5.6"
|
||||
url = "2.2.2"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
humansize = "1.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.57.0"
|
||||
version = "1.59.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
@@ -22,7 +22,7 @@ num-traits = "0.2.6"
|
||||
serde_json = "1.0"
|
||||
async-std = "1.9.0"
|
||||
anyhow = "1.0.42"
|
||||
thiserror = "1.0.25"
|
||||
thiserror = "1.0.26"
|
||||
rand = "0.7.3"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -30,6 +30,6 @@ fn main() {
|
||||
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
|
||||
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
|
||||
.unwrap()
|
||||
.write_all(&pkg_config.as_bytes())
|
||||
.write_all(pkg_config.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ pub unsafe extern "C" fn dc_set_config_from_qr(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match qr::set_config_from_qr(&ctx, &qr).await {
|
||||
match qr::set_config_from_qr(ctx, &qr).await {
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
error!(ctx, "Failed to create account from QR code: {}", err);
|
||||
@@ -303,7 +303,7 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
||||
let redirect = to_string_lossy(redirect);
|
||||
|
||||
block_on(async move {
|
||||
match oauth2::dc_get_oauth2_url(&ctx, &addr, &redirect).await {
|
||||
match oauth2::dc_get_oauth2_url(ctx, &addr, &redirect).await {
|
||||
Some(res) => res.strdup(),
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
@@ -608,7 +608,7 @@ pub unsafe extern "C" fn dc_preconfigure_keypair(
|
||||
public,
|
||||
secret,
|
||||
};
|
||||
key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default).await?;
|
||||
key::store_self_keypair(ctx, &keypair, key::KeyPairUse::Default).await?;
|
||||
Ok::<_, anyhow::Error>(1)
|
||||
})
|
||||
.log_err(ctx, "Failed to save keypair")
|
||||
@@ -632,7 +632,7 @@ pub unsafe extern "C" fn dc_get_chatlist(
|
||||
let qi = if query_id == 0 { None } else { Some(query_id) };
|
||||
|
||||
block_on(async move {
|
||||
match chatlist::Chatlist::try_load(&ctx, flags as usize, qs.as_deref(), qi).await {
|
||||
match chatlist::Chatlist::try_load(ctx, flags as usize, qs.as_deref(), qi).await {
|
||||
Ok(list) => {
|
||||
let ffi_list = ChatlistWrapper { context, list };
|
||||
Box::into_raw(Box::new(ffi_list))
|
||||
@@ -654,7 +654,7 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
ChatId::create_for_contact(&ctx, contact_id)
|
||||
ChatId::create_for_contact(ctx, contact_id)
|
||||
.await
|
||||
.log_err(ctx, "Failed to create chat from contact_id")
|
||||
.map(|id| id.to_u32())
|
||||
@@ -674,7 +674,7 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
ChatId::lookup_by_contact(&ctx, contact_id)
|
||||
ChatId::lookup_by_contact(ctx, contact_id)
|
||||
.await
|
||||
.log_err(ctx, "Failed to get chat for contact_id")
|
||||
.unwrap_or_default() // unwraps the Result
|
||||
@@ -697,9 +697,9 @@ pub unsafe extern "C" fn dc_prepare_msg(
|
||||
let ffi_msg: &mut MessageWrapper = &mut *msg;
|
||||
|
||||
block_on(async move {
|
||||
chat::prepare_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
chat::prepare_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to prepare message")
|
||||
.unwrap_or_log_default(ctx, "Failed to prepare message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -718,9 +718,9 @@ pub unsafe extern "C" fn dc_send_msg(
|
||||
let ffi_msg = &mut *msg;
|
||||
|
||||
block_on(async move {
|
||||
chat::send_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
chat::send_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to send message")
|
||||
.unwrap_or_log_default(ctx, "Failed to send message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -739,9 +739,9 @@ pub unsafe extern "C" fn dc_send_msg_sync(
|
||||
let ffi_msg = &mut *msg;
|
||||
|
||||
block_on(async move {
|
||||
chat::send_msg_sync(&ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
chat::send_msg_sync(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to send message")
|
||||
.unwrap_or_log_default(ctx, "Failed to send message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -760,10 +760,10 @@ pub unsafe extern "C" fn dc_send_text_msg(
|
||||
let text_to_send = to_string_lossy(text_to_send);
|
||||
|
||||
block_on(async move {
|
||||
chat::send_text_msg(&ctx, ChatId::new(chat_id), text_to_send)
|
||||
chat::send_text_msg(ctx, ChatId::new(chat_id), text_to_send)
|
||||
.await
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.unwrap_or_log_default(&ctx, "Failed to send text message")
|
||||
.unwrap_or_log_default(ctx, "Failed to send text message")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -779,10 +779,10 @@ pub unsafe extern "C" fn dc_send_videochat_invitation(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::send_videochat_invitation(&ctx, ChatId::new(chat_id))
|
||||
chat::send_videochat_invitation(ctx, ChatId::new(chat_id))
|
||||
.await
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.unwrap_or_log_default(&ctx, "Failed to send video chat invitation")
|
||||
.unwrap_or_log_default(ctx, "Failed to send video chat invitation")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -806,7 +806,7 @@ pub unsafe extern "C" fn dc_set_draft(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.set_draft(&ctx, msg)
|
||||
.set_draft(ctx, msg)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to set draft");
|
||||
});
|
||||
@@ -831,9 +831,9 @@ pub unsafe extern "C" fn dc_add_device_msg(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
chat::add_device_msg(&ctx, to_opt_string_lossy(label).as_deref(), msg)
|
||||
chat::add_device_msg(ctx, to_opt_string_lossy(label).as_deref(), msg)
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to add device message")
|
||||
.unwrap_or_log_default(ctx, "Failed to add device message")
|
||||
})
|
||||
.to_u32()
|
||||
}
|
||||
@@ -850,7 +850,7 @@ pub unsafe extern "C" fn dc_was_device_msg_ever_added(
|
||||
let ctx = &mut *context;
|
||||
|
||||
block_on(async move {
|
||||
chat::was_device_msg_ever_added(&ctx, &to_string_lossy(label))
|
||||
chat::was_device_msg_ever_added(ctx, &to_string_lossy(label))
|
||||
.await
|
||||
.unwrap_or(false) as libc::c_int
|
||||
})
|
||||
@@ -865,7 +865,7 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32)
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match ChatId::new(chat_id).get_draft(&ctx).await {
|
||||
match ChatId::new(chat_id).get_draft(ctx).await {
|
||||
Ok(Some(draft)) => {
|
||||
let ffi_msg = MessageWrapper {
|
||||
context,
|
||||
@@ -902,7 +902,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
|
||||
|
||||
block_on(async move {
|
||||
Box::into_raw(Box::new(
|
||||
chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag)
|
||||
chat::get_chat_msgs(ctx, ChatId::new(chat_id), flags, marker_flag)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get chat msgs")
|
||||
.into(),
|
||||
@@ -920,7 +920,7 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.get_msg_cnt(&ctx)
|
||||
.get_msg_cnt(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get msg count") as libc::c_int
|
||||
})
|
||||
@@ -939,7 +939,7 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.get_fresh_msg_cnt(&ctx)
|
||||
.get_fresh_msg_cnt(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get fresh msg cnt") as libc::c_int
|
||||
})
|
||||
@@ -996,7 +996,7 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::marknoticed_chat(&ctx, ChatId::new(chat_id))
|
||||
chat::marknoticed_chat(ctx, ChatId::new(chat_id))
|
||||
.await
|
||||
.log_err(ctx, "Failed marknoticed chat")
|
||||
.unwrap_or(())
|
||||
@@ -1033,7 +1033,7 @@ pub unsafe extern "C" fn dc_get_chat_media(
|
||||
block_on(async move {
|
||||
Box::into_raw(Box::new(
|
||||
chat::get_chat_media(
|
||||
&ctx,
|
||||
ctx,
|
||||
ChatId::new(chat_id),
|
||||
msg_type,
|
||||
or_msg_type2,
|
||||
@@ -1074,7 +1074,7 @@ pub unsafe extern "C" fn dc_get_next_media(
|
||||
|
||||
block_on(async move {
|
||||
chat::get_next_media(
|
||||
&ctx,
|
||||
ctx,
|
||||
MsgId::new(msg_id),
|
||||
direction,
|
||||
msg_type,
|
||||
@@ -1106,7 +1106,7 @@ pub unsafe extern "C" fn dc_set_chat_protection(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
match ChatId::new(chat_id).set_protection(&ctx, protect).await {
|
||||
match ChatId::new(chat_id).set_protection(ctx, protect).await {
|
||||
Ok(()) => 1,
|
||||
Err(_) => 0,
|
||||
}
|
||||
@@ -1139,7 +1139,7 @@ pub unsafe extern "C" fn dc_set_chat_visibility(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.set_visibility(&ctx, visibility)
|
||||
.set_visibility(ctx, visibility)
|
||||
.await
|
||||
.log_err(ctx, "Failed setting chat visibility")
|
||||
.unwrap_or(())
|
||||
@@ -1156,7 +1156,7 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.delete(&ctx)
|
||||
.delete(ctx)
|
||||
.await
|
||||
.ok_or_log_msg(ctx, "Failed chat delete");
|
||||
})
|
||||
@@ -1172,7 +1172,7 @@ pub unsafe extern "C" fn dc_block_chat(context: *mut dc_context_t, chat_id: u32)
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.block(&ctx)
|
||||
.block(ctx)
|
||||
.await
|
||||
.ok_or_log_msg(ctx, "Failed chat block");
|
||||
})
|
||||
@@ -1188,7 +1188,7 @@ pub unsafe extern "C" fn dc_accept_chat(context: *mut dc_context_t, chat_id: u32
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.accept(&ctx)
|
||||
.accept(ctx)
|
||||
.await
|
||||
.ok_or_log_msg(ctx, "Failed chat accept");
|
||||
})
|
||||
@@ -1207,7 +1207,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
|
||||
|
||||
block_on(async move {
|
||||
let arr = dc_array_t::from(
|
||||
chat::get_chat_contacts(&ctx, ChatId::new(chat_id))
|
||||
chat::get_chat_contacts(ctx, ChatId::new(chat_id))
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "Failed get_chat_contacts"),
|
||||
);
|
||||
@@ -1254,7 +1254,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await {
|
||||
match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
|
||||
Ok(chat) => {
|
||||
let ffi_chat = ChatWrapper { context, chat };
|
||||
Box::into_raw(Box::new(ffi_chat))
|
||||
@@ -1283,7 +1283,7 @@ pub unsafe extern "C" fn dc_create_group_chat(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
chat::create_group_chat(&ctx, protect, &to_string_lossy(name))
|
||||
chat::create_group_chat(ctx, protect, &to_string_lossy(name))
|
||||
.await
|
||||
.log_err(ctx, "Failed to create group chat")
|
||||
.map(|id| id.to_u32())
|
||||
@@ -1303,7 +1303,7 @@ pub unsafe extern "C" fn dc_is_contact_in_chat(
|
||||
}
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move { chat::is_contact_in_chat(&ctx, ChatId::new(chat_id), contact_id).await })
|
||||
block_on(async move { chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id).await })
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1320,7 +1320,7 @@ pub unsafe extern "C" fn dc_add_contact_to_chat(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::add_contact_to_chat(&ctx, ChatId::new(chat_id), contact_id).await as libc::c_int
|
||||
chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id).await as libc::c_int
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1337,10 +1337,10 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::remove_contact_from_chat(&ctx, ChatId::new(chat_id), contact_id)
|
||||
chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id)
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to remove contact")
|
||||
.unwrap_or_log_default(ctx, "Failed to remove contact")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1358,10 +1358,10 @@ pub unsafe extern "C" fn dc_set_chat_name(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::set_chat_name(&ctx, ChatId::new(chat_id), &to_string_lossy(name))
|
||||
chat::set_chat_name(ctx, ChatId::new(chat_id), &to_string_lossy(name))
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to set chat name")
|
||||
.unwrap_or_log_default(ctx, "Failed to set chat name")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1378,10 +1378,10 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::set_chat_profile_image(&ctx, ChatId::new(chat_id), to_string_lossy(image))
|
||||
chat::set_chat_profile_image(ctx, ChatId::new(chat_id), to_string_lossy(image))
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to set profile image")
|
||||
.unwrap_or_log_default(ctx, "Failed to set profile image")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1412,10 +1412,10 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
chat::set_muted(&ctx, ChatId::new(chat_id), muteDuration)
|
||||
chat::set_muted(ctx, ChatId::new(chat_id), muteDuration)
|
||||
.await
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(&ctx, "Failed to set mute duration")
|
||||
.unwrap_or_log_default(ctx, "Failed to set mute duration")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1432,11 +1432,11 @@ pub unsafe extern "C" fn dc_get_chat_encrinfo(
|
||||
|
||||
block_on(async move {
|
||||
ChatId::new(chat_id)
|
||||
.get_encryption_info(&ctx)
|
||||
.get_encryption_info(ctx)
|
||||
.await
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|e| {
|
||||
error!(&ctx, "{}", e);
|
||||
error!(ctx, "{}", e);
|
||||
ptr::null_mut()
|
||||
})
|
||||
})
|
||||
@@ -1497,7 +1497,7 @@ pub unsafe extern "C" fn dc_get_msg_info(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
message::get_msg_info(&ctx, MsgId::new(msg_id))
|
||||
message::get_msg_info(ctx, MsgId::new(msg_id))
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get msg id")
|
||||
.strdup()
|
||||
@@ -1515,7 +1515,7 @@ pub unsafe extern "C" fn dc_get_msg_html(
|
||||
}
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(MsgId::new(msg_id).get_html(&ctx))
|
||||
block_on(MsgId::new(msg_id).get_html(ctx))
|
||||
.unwrap_or_log_default(ctx, "Failed get_msg_html")
|
||||
.strdup()
|
||||
}
|
||||
@@ -1532,7 +1532,7 @@ pub unsafe extern "C" fn dc_get_mime_headers(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let mime = message::get_mime_headers(&ctx, MsgId::new(msg_id))
|
||||
let mime = message::get_mime_headers(ctx, MsgId::new(msg_id))
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get mime headers");
|
||||
if mime.is_empty() {
|
||||
@@ -1555,7 +1555,7 @@ pub unsafe extern "C" fn dc_delete_msgs(
|
||||
let ctx = &*context;
|
||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
||||
|
||||
block_on(message::delete_msgs(&ctx, &msg_ids))
|
||||
block_on(message::delete_msgs(ctx, &msg_ids))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -1577,9 +1577,9 @@ pub unsafe extern "C" fn dc_forward_msgs(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
chat::forward_msgs(&ctx, &msg_ids[..], ChatId::new(chat_id))
|
||||
chat::forward_msgs(ctx, &msg_ids[..], ChatId::new(chat_id))
|
||||
.await
|
||||
.unwrap_or_log_default(&ctx, "Failed to forward message")
|
||||
.unwrap_or_log_default(ctx, "Failed to forward message")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1596,7 +1596,7 @@ pub unsafe extern "C" fn dc_markseen_msgs(
|
||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(message::markseen_msgs(&ctx, msg_ids))
|
||||
block_on(message::markseen_msgs(ctx, msg_ids))
|
||||
.log_err(ctx, "failed dc_markseen_msgs() call")
|
||||
.ok();
|
||||
}
|
||||
@@ -1610,19 +1610,19 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let message = match message::Message::load_from_db(&ctx, MsgId::new(msg_id)).await {
|
||||
let message = match message::Message::load_from_db(ctx, MsgId::new(msg_id)).await {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
if msg_id <= constants::DC_MSG_ID_LAST_SPECIAL {
|
||||
// C-core API returns empty messages, do the same
|
||||
warn!(
|
||||
&ctx,
|
||||
ctx,
|
||||
"dc_get_msg called with special msg_id={}, returning empty msg", msg_id
|
||||
);
|
||||
message::Message::default()
|
||||
} else {
|
||||
error!(
|
||||
&ctx,
|
||||
ctx,
|
||||
"dc_get_msg could not retrieve msg_id {}: {}", msg_id, e
|
||||
);
|
||||
return ptr::null_mut();
|
||||
@@ -1656,7 +1656,7 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::lookup_id_by_addr(&ctx, to_string_lossy(addr), Origin::IncomingReplyTo)
|
||||
Contact::lookup_id_by_addr(ctx, to_string_lossy(addr), Origin::IncomingReplyTo)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to lookup id")
|
||||
.unwrap_or(0)
|
||||
@@ -1677,7 +1677,7 @@ pub unsafe extern "C" fn dc_create_contact(
|
||||
let name = to_string_lossy(name);
|
||||
|
||||
block_on(async move {
|
||||
Contact::create(&ctx, &name, &to_string_lossy(addr))
|
||||
Contact::create(ctx, &name, &to_string_lossy(addr))
|
||||
.await
|
||||
.unwrap_or(0)
|
||||
})
|
||||
@@ -1695,7 +1695,7 @@ pub unsafe extern "C" fn dc_add_address_book(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match Contact::add_address_book(&ctx, &to_string_lossy(addr_book)).await {
|
||||
match Contact::add_address_book(ctx, &to_string_lossy(addr_book)).await {
|
||||
Ok(cnt) => cnt as libc::c_int,
|
||||
Err(_) => 0,
|
||||
}
|
||||
@@ -1716,7 +1716,7 @@ pub unsafe extern "C" fn dc_get_contacts(
|
||||
let query = to_opt_string_lossy(query);
|
||||
|
||||
block_on(async move {
|
||||
match Contact::get_all(&ctx, flags, query).await {
|
||||
match Contact::get_all(ctx, flags, query).await {
|
||||
Ok(contacts) => Box::into_raw(Box::new(dc_array_t::from(contacts))),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
@@ -1732,7 +1732,7 @@ pub unsafe extern "C" fn dc_get_blocked_cnt(context: *mut dc_context_t) -> libc:
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::get_all_blocked(&ctx)
|
||||
Contact::get_all_blocked(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get blocked count")
|
||||
.len() as libc::c_int
|
||||
@@ -1751,9 +1751,9 @@ pub unsafe extern "C" fn dc_get_blocked_contacts(
|
||||
|
||||
block_on(async move {
|
||||
Box::into_raw(Box::new(dc_array_t::from(
|
||||
Contact::get_all_blocked(&ctx)
|
||||
Contact::get_all_blocked(ctx)
|
||||
.await
|
||||
.log_err(&ctx, "Can't get blocked contacts")
|
||||
.log_err(ctx, "Can't get blocked contacts")
|
||||
.unwrap_or_default(),
|
||||
)))
|
||||
})
|
||||
@@ -1772,13 +1772,13 @@ pub unsafe extern "C" fn dc_block_contact(
|
||||
let ctx = &*context;
|
||||
block_on(async move {
|
||||
if block == 0 {
|
||||
Contact::unblock(&ctx, contact_id)
|
||||
Contact::unblock(ctx, contact_id)
|
||||
.await
|
||||
.ok_or_log_msg(&ctx, "Can't unblock contact");
|
||||
.ok_or_log_msg(ctx, "Can't unblock contact");
|
||||
} else {
|
||||
Contact::block(&ctx, contact_id)
|
||||
Contact::block(ctx, contact_id)
|
||||
.await
|
||||
.ok_or_log_msg(&ctx, "Can't block contact");
|
||||
.ok_or_log_msg(ctx, "Can't block contact");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1795,11 +1795,11 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::get_encrinfo(&ctx, contact_id)
|
||||
Contact::get_encrinfo(ctx, contact_id)
|
||||
.await
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|e| {
|
||||
error!(&ctx, "{}", e);
|
||||
error!(ctx, "{}", e);
|
||||
ptr::null_mut()
|
||||
})
|
||||
})
|
||||
@@ -1817,7 +1817,7 @@ pub unsafe extern "C" fn dc_delete_contact(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match Contact::delete(&ctx, contact_id).await {
|
||||
match Contact::delete(ctx, contact_id).await {
|
||||
Ok(_) => 1,
|
||||
Err(_) => 0,
|
||||
}
|
||||
@@ -1836,7 +1836,7 @@ pub unsafe extern "C" fn dc_get_contact(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
Contact::get_by_id(&ctx, contact_id)
|
||||
Contact::get_by_id(ctx, contact_id)
|
||||
.await
|
||||
.map(|contact| Box::into_raw(Box::new(ContactWrapper { context, contact })))
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
@@ -1866,7 +1866,7 @@ pub unsafe extern "C" fn dc_imex(
|
||||
|
||||
if let Some(param1) = to_opt_string_lossy(param1) {
|
||||
spawn(async move {
|
||||
imex::imex(&ctx, what, param1.as_ref())
|
||||
imex::imex(ctx, what, param1.as_ref())
|
||||
.await
|
||||
.log_err(ctx, "IMEX failed")
|
||||
});
|
||||
@@ -1887,12 +1887,12 @@ pub unsafe extern "C" fn dc_imex_has_backup(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match imex::has_backup(&ctx, to_string_lossy(dir).as_ref()).await {
|
||||
match imex::has_backup(ctx, to_string_lossy(dir).as_ref()).await {
|
||||
Ok(res) => res.strdup(),
|
||||
Err(err) => {
|
||||
// do not bubble up error to the user,
|
||||
// the ui will expect that the file does not exist or cannot be accessed
|
||||
warn!(&ctx, "dc_imex_has_backup: {}", err);
|
||||
warn!(ctx, "dc_imex_has_backup: {}", err);
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -1908,10 +1908,10 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match imex::initiate_key_transfer(&ctx).await {
|
||||
match imex::initiate_key_transfer(ctx).await {
|
||||
Ok(res) => res.strdup(),
|
||||
Err(err) => {
|
||||
error!(&ctx, "dc_initiate_key_transfer(): {}", err);
|
||||
error!(ctx, "dc_initiate_key_transfer(): {}", err);
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -1934,12 +1934,12 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
match imex::continue_key_transfer(&ctx, MsgId::new(msg_id), &to_string_lossy(setup_code))
|
||||
match imex::continue_key_transfer(ctx, MsgId::new(msg_id), &to_string_lossy(setup_code))
|
||||
.await
|
||||
{
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
warn!(&ctx, "dc_continue_key_transfer: {}", err);
|
||||
warn!(ctx, "dc_continue_key_transfer: {}", err);
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -1968,7 +1968,7 @@ pub unsafe extern "C" fn dc_check_qr(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let lot = qr::check_qr(&ctx, &to_string_lossy(qr)).await;
|
||||
let lot = qr::check_qr(ctx, &to_string_lossy(qr)).await;
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
}
|
||||
@@ -1990,7 +1990,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
|
||||
};
|
||||
|
||||
block_on(async move {
|
||||
securejoin::dc_get_securejoin_qr(&ctx, chat_id)
|
||||
securejoin::dc_get_securejoin_qr(ctx, chat_id)
|
||||
.await
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.strdup()
|
||||
@@ -2009,7 +2009,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
securejoin::dc_join_securejoin(&ctx, &to_string_lossy(qr))
|
||||
securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr))
|
||||
.await
|
||||
.map(|chatid| chatid.to_u32())
|
||||
.log_err(ctx, "failed dc_join_securejoin() call")
|
||||
@@ -2030,7 +2030,7 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(location::send_locations_to_chat(
|
||||
&ctx,
|
||||
ctx,
|
||||
ChatId::new(chat_id),
|
||||
seconds as i64,
|
||||
));
|
||||
@@ -2052,7 +2052,7 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat(
|
||||
Some(ChatId::new(chat_id))
|
||||
};
|
||||
|
||||
block_on(location::is_sending_locations_to_chat(&ctx, chat_id)) as libc::c_int
|
||||
block_on(location::is_sending_locations_to_chat(ctx, chat_id)) as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2068,7 +2068,7 @@ pub unsafe extern "C" fn dc_set_location(
|
||||
}
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(location::set(&ctx, latitude, longitude, accuracy)) as _
|
||||
block_on(location::set(ctx, latitude, longitude, accuracy)) as _
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2097,7 +2097,7 @@ pub unsafe extern "C" fn dc_get_locations(
|
||||
|
||||
block_on(async move {
|
||||
let res = location::get_range(
|
||||
&ctx,
|
||||
ctx,
|
||||
chat_id,
|
||||
contact_id,
|
||||
timestamp_begin as i64,
|
||||
@@ -2118,7 +2118,7 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) {
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
location::delete_all(&ctx)
|
||||
location::delete_all(ctx)
|
||||
.await
|
||||
.log_err(ctx, "Failed to delete locations")
|
||||
.ok()
|
||||
@@ -2385,7 +2385,7 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
|
||||
block_on(async move {
|
||||
let lot = ffi_list
|
||||
.list
|
||||
.get_summary(&ctx, index as usize, maybe_chat)
|
||||
.get_summary(ctx, index as usize, maybe_chat)
|
||||
.await
|
||||
.log_err(ctx, "get_summary failed")
|
||||
.unwrap_or_default();
|
||||
@@ -2410,7 +2410,7 @@ pub unsafe extern "C" fn dc_chatlist_get_summary2(
|
||||
Some(MsgId::new(msg_id))
|
||||
};
|
||||
block_on(async move {
|
||||
let lot = Chatlist::get_summary2(&ctx, ChatId::new(chat_id), msg_id, None)
|
||||
let lot = Chatlist::get_summary2(ctx, ChatId::new(chat_id), msg_id, None)
|
||||
.await
|
||||
.log_err(ctx, "get_summary2 failed")
|
||||
.unwrap_or_default();
|
||||
@@ -2496,7 +2496,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
|
||||
let ctx = &*ffi_chat.context;
|
||||
|
||||
block_on(async move {
|
||||
match ffi_chat.chat.get_profile_image(&ctx).await {
|
||||
match ffi_chat.chat.get_profile_image(ctx).await {
|
||||
Ok(Some(p)) => p.to_string_lossy().strdup(),
|
||||
Ok(None) => ptr::null_mut(),
|
||||
Err(err) => {
|
||||
@@ -2516,7 +2516,7 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 {
|
||||
let ffi_chat = &*chat;
|
||||
let ctx = &*ffi_chat.context;
|
||||
|
||||
block_on(ffi_chat.chat.get_color(&ctx)).unwrap_or_log_default(ctx, "Failed get_color")
|
||||
block_on(ffi_chat.chat.get_color(ctx)).unwrap_or_log_default(ctx, "Failed get_color")
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2647,25 +2647,25 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
|
||||
let ctx = &*context;
|
||||
|
||||
block_on(async move {
|
||||
let chat = match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await {
|
||||
let chat = match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
|
||||
Ok(chat) => chat,
|
||||
Err(err) => {
|
||||
error!(&ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
|
||||
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
let info = match chat.get_info(&ctx).await {
|
||||
let info = match chat.get_info(ctx).await {
|
||||
Ok(info) => info,
|
||||
Err(err) => {
|
||||
error!(
|
||||
&ctx,
|
||||
ctx,
|
||||
"dc_get_chat_info_json() failed to get chat info: {}", err
|
||||
);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
serde_json::to_string(&info)
|
||||
.unwrap_or_log_default(&ctx, "dc_get_chat_info_json() failed to serialise to json")
|
||||
.unwrap_or_log_default(ctx, "dc_get_chat_info_json() failed to serialise to json")
|
||||
.strdup()
|
||||
})
|
||||
}
|
||||
@@ -2866,7 +2866,7 @@ pub unsafe extern "C" fn dc_msg_get_filebytes(msg: *mut dc_msg_t) -> u64 {
|
||||
let ffi_msg = &*msg;
|
||||
let ctx = &*ffi_msg.context;
|
||||
|
||||
block_on(ffi_msg.message.get_filebytes(&ctx))
|
||||
block_on(ffi_msg.message.get_filebytes(ctx))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2958,7 +2958,7 @@ pub unsafe extern "C" fn dc_msg_get_summary(
|
||||
let ctx = &*ffi_msg.context;
|
||||
|
||||
block_on(async move {
|
||||
let lot = ffi_msg.message.get_summary(&ctx, maybe_chat).await;
|
||||
let lot = ffi_msg.message.get_summary(ctx, maybe_chat).await;
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
}
|
||||
@@ -2978,7 +2978,7 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
|
||||
block_on({
|
||||
ffi_msg
|
||||
.message
|
||||
.get_summarytext(&ctx, approx_characters.try_into().unwrap_or_default())
|
||||
.get_summarytext(ctx, approx_characters.try_into().unwrap_or_default())
|
||||
})
|
||||
.strdup()
|
||||
}
|
||||
@@ -3118,7 +3118,7 @@ pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut l
|
||||
let ffi_msg = &*msg;
|
||||
let ctx = &*ffi_msg.context;
|
||||
|
||||
block_on(ffi_msg.message.get_setupcodebegin(&ctx))
|
||||
block_on(ffi_msg.message.get_setupcodebegin(ctx))
|
||||
.unwrap_or_default()
|
||||
.strdup()
|
||||
}
|
||||
@@ -3230,7 +3230,7 @@ pub unsafe extern "C" fn dc_msg_latefiling_mediasize(
|
||||
block_on({
|
||||
ffi_msg
|
||||
.message
|
||||
.latefiling_mediasize(&ctx, width, height, duration)
|
||||
.latefiling_mediasize(ctx, width, height, duration)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3410,7 +3410,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
|
||||
block_on(async move {
|
||||
ffi_contact
|
||||
.contact
|
||||
.get_profile_image(&ctx)
|
||||
.get_profile_image(ctx)
|
||||
.await
|
||||
.unwrap_or_log_default(ctx, "failed to get profile image")
|
||||
.map(|p| p.to_string_lossy().strdup())
|
||||
@@ -3457,7 +3457,7 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l
|
||||
let ffi_contact = &*contact;
|
||||
let ctx = &*ffi_contact.context;
|
||||
|
||||
block_on(async move { ffi_contact.contact.is_verified(&ctx).await as libc::c_int })
|
||||
block_on(async move { ffi_contact.contact.is_verified(ctx).await as libc::c_int })
|
||||
}
|
||||
|
||||
// dc_lot_t
|
||||
|
||||
@@ -17,15 +17,12 @@ use std::ptr;
|
||||
/// }
|
||||
/// ```
|
||||
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char;
|
||||
if !s.is_null() {
|
||||
ret = libc::strdup(s);
|
||||
assert!(!ret.is_null());
|
||||
let ret: *mut libc::c_char = if !s.is_null() {
|
||||
libc::strdup(s)
|
||||
} else {
|
||||
ret = libc::calloc(1, 1) as *mut libc::c_char;
|
||||
assert!(!ret.is_null());
|
||||
}
|
||||
|
||||
libc::calloc(1, 1) as *mut libc::c_char
|
||||
};
|
||||
assert!(!ret.is_null());
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.50.0
|
||||
1.54.0
|
||||
|
||||
@@ -6,6 +6,14 @@ resources:
|
||||
branch: master
|
||||
uri: https://github.com/deltachat/deltachat-core-rust.git
|
||||
|
||||
- name: deltachat-core-rust-release
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
branch: master
|
||||
uri: https://github.com/deltachat/deltachat-core-rust.git
|
||||
tag_filter: "py-*"
|
||||
|
||||
jobs:
|
||||
- name: doxygen
|
||||
plan:
|
||||
@@ -58,12 +66,15 @@ jobs:
|
||||
- name: python-x86_64
|
||||
plan:
|
||||
- get: deltachat-core-rust
|
||||
- get: deltachat-core-rust-release
|
||||
trigger: true
|
||||
|
||||
# Build manylinux image with additional dependencies
|
||||
- task: build-coredeps
|
||||
privileged: true
|
||||
config:
|
||||
inputs:
|
||||
# Building the latest, not tagged coredeps
|
||||
- name: deltachat-core-rust
|
||||
image_resource:
|
||||
source:
|
||||
@@ -86,7 +97,7 @@ jobs:
|
||||
image: coredeps-image
|
||||
config:
|
||||
inputs:
|
||||
- name: deltachat-core-rust
|
||||
- name: deltachat-core-rust-release
|
||||
path: .
|
||||
outputs:
|
||||
- name: py-docs
|
||||
@@ -154,12 +165,15 @@ jobs:
|
||||
- name: python-aarch64
|
||||
plan:
|
||||
- get: deltachat-core-rust
|
||||
- get: deltachat-core-rust-release
|
||||
trigger: true
|
||||
|
||||
# Build manylinux image with additional dependencies
|
||||
- task: build-coredeps
|
||||
privileged: true
|
||||
config:
|
||||
inputs:
|
||||
# Building the latest, not tagged coredeps
|
||||
- name: deltachat-core-rust
|
||||
image_resource:
|
||||
source:
|
||||
@@ -182,7 +196,7 @@ jobs:
|
||||
image: coredeps-image
|
||||
config:
|
||||
inputs:
|
||||
- name: deltachat-core-rust
|
||||
- name: deltachat-core-rust-release
|
||||
path: .
|
||||
outputs:
|
||||
- name: py-wheels
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
PERL_VERSION=5.30.0
|
||||
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
|
||||
PERL_VERSION=5.34.0
|
||||
# PERL_SHA256=551efc818b968b05216024fb0b727ef2ad4c100f8cb6b43fab615fa78ae5be9a
|
||||
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
|
||||
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
|
||||
tar -xzf perl-${PERL_VERSION}.tar.gz
|
||||
|
||||
@@ -8,9 +8,11 @@ set -e -x
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
curl "https://static.rust-lang.org/dist/rust-1.52.1-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-1.52.1-$(uname -m)-unknown-linux-gnu"
|
||||
RUST_VERSION=1.54.0
|
||||
|
||||
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
|
||||
rustc --version
|
||||
cd ..
|
||||
rm -fr "rust-1.52.1-$(uname -m)-unknown-linux-gnu"
|
||||
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
PERL_VERSION=5.30.0
|
||||
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
|
||||
PERL_VERSION=5.34.0
|
||||
# PERL_SHA256=551efc818b968b05216024fb0b727ef2ad4c100f8cb6b43fab615fa78ae5be9a
|
||||
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
|
||||
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
|
||||
tar -xzf perl-${PERL_VERSION}.tar.gz
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
set -e -x
|
||||
|
||||
# Install Rust
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain "1.50.0-$(uname -m)-unknown-linux-gnu" -y
|
||||
export PATH=/root/.cargo/bin:$PATH
|
||||
rustc --version
|
||||
#
|
||||
# Path from https://forge.rust-lang.org/infra/other-installation-methods.html
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
RUST_VERSION=1.54.0
|
||||
|
||||
# remove some 300-400 MB that we don't need for automated builds
|
||||
rm -rf "/root/.rustup/toolchains/1.50.0-$(uname -m)-unknown-linux-gnu/share"
|
||||
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
|
||||
rustc --version
|
||||
cd ..
|
||||
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
|
||||
@@ -78,6 +78,14 @@ impl Accounts {
|
||||
self.accounts.read().await.get(&id).cloned()
|
||||
}
|
||||
|
||||
/// Returns the currently selected account's id or None if no account is selected.
|
||||
pub async fn get_selected_account_id(&self) -> Option<u32> {
|
||||
match self.config.get_selected_account().await {
|
||||
0 => None,
|
||||
id => Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the given account.
|
||||
pub async fn select_account(&self, id: u32) -> Result<()> {
|
||||
self.config.select_account(id).await?;
|
||||
@@ -118,6 +126,7 @@ impl Accounts {
|
||||
/// Migrate an existing account into this structure.
|
||||
pub async fn migrate_account(&self, dbfile: PathBuf) -> Result<u32> {
|
||||
let blobdir = Context::derive_blobdir(&dbfile);
|
||||
let walfile = Context::derive_walfile(&dbfile);
|
||||
|
||||
ensure!(
|
||||
dbfile.exists().await,
|
||||
@@ -141,6 +150,7 @@ impl Accounts {
|
||||
|
||||
let new_dbfile = account_config.dbfile().into();
|
||||
let new_blobdir = Context::derive_blobdir(&new_dbfile);
|
||||
let new_walfile = Context::derive_walfile(&new_dbfile);
|
||||
|
||||
let res = {
|
||||
fs::create_dir_all(&account_config.dir)
|
||||
@@ -152,6 +162,11 @@ impl Accounts {
|
||||
fs::rename(&blobdir, &new_blobdir)
|
||||
.await
|
||||
.context("failed to rename blobdir")?;
|
||||
if walfile.exists().await {
|
||||
fs::rename(&walfile, &new_walfile)
|
||||
.await
|
||||
.context("failed to rename walfile")?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
@@ -164,6 +179,7 @@ impl Accounts {
|
||||
account_config.id,
|
||||
)
|
||||
.await?;
|
||||
self.emitter.add_account(&ctx).await?;
|
||||
self.accounts.write().await.insert(account_config.id, ctx);
|
||||
Ok(account_config.id)
|
||||
}
|
||||
|
||||
35
src/chat.rs
35
src/chat.rs
@@ -381,7 +381,14 @@ impl ChatId {
|
||||
msg.param.set_cmd(cmd);
|
||||
send_msg(context, self, &mut msg).await?;
|
||||
} else {
|
||||
add_info_msg_with_cmd(context, self, msg_text, cmd).await?;
|
||||
add_info_msg_with_cmd(
|
||||
context,
|
||||
self,
|
||||
msg_text,
|
||||
cmd,
|
||||
dc_create_smeared_timestamp(context).await,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1157,7 +1164,7 @@ impl Chat {
|
||||
}
|
||||
|
||||
// the whole list of messages referenced may be huge;
|
||||
// only use the oldest and and the parent message
|
||||
// only use the oldest and the parent message
|
||||
let parent_references = parent_references
|
||||
.find(' ')
|
||||
.and_then(|n| parent_references.get(..n))
|
||||
@@ -1313,7 +1320,7 @@ impl rusqlite::types::FromSql for ChatVisibility {
|
||||
2 => ChatVisibility::Pinned,
|
||||
1 => ChatVisibility::Archived,
|
||||
0 => ChatVisibility::Normal,
|
||||
// fallback to to Normal for unknown values, may happen eg. on imports created by a newer version.
|
||||
// fallback to Normal for unknown values, may happen eg. on imports created by a newer version.
|
||||
_ => ChatVisibility::Normal,
|
||||
}
|
||||
})
|
||||
@@ -2808,10 +2815,10 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
|
||||
pub(crate) async fn get_chat_id_by_grpid(
|
||||
context: &Context,
|
||||
grpid: impl AsRef<str>,
|
||||
) -> Result<(ChatId, bool, Blocked)> {
|
||||
) -> Result<Option<(ChatId, bool, Blocked)>> {
|
||||
context
|
||||
.sql
|
||||
.query_row(
|
||||
.query_row_optional(
|
||||
"SELECT id, blocked, protected FROM chats WHERE grpid=?;",
|
||||
paramsv![grpid.as_ref()],
|
||||
|row| {
|
||||
@@ -2981,6 +2988,7 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
chat_id: ChatId,
|
||||
text: impl AsRef<str>,
|
||||
cmd: SystemMessage,
|
||||
timestamp: i64,
|
||||
) -> Result<MsgId> {
|
||||
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
|
||||
let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
|
||||
@@ -2997,7 +3005,7 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
chat_id,
|
||||
DC_CONTACT_ID_INFO,
|
||||
DC_CONTACT_ID_INFO,
|
||||
dc_create_smeared_timestamp(context).await,
|
||||
timestamp,
|
||||
Viewtype::Text,
|
||||
MessageState::InNoticed,
|
||||
text.as_ref().to_string(),
|
||||
@@ -3012,8 +3020,16 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef<str>) {
|
||||
if let Err(e) = add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown).await {
|
||||
/// Adds info message with a given text and `timestamp` to the chat.
|
||||
pub(crate) async fn add_info_msg(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
text: impl AsRef<str>,
|
||||
timestamp: i64,
|
||||
) {
|
||||
if let Err(e) =
|
||||
add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await
|
||||
{
|
||||
warn!(context, "Could not add info msg: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -3637,7 +3653,7 @@ mod tests {
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
.await
|
||||
.unwrap();
|
||||
add_info_msg(&t, chat_id, "foo info").await;
|
||||
add_info_msg(&t, chat_id, "foo info", 200000).await;
|
||||
|
||||
let msg = t.get_last_msg_in(chat_id).await;
|
||||
assert_eq!(msg.get_chat_id(), chat_id);
|
||||
@@ -3658,6 +3674,7 @@ mod tests {
|
||||
chat_id,
|
||||
"foo bar info",
|
||||
SystemMessage::EphemeralTimerChanged,
|
||||
10000,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -218,6 +218,7 @@ impl Contact {
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
contact.name = stock_str::device_messages(context).await;
|
||||
contact.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
|
||||
contact.status = stock_str::device_messages_hint(context).await;
|
||||
}
|
||||
Ok(contact)
|
||||
}
|
||||
@@ -1202,7 +1203,8 @@ WHERE type=? AND id IN (
|
||||
// also unblock mailinglist
|
||||
// if the contact is a mailinglist address explicitly created to allow unblocking
|
||||
if !new_blocking && contact.origin == Origin::MailinglistAddress {
|
||||
if let Ok((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await {
|
||||
if let Some((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await?
|
||||
{
|
||||
chat_id.unblock(context).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::events::{Event, EventEmitter, EventType, Events};
|
||||
use crate::key::{DcKey, SignedPublicKey};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{self, MessageState, MsgId};
|
||||
use crate::quota::QuotaInfo;
|
||||
use crate::scheduler::Scheduler;
|
||||
use crate::securejoin::Bob;
|
||||
use crate::sql::Sql;
|
||||
@@ -62,6 +63,10 @@ pub struct InnerContext {
|
||||
pub(crate) scheduler: RwLock<Scheduler>,
|
||||
pub(crate) ephemeral_task: RwLock<Option<task::JoinHandle<()>>>,
|
||||
|
||||
/// Recently loaded quota information, if any.
|
||||
/// Set to `None` if quota was never tried to load.
|
||||
pub(crate) quota: RwLock<Option<QuotaInfo>>,
|
||||
|
||||
pub(crate) last_full_folder_scan: Mutex<Option<Instant>>,
|
||||
|
||||
/// ID for this `Context` in the current process.
|
||||
@@ -139,6 +144,7 @@ impl Context {
|
||||
events: Events::default(),
|
||||
scheduler: RwLock::new(Scheduler::Stopped),
|
||||
ephemeral_task: RwLock::new(None),
|
||||
quota: RwLock::new(None),
|
||||
creation_time: std::time::SystemTime::now(),
|
||||
last_full_folder_scan: Mutex::new(None),
|
||||
};
|
||||
@@ -559,6 +565,13 @@ impl Context {
|
||||
blob_fname.push("-blobs");
|
||||
dbfile.with_file_name(blob_fname)
|
||||
}
|
||||
|
||||
pub fn derive_walfile(dbfile: &PathBuf) -> PathBuf {
|
||||
let mut wal_fname = OsString::new();
|
||||
wal_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
wal_fname.push("-wal");
|
||||
dbfile.with_file_name(wal_fname)
|
||||
}
|
||||
}
|
||||
|
||||
impl InnerContext {
|
||||
|
||||
@@ -371,7 +371,7 @@ async fn add_parts(
|
||||
prevent_rename: bool,
|
||||
) -> Result<ChatId> {
|
||||
let mut state: MessageState;
|
||||
let mut chat_id = ChatId::new(0);
|
||||
let mut chat_id = None;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut incoming_origin = incoming_origin;
|
||||
|
||||
@@ -400,7 +400,7 @@ async fn add_parts(
|
||||
match show_emails {
|
||||
ShowEmails::Off => {
|
||||
info!(context, "Classical email not shown (TRASH)");
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
allow_creation = false;
|
||||
}
|
||||
ShowEmails::AcceptedContacts => allow_creation = false,
|
||||
@@ -425,7 +425,7 @@ async fn add_parts(
|
||||
// handshake may mark contacts as verified and must be processed before chats are created
|
||||
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
||||
is_dc_message = MessengerMessage::Yes; // avoid discarding by show_emails setting
|
||||
chat_id = ChatId::new(0);
|
||||
chat_id = None;
|
||||
allow_creation = true;
|
||||
match handle_securejoin_handshake(context, mime_parser, from_id).await {
|
||||
Ok(securejoin::HandshakeMessage::Done) => {
|
||||
@@ -441,9 +441,8 @@ async fn add_parts(
|
||||
// process messages as "member added" normally
|
||||
}
|
||||
Err(err) => {
|
||||
*hidden = true;
|
||||
warn!(context, "Error in Secure-Join message handling: {}", err);
|
||||
return Ok(chat_id);
|
||||
return Ok(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,21 +451,23 @@ async fn add_parts(
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if chat_id.is_unset() && mime_parser.failure_report.is_some() {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
if chat_id.is_none() && mime_parser.failure_report.is_some() {
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message belongs to an NDN (TRASH)",);
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to assign to a chat based on In-Reply-To/References:
|
||||
|
||||
let (new_chat_id, new_chat_id_blocked) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to create a group
|
||||
|
||||
let create_blocked = match test_normal_chat {
|
||||
@@ -477,7 +478,7 @@ async fn add_parts(
|
||||
_ => Blocked::Request,
|
||||
};
|
||||
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
if test_normal_chat.is_none() {
|
||||
@@ -489,63 +490,69 @@ async fn add_parts(
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if !chat_id.is_unset()
|
||||
&& chat_id_blocked != Blocked::Not
|
||||
&& create_blocked == Blocked::Not
|
||||
.await?
|
||||
{
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not {
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In lookup_chat_by_reply() and create_or_lookup_group(), it can happen that the message is put into a chat
|
||||
// but the From-address is not a member of this chat.
|
||||
if !chat_id.is_unset() && !chat::is_contact_in_chat(context, chat_id, from_id as u32).await
|
||||
{
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
if chat.is_protected() {
|
||||
let s = stock_str::unknown_sender_for_chat(context).await;
|
||||
mime_parser.repl_msg_by_error(s);
|
||||
} else if let Some(from) = mime_parser.from.first() {
|
||||
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
|
||||
// to the sender's name, indicating to the user that he/she is not part of the group.
|
||||
let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
part.param.set(Param::OverrideSenderDisplayname, name);
|
||||
if let Some(chat_id) = chat_id {
|
||||
if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await {
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
if chat.is_protected() {
|
||||
let s = stock_str::unknown_sender_for_chat(context).await;
|
||||
mime_parser.repl_msg_by_error(s);
|
||||
} else if let Some(from) = mime_parser.from.first() {
|
||||
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
|
||||
// to the sender's name, indicating to the user that he/she is not part of the group.
|
||||
let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
part.param.set(Param::OverrideSenderDisplayname, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// check if the message belongs to a mailing list
|
||||
match mime_parser.get_mailinglist_type() {
|
||||
MailinglistType::ListIdBased => {
|
||||
if let Some(list_id) = mime_parser.get(HeaderDef::ListId) {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
list_id,
|
||||
mime_parser,
|
||||
)
|
||||
.await;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
list_id,
|
||||
mime_parser,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
MailinglistType::SenderBased => {
|
||||
if let Some(sender) = mime_parser.get(HeaderDef::Sender) {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
sender,
|
||||
mime_parser,
|
||||
)
|
||||
.await;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
create_or_lookup_mailinglist(
|
||||
context,
|
||||
allow_creation,
|
||||
sender,
|
||||
mime_parser,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
MailinglistType::None => {}
|
||||
@@ -564,7 +571,7 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to create a normal chat
|
||||
let create_blocked = if from_id == to_id {
|
||||
Blocked::Not
|
||||
@@ -573,40 +580,39 @@ async fn add_parts(
|
||||
};
|
||||
|
||||
if let Some(chat) = test_normal_chat {
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
} else if allow_creation {
|
||||
if let Ok(chat) = ChatIdBlocked::get_for_contact(context, from_id, create_blocked)
|
||||
.await
|
||||
.log_err(context, "Failed to get (new) chat for contact")
|
||||
{
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
}
|
||||
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
|
||||
if Blocked::Not == create_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
} else if parent.is_some() {
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
// the contact requests will pop up and this should be just fine.
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo).await;
|
||||
info!(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
);
|
||||
if !incoming_origin.is_known() {
|
||||
incoming_origin = Origin::IncomingReplyTo;
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
if Blocked::Not != chat_id_blocked {
|
||||
if Blocked::Not == create_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
} else if parent.is_some() {
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
// the contact requests will pop up and this should be just fine.
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo)
|
||||
.await;
|
||||
info!(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
);
|
||||
if !incoming_origin.is_known() {
|
||||
incoming_origin = Origin::IncomingReplyTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if chat_id.is_unset() {
|
||||
// maybe from_id is null or sth. else is suspicious, move message to trash
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
info!(context, "No chat id for incoming msg (TRASH)")
|
||||
}
|
||||
|
||||
// if the chat_id is blocked,
|
||||
// for unknown senders and non-delta-messages set the state to NOTICED
|
||||
@@ -629,7 +635,7 @@ async fn add_parts(
|
||||
&& (is_dc_message == MessengerMessage::No)
|
||||
&& context.is_spam_folder(server_folder).await?;
|
||||
if is_spam {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message is probably spam (TRASH)");
|
||||
}
|
||||
} else {
|
||||
@@ -643,7 +649,7 @@ async fn add_parts(
|
||||
// handshake may mark contacts as verified and must be processed before chats are created
|
||||
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
||||
is_dc_message = MessengerMessage::Yes; // avoid discarding by show_emails setting
|
||||
chat_id = ChatId::new(0);
|
||||
chat_id = None;
|
||||
allow_creation = true;
|
||||
match observe_securejoin_on_other_device(context, mime_parser, to_id).await {
|
||||
Ok(securejoin::HandshakeMessage::Done)
|
||||
@@ -654,9 +660,8 @@ async fn add_parts(
|
||||
// process messages as "member added" normally
|
||||
}
|
||||
Err(err) => {
|
||||
*hidden = true;
|
||||
warn!(context, "Error in Secure-Join watching: {}", err);
|
||||
return Ok(chat_id);
|
||||
return Ok(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -682,22 +687,24 @@ async fn add_parts(
|
||||
if is_draft {
|
||||
// Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
|
||||
info!(context, "Email is probably just a draft (TRASH)");
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
allow_creation = false;
|
||||
}
|
||||
|
||||
if chat_id.is_unset() {
|
||||
if chat_id.is_none() {
|
||||
// try to assign to a chat based on In-Reply-To/References:
|
||||
|
||||
let (new_chat_id, new_chat_id_blocked) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) =
|
||||
lookup_chat_by_reply(context, &mime_parser, &parent, from_id, to_ids).await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
}
|
||||
}
|
||||
|
||||
if !to_ids.is_empty() {
|
||||
if chat_id.is_unset() {
|
||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||
if chat_id.is_none() {
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
allow_creation,
|
||||
@@ -705,16 +712,18 @@ async fn add_parts(
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await?;
|
||||
chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
// automatically unblock chat when the user sends a message
|
||||
if !chat_id.is_unset() && chat_id_blocked != Blocked::Not {
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
.await?
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
// automatically unblock chat when the user sends a message
|
||||
if chat_id_blocked != Blocked::Not {
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
if chat_id.is_unset() && allow_creation {
|
||||
if chat_id.is_none() && allow_creation {
|
||||
let create_blocked = if !Contact::is_blocked_load(context, to_id).await {
|
||||
Blocked::Not
|
||||
} else {
|
||||
@@ -723,16 +732,15 @@ async fn add_parts(
|
||||
if let Ok(chat) =
|
||||
ChatIdBlocked::get_for_contact(context, to_id, create_blocked).await
|
||||
{
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
|
||||
if !chat_id.is_unset()
|
||||
&& Blocked::Not != chat_id_blocked
|
||||
&& Blocked::Not == create_blocked
|
||||
{
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
if let Some(chat_id) = chat_id {
|
||||
if Blocked::Not != chat_id_blocked && Blocked::Not == create_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -740,7 +748,7 @@ async fn add_parts(
|
||||
&& to_ids.len() == 1
|
||||
&& to_ids.contains(&DC_CONTACT_ID_SELF);
|
||||
|
||||
if chat_id.is_unset() && self_sent {
|
||||
if chat_id.is_none() && self_sent {
|
||||
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
|
||||
// maybe an Autocrypt Setup Message
|
||||
if let Ok(chat) =
|
||||
@@ -748,27 +756,30 @@ async fn add_parts(
|
||||
.await
|
||||
.log_err(context, "Failed to get (new) chat for contact")
|
||||
{
|
||||
chat_id = chat.id;
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
|
||||
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
if let Some(chat_id) = chat_id {
|
||||
if Blocked::Not != chat_id_blocked {
|
||||
chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
if chat_id.is_unset() {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
info!(context, "No chat id for outgoing message (TRASH)")
|
||||
}
|
||||
}
|
||||
|
||||
if fetching_existing_messages && mime_parser.decrypting_failed {
|
||||
chat_id = DC_CHAT_ID_TRASH;
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
// We are only gathering old messages on first start. We do not want to add loads of non-decryptable messages to the chats.
|
||||
info!(context, "Existing non-decipherable message. (TRASH)");
|
||||
}
|
||||
|
||||
let chat_id = chat_id.unwrap_or_else(|| {
|
||||
info!(context, "No chat id for message (TRASH)");
|
||||
DC_CHAT_ID_TRASH
|
||||
});
|
||||
|
||||
// Extract ephemeral timer from the message.
|
||||
let mut ephemeral_timer = if let Some(value) = mime_parser.get(HeaderDef::EphemeralTimer) {
|
||||
match value.parse::<EphemeralTimer>() {
|
||||
@@ -787,6 +798,12 @@ async fn add_parts(
|
||||
|
||||
let location_kml_is = mime_parser.location_kml.is_some();
|
||||
|
||||
// correct message_timestamp, it should not be used before,
|
||||
// however, we cannot do this earlier as we need from_id to be set
|
||||
let in_fresh = state == MessageState::InFresh;
|
||||
let rcvd_timestamp = time();
|
||||
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
|
||||
|
||||
// Apply ephemeral timer changes to the chat.
|
||||
//
|
||||
// Only non-hidden timers are applied now. Timers from hidden
|
||||
@@ -814,6 +831,7 @@ async fn add_parts(
|
||||
context,
|
||||
chat_id,
|
||||
stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
|
||||
sort_timestamp,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -857,6 +875,7 @@ async fn add_parts(
|
||||
context,
|
||||
chat_id,
|
||||
format!("Cannot set protection: {}", e),
|
||||
sort_timestamp,
|
||||
)
|
||||
.await;
|
||||
return Ok(chat_id); // do not return an error as this would result in retrying the message
|
||||
@@ -872,12 +891,6 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
// correct message_timestamp, it should not be used before,
|
||||
// however, we cannot do this earlier as we need from_id to be set
|
||||
let in_fresh = state == MessageState::InFresh;
|
||||
let rcvd_timestamp = time();
|
||||
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
|
||||
|
||||
// Ensure replies to messages are sorted after the parent message.
|
||||
//
|
||||
// This is useful in a case where sender clocks are not
|
||||
@@ -936,7 +949,6 @@ async fn add_parts(
|
||||
|
||||
let sent_timestamp = *sent_timestamp;
|
||||
let is_hidden = *hidden;
|
||||
let chat_id = chat_id;
|
||||
|
||||
let mut is_hidden = is_hidden;
|
||||
let mut ids = Vec::with_capacity(parts.len());
|
||||
@@ -979,7 +991,8 @@ INSERT INTO msgs
|
||||
}
|
||||
}
|
||||
|
||||
let mime_modified = save_mime_modified && !part.msg.is_empty();
|
||||
let part_is_empty = part.msg.is_empty() && part.param.get(Param::Quote).is_none();
|
||||
let mime_modified = save_mime_modified && !part_is_empty;
|
||||
if mime_modified {
|
||||
// Avoid setting mime_modified for more than one part.
|
||||
save_mime_modified = false;
|
||||
@@ -1201,7 +1214,7 @@ async fn lookup_chat_by_reply(
|
||||
parent: &Option<Message>,
|
||||
from_id: u32,
|
||||
to_ids: &ContactIds,
|
||||
) -> Result<(ChatId, Blocked)> {
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
// Try to assign message to the same chat as the parent message.
|
||||
|
||||
// If this was a private message just to self, it was probably a private reply.
|
||||
@@ -1215,7 +1228,7 @@ async fn lookup_chat_by_reply(
|
||||
// This message will be assigned to this chat, anyway.
|
||||
// But if we assigned it now, create_or_lookup_group() will not be called
|
||||
// and group commands will not be executed.
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1224,25 +1237,25 @@ async fn lookup_chat_by_reply(
|
||||
// (undecipherable group msgs often get assigned to the 1:1 chat with the sender).
|
||||
// We don't have any way of finding out whether a msg is undecipherable, so we check for
|
||||
// error.is_some() instead.
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if parent_chat.id == DC_CHAT_ID_TRASH {
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if is_probably_private_reply(context, to_ids, mime_parser, parent_chat.id, from_id).await? {
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"Assigning message to {} as it's a reply to {}", parent_chat.id, parent.rfc724_mid
|
||||
);
|
||||
return Ok((parent_chat.id, parent_chat.blocked));
|
||||
return Ok(Some((parent_chat.id, parent_chat.blocked)));
|
||||
}
|
||||
|
||||
Ok((ChatId::new(0), Blocked::Not))
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// If this method returns true, the message shall be assigned to the 1:1 chat with the sender.
|
||||
@@ -1299,7 +1312,7 @@ async fn create_or_lookup_group(
|
||||
create_blocked: Blocked,
|
||||
from_id: u32,
|
||||
to_ids: &ContactIds,
|
||||
) -> Result<(ChatId, Blocked)> {
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut recreate_member_list = false;
|
||||
let mut send_EVENT_CHAT_MODIFIED = false;
|
||||
@@ -1324,16 +1337,12 @@ async fn create_or_lookup_group(
|
||||
}
|
||||
if !allow_creation {
|
||||
info!(context, "creating ad-hoc group prevented from caller");
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
return create_adhoc_group(context, mime_parser, create_blocked, &member_ids)
|
||||
.await
|
||||
.map(|chat_id| {
|
||||
chat_id
|
||||
.map(|chat_id| (chat_id, create_blocked))
|
||||
.unwrap_or((ChatId::new(0), Blocked::Not))
|
||||
})
|
||||
.map(|chat_id| chat_id.map(|chat_id| (chat_id, create_blocked)))
|
||||
.map_err(|err| {
|
||||
info!(context, "could not create adhoc-group: {:?}", err);
|
||||
err
|
||||
@@ -1341,16 +1350,18 @@ async fn create_or_lookup_group(
|
||||
};
|
||||
|
||||
// check, if we have a chat with this group ID
|
||||
let (mut chat_id, _, _blocked) = chat::get_chat_id_by_grpid(context, &grpid)
|
||||
.await
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
||||
let mut chat_id = chat::get_chat_id_by_grpid(context, &grpid)
|
||||
.await?
|
||||
.map(|(chat_id, _protected, _blocked)| chat_id);
|
||||
|
||||
// For chat messages, we don't have to guess (is_*probably*_private_reply()) but we know for sure that
|
||||
// they belong to the group because of the Chat-Group-Id or Message-Id header
|
||||
if !mime_parser.has_chat_version()
|
||||
&& is_probably_private_reply(context, to_ids, mime_parser, chat_id, from_id).await?
|
||||
{
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
if let Some(chat_id) = chat_id {
|
||||
if !mime_parser.has_chat_version()
|
||||
&& is_probably_private_reply(context, to_ids, mime_parser, chat_id, from_id).await?
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// now we have a grpid that is non-empty
|
||||
@@ -1421,7 +1432,7 @@ async fn create_or_lookup_group(
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
if chat_id.is_unset()
|
||||
if chat_id.is_none()
|
||||
&& !mime_parser.is_mailinglist_message()
|
||||
&& !grpid.is_empty()
|
||||
&& grpname.is_some()
|
||||
@@ -1446,7 +1457,7 @@ async fn create_or_lookup_group(
|
||||
|
||||
if !allow_creation {
|
||||
info!(context, "creating group forbidden by caller");
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
chat_id = create_multiuser_record(
|
||||
@@ -1458,6 +1469,7 @@ async fn create_or_lookup_group(
|
||||
create_protected,
|
||||
)
|
||||
.await
|
||||
.map(Some)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(
|
||||
context,
|
||||
@@ -1467,7 +1479,7 @@ async fn create_or_lookup_group(
|
||||
err,
|
||||
);
|
||||
|
||||
ChatId::new(0)
|
||||
None
|
||||
});
|
||||
|
||||
chat_id_blocked = create_blocked;
|
||||
@@ -1486,22 +1498,22 @@ async fn create_or_lookup_group(
|
||||
}
|
||||
|
||||
// again, check chat_id
|
||||
if chat_id.is_special() {
|
||||
if mime_parser.decrypting_failed {
|
||||
// It is possible that the message was sent to a valid,
|
||||
// yet unknown group, which was rejected because
|
||||
// Chat-Group-Name, which is in the encrypted part, was
|
||||
// not found. We can't create a properly named group in
|
||||
// this case, so assign error message to 1:1 chat with the
|
||||
// sender instead.
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
} else {
|
||||
// The message was decrypted successfully, but contains a late "quit" or otherwise
|
||||
// unwanted message.
|
||||
info!(context, "message belongs to unwanted group (TRASH)");
|
||||
return Ok((DC_CHAT_ID_TRASH, chat_id_blocked));
|
||||
}
|
||||
}
|
||||
let chat_id = if let Some(chat_id) = chat_id {
|
||||
chat_id
|
||||
} else if mime_parser.decrypting_failed {
|
||||
// It is possible that the message was sent to a valid,
|
||||
// yet unknown group, which was rejected because
|
||||
// Chat-Group-Name, which is in the encrypted part, was
|
||||
// not found. We can't create a properly named group in
|
||||
// this case, so assign error message to 1:1 chat with the
|
||||
// sender instead.
|
||||
return Ok(None);
|
||||
} else {
|
||||
// The message was decrypted successfully, but contains a late "quit" or otherwise
|
||||
// unwanted message.
|
||||
info!(context, "message belongs to unwanted group (TRASH)");
|
||||
return Ok(Some((DC_CHAT_ID_TRASH, chat_id_blocked)));
|
||||
};
|
||||
|
||||
// We have a valid chat_id > DC_CHAT_ID_LAST_SPECIAL.
|
||||
|
||||
@@ -1585,7 +1597,7 @@ async fn create_or_lookup_group(
|
||||
if send_EVENT_CHAT_MODIFIED {
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
Ok((chat_id, chat_id_blocked))
|
||||
Ok(Some((chat_id, chat_id_blocked)))
|
||||
}
|
||||
|
||||
/// Create or lookup a mailing list chat.
|
||||
@@ -1603,7 +1615,7 @@ async fn create_or_lookup_mailinglist(
|
||||
allow_creation: bool,
|
||||
list_id_header: &str,
|
||||
mime_parser: &MimeMessage,
|
||||
) -> (ChatId, Blocked) {
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
static LIST_ID: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(.+)<(.+)>$").unwrap());
|
||||
let (mut name, listid) = match LIST_ID.captures(list_id_header) {
|
||||
Some(cap) => (cap[1].trim().to_string(), cap[2].trim().to_string()),
|
||||
@@ -1617,8 +1629,8 @@ async fn create_or_lookup_mailinglist(
|
||||
),
|
||||
};
|
||||
|
||||
if let Ok((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await {
|
||||
return (chat_id, blocked);
|
||||
if let Some((chat_id, _, blocked)) = chat::get_chat_id_by_grpid(context, &listid).await? {
|
||||
return Ok(Some((chat_id, blocked)));
|
||||
}
|
||||
|
||||
// for mailchimp lists, the name in `ListId` is just a long number.
|
||||
@@ -1665,7 +1677,7 @@ async fn create_or_lookup_mailinglist(
|
||||
|
||||
if allow_creation {
|
||||
// list does not exist but should be created
|
||||
match create_multiuser_record(
|
||||
let chat_id = create_multiuser_record(
|
||||
context,
|
||||
Chattype::Mailinglist,
|
||||
&listid,
|
||||
@@ -1674,25 +1686,18 @@ async fn create_or_lookup_mailinglist(
|
||||
ProtectionStatus::Unprotected,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(chat_id) => {
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
|
||||
(chat_id, Blocked::Request)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to create mailinglist '{}' for grpid={}: {}",
|
||||
&name,
|
||||
&listid,
|
||||
e.to_string()
|
||||
);
|
||||
(ChatId::new(0), Blocked::Request)
|
||||
}
|
||||
}
|
||||
.map_err(|err| {
|
||||
err.context(format!(
|
||||
"Failed to create mailinglist '{}' for grpid={}",
|
||||
&name, &listid
|
||||
))
|
||||
})?;
|
||||
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
|
||||
Ok(Some((chat_id, Blocked::Request)))
|
||||
} else {
|
||||
info!(context, "creating list forbidden by caller");
|
||||
(ChatId::new(0), Blocked::Not)
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4441,4 +4446,25 @@ Reply to all"#,
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_gmx_forwarded_msg() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/gmx-forward.eml"),
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let msg = t.get_last_msg().await;
|
||||
assert!(msg.has_html());
|
||||
assert_eq!(msg.id.get_html(&t).await?.unwrap().replace("\r\n", "\n"), "<html><head></head><body><div style=\"font-family: Verdana;font-size: 12.0px;\"><div> </div>\n\n<div> \n<div> \n<div data-darkreader-inline-border-left=\"\" name=\"quote\" style=\"margin: 10px 5px 5px 10px; padding: 10px 0px 10px 10px; border-left: 2px solid rgb(195, 217, 229); overflow-wrap: break-word; --darkreader-inline-border-left:#274759;\">\n<div style=\"margin:0 0 10px 0;\"><b>Gesendet:</b> Donnerstag, 12. August 2021 um 15:52 Uhr<br/>\n<b>Von:</b> "Claire" <claire@example.org><br/>\n<b>An:</b> alice@example.com<br/>\n<b>Betreff:</b> subject</div>\n\n<div name=\"quoted-content\">bodytext</div>\n</div>\n</div>\n</div></div></body></html>\n\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,9 @@ pub async fn try_decrypt(
|
||||
let mut signatures = HashSet::default();
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
peerstate.handle_fingerprint_change(context).await?;
|
||||
peerstate
|
||||
.handle_fingerprint_change(context, message_time)
|
||||
.await?;
|
||||
if let Some(key) = &peerstate.public_key {
|
||||
public_keyring_for_validate.add(key.clone());
|
||||
} else if let Some(key) = &peerstate.gossip_key {
|
||||
|
||||
28
src/imap.rs
28
src/imap.rs
@@ -5,10 +5,10 @@
|
||||
|
||||
use std::{cmp, cmp::max, collections::BTreeMap};
|
||||
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
use anyhow::{anyhow, bail, format_err, Context as _, Result};
|
||||
use async_imap::{
|
||||
error::Result as ImapResult,
|
||||
types::{Fetch, Flag, Mailbox, Name, NameAttribute, UnsolicitedResponse},
|
||||
types::{Fetch, Flag, Mailbox, Name, NameAttribute, Quota, QuotaRoot, UnsolicitedResponse},
|
||||
};
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::prelude::*;
|
||||
@@ -153,6 +153,10 @@ struct ImapConfig {
|
||||
/// True if the server has MOVE capability as defined in
|
||||
/// <https://tools.ietf.org/html/rfc6851>
|
||||
pub can_move: bool,
|
||||
|
||||
/// True if the server has QUOTA capability as defined in
|
||||
/// <https://tools.ietf.org/html/rfc2087>
|
||||
pub can_check_quota: bool,
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
@@ -186,6 +190,7 @@ impl Imap {
|
||||
selected_folder_needs_expunge: false,
|
||||
can_idle: false,
|
||||
can_move: false,
|
||||
can_check_quota: false,
|
||||
};
|
||||
|
||||
let imap = Imap {
|
||||
@@ -362,6 +367,7 @@ impl Imap {
|
||||
Ok(caps) => {
|
||||
self.config.can_idle = caps.has_str("IDLE");
|
||||
self.config.can_move = caps.has_str("MOVE");
|
||||
self.config.can_check_quota = caps.has_str("QUOTA");
|
||||
self.capabilities_determined = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1392,6 +1398,22 @@ impl Imap {
|
||||
}
|
||||
unsolicited_exists
|
||||
}
|
||||
|
||||
pub fn can_check_quota(&self) -> bool {
|
||||
self.config.can_check_quota
|
||||
}
|
||||
|
||||
pub async fn get_quota_roots(
|
||||
&mut self,
|
||||
mailbox_name: &str,
|
||||
) -> Result<(Vec<QuotaRoot>, Vec<Quota>)> {
|
||||
if let Some(session) = self.session.as_mut() {
|
||||
let quota_roots = session.get_quota_root(mailbox_name).await?;
|
||||
Ok(quota_roots)
|
||||
} else {
|
||||
Err(anyhow!("Not connected to IMAP, no session"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get the folder meaning by the name of the folder only used if the server does not support XLIST.
|
||||
@@ -1636,7 +1658,7 @@ pub(crate) async fn prefetch_should_download(
|
||||
// deleted from the database or has not arrived yet.
|
||||
if let Some(rfc724_mid) = headers.get_header_value(HeaderDef::MessageId) {
|
||||
if let Some(group_id) = dc_extract_grpid_from_rfc724_mid(&rfc724_mid) {
|
||||
if let Ok((_chat_id, _, _)) = get_chat_id_by_grpid(context, group_id).await {
|
||||
if get_chat_id_by_grpid(context, group_id).await?.is_some() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,15 @@ impl Imap {
|
||||
}
|
||||
};
|
||||
|
||||
// Gmail labels are not folders and should be skipped. For example,
|
||||
// emails appear in the inbox and under "All Mail" as soon as it is
|
||||
// received. The code used to wrongly conclude that the email had
|
||||
// already been moved and left it in the inbox.
|
||||
let folder_name = folder.name();
|
||||
if folder_name.starts_with("[Gmail]") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let folder_meaning = get_folder_meaning(&folder);
|
||||
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
|
||||
|
||||
@@ -93,7 +102,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_watched_folders(context: &Context) -> Vec<String> {
|
||||
pub(crate) async fn get_watched_folders(context: &Context) -> Vec<String> {
|
||||
let mut res = Vec::new();
|
||||
let folder_watched_configured = &[
|
||||
(Config::SentboxWatch, Config::ConfiguredSentboxFolder),
|
||||
|
||||
22
src/job.rs
22
src/job.rs
@@ -95,6 +95,9 @@ pub enum Action {
|
||||
FetchExistingMsgs = 110,
|
||||
MarkseenMsgOnImap = 130,
|
||||
|
||||
// this is user initiated so it should have a fairly high priority
|
||||
UpdateRecentQuota = 140,
|
||||
|
||||
// Moving message is prioritized lower than deletion so we don't
|
||||
// bother moving message if it is already scheduled for deletion.
|
||||
MoveMsg = 200,
|
||||
@@ -130,6 +133,7 @@ impl From<Action> for Thread {
|
||||
ResyncFolders => Thread::Imap,
|
||||
MarkseenMsgOnImap => Thread::Imap,
|
||||
MoveMsg => Thread::Imap,
|
||||
UpdateRecentQuota => Thread::Imap,
|
||||
|
||||
MaybeSendLocations => Thread::Smtp,
|
||||
MaybeSendLocationsEnded => Thread::Smtp,
|
||||
@@ -148,7 +152,6 @@ pub struct Job {
|
||||
pub added_timestamp: i64,
|
||||
pub tries: u32,
|
||||
pub param: Params,
|
||||
pub pending_error: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Job {
|
||||
@@ -169,7 +172,6 @@ impl Job {
|
||||
added_timestamp: timestamp,
|
||||
tries: 0,
|
||||
param,
|
||||
pending_error: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,12 +253,13 @@ impl Job {
|
||||
|
||||
smtp.connectivity.set_working(context).await;
|
||||
|
||||
let status = match smtp.send(context, recipients, message, job_id).await {
|
||||
let send_result = smtp.send(context, recipients, message, job_id).await;
|
||||
smtp.last_send_error = send_result.as_ref().err().map(|e| e.to_string());
|
||||
|
||||
let status = match send_result {
|
||||
Err(crate::smtp::send::Error::SmtpSend(err)) => {
|
||||
// Remote error, retry later.
|
||||
warn!(context, "SMTP failed to send: {:?}", &err);
|
||||
smtp.connectivity.set_err(context, &err).await;
|
||||
self.pending_error = Some(err.to_string());
|
||||
|
||||
let res = match err {
|
||||
async_smtp::smtp::error::Error::Permanent(ref response) => {
|
||||
@@ -365,6 +368,7 @@ impl Job {
|
||||
// SMTP server, if not yet done
|
||||
if let Err(err) = smtp.connect_configured(context).await {
|
||||
warn!(context, "SMTP connection failure: {:?}", err);
|
||||
smtp.last_send_error = Some(format!("SMTP connection failure: {:#}", err));
|
||||
return Status::RetryLater;
|
||||
}
|
||||
|
||||
@@ -407,6 +411,8 @@ impl Job {
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "failed to check message existence: {:?}", err);
|
||||
smtp.last_send_error =
|
||||
Some(format!("failed to check message existence: {:#}", err));
|
||||
return Status::RetryLater;
|
||||
}
|
||||
}
|
||||
@@ -521,6 +527,7 @@ impl Job {
|
||||
// connect to SMTP server, if not yet done
|
||||
if let Err(err) = smtp.connect_configured(context).await {
|
||||
warn!(context, "SMTP connection failure: {:?}", err);
|
||||
smtp.last_send_error = Some(err.to_string());
|
||||
return Status::RetryLater;
|
||||
}
|
||||
|
||||
@@ -1145,6 +1152,7 @@ async fn perform_job_action(
|
||||
sql::housekeeping(context).await.ok_or_log(context);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
Action::UpdateRecentQuota => context.update_recent_quota(connection.inbox()).await,
|
||||
};
|
||||
|
||||
info!(context, "Finished immediate try {} of job {}", tries, job);
|
||||
@@ -1207,7 +1215,8 @@ pub async fn add(context: &Context, job: Job) {
|
||||
| Action::ResyncFolders
|
||||
| Action::MarkseenMsgOnImap
|
||||
| Action::FetchExistingMsgs
|
||||
| Action::MoveMsg => {
|
||||
| Action::MoveMsg
|
||||
| Action::UpdateRecentQuota => {
|
||||
info!(context, "interrupt: imap");
|
||||
context
|
||||
.interrupt_inbox(InterruptInfo::new(false, None))
|
||||
@@ -1321,7 +1330,6 @@ LIMIT 1;
|
||||
added_timestamp: row.get("added_timestamp")?,
|
||||
tries: row.get("tries")?,
|
||||
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
Ok(job)
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
clippy::wildcard_imports,
|
||||
clippy::needless_borrow
|
||||
)]
|
||||
#![allow(clippy::match_bool, clippy::eval_order_dependence)]
|
||||
#![allow(
|
||||
clippy::match_bool,
|
||||
clippy::eval_order_dependence,
|
||||
clippy::bool_assert_comparison
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate num_derive;
|
||||
@@ -71,6 +75,7 @@ pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod provider;
|
||||
pub mod qr;
|
||||
pub mod quota;
|
||||
pub mod securejoin;
|
||||
mod simplify;
|
||||
mod smtp;
|
||||
|
||||
@@ -190,7 +190,7 @@ impl Kml {
|
||||
}
|
||||
}
|
||||
|
||||
// location streaming
|
||||
/// Enables location streaming in chat identified by `chat_id` for `seconds` seconds.
|
||||
pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) {
|
||||
let now = time();
|
||||
if !(seconds < 0 || chat_id.is_special()) {
|
||||
@@ -221,7 +221,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
|
||||
.unwrap_or_default();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str = stock_str::msg_location_disabled(context).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str, now).await;
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
if 0 != seconds {
|
||||
@@ -716,7 +716,8 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
||||
.await
|
||||
);
|
||||
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
let now = time();
|
||||
if !(send_begin != 0 && now <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
@@ -735,7 +736,7 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
||||
);
|
||||
|
||||
let stock_str = stock_str::msg_location_disabled(context).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str, now).await;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ use crate::constants::{
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::{
|
||||
dc_get_filebytes, dc_get_filemeta, dc_gm2local_offset, dc_read_file, dc_timestamp_to_str,
|
||||
dc_truncate, time,
|
||||
dc_create_smeared_timestamp, dc_get_filebytes, dc_get_filemeta, dc_gm2local_offset,
|
||||
dc_read_file, dc_timestamp_to_str, dc_truncate, time,
|
||||
};
|
||||
use crate::ephemeral::Timer as EphemeralTimer;
|
||||
use crate::events::EventType;
|
||||
@@ -1805,7 +1805,13 @@ async fn ndn_maybe_add_info_msg(
|
||||
// Tell the user which of the recipients failed if we know that (because in
|
||||
// a group, this might otherwise be unclear)
|
||||
let text = stock_str::failed_sending_to(context, contact.get_display_name()).await;
|
||||
chat::add_info_msg(context, chat_id, text).await;
|
||||
chat::add_info_msg(
|
||||
context,
|
||||
chat_id,
|
||||
text,
|
||||
dc_create_smeared_timestamp(context).await,
|
||||
)
|
||||
.await;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ impl MimeMessage {
|
||||
parser.maybe_remove_bad_parts();
|
||||
parser.maybe_remove_inline_mailinglist_footer();
|
||||
parser.heuristically_parse_ndn(context).await;
|
||||
parser.parse_headers(context).await;
|
||||
parser.parse_headers(context).await?;
|
||||
|
||||
if warn_empty_signature && parser.signatures.is_empty() {
|
||||
for part in parser.parts.iter_mut() {
|
||||
@@ -419,7 +419,7 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
async fn parse_headers(&mut self, context: &Context) {
|
||||
async fn parse_headers(&mut self, context: &Context) -> Result<()> {
|
||||
self.parse_system_message_headers(context);
|
||||
self.parse_avatar_headers(context).await;
|
||||
self.parse_videochat_headers();
|
||||
@@ -464,15 +464,20 @@ impl MimeMessage {
|
||||
if !self.decrypting_failed && !self.parts.is_empty() {
|
||||
if let Some(ref dn_to) = self.chat_disposition_notification_to {
|
||||
if let Some(from) = self.from.get(0) {
|
||||
if from.addr.to_lowercase() == dn_to.addr.to_lowercase() {
|
||||
if let Some(part) = self.parts.last_mut() {
|
||||
part.param.set_int(Param::WantsMdn, 1);
|
||||
// Check that the message is not outgoing.
|
||||
if !context.is_self_addr(&from.addr).await? {
|
||||
if from.addr.to_lowercase() == dn_to.addr.to_lowercase() {
|
||||
if let Some(part) = self.parts.last_mut() {
|
||||
part.param.set_int(Param::WantsMdn, 1);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"{} requested a read receipt to {}, ignoring",
|
||||
from.addr,
|
||||
dn_to.addr
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"{} requested a read receipt to {}, ignoring", from.addr, dn_to.addr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,6 +507,8 @@ impl MimeMessage {
|
||||
part.param.set(Param::Bot, "1");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn avatar_action_from_header(
|
||||
@@ -1347,7 +1354,9 @@ async fn update_gossip_peerstates(
|
||||
peerstate = Some(p);
|
||||
}
|
||||
if let Some(peerstate) = peerstate {
|
||||
peerstate.handle_fingerprint_change(context).await?;
|
||||
peerstate
|
||||
.handle_fingerprint_change(context, message_time)
|
||||
.await?;
|
||||
}
|
||||
|
||||
gossipped_addr.insert(header.addr.clone());
|
||||
@@ -2952,4 +2961,36 @@ Some reply
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test that WantsMdn parameter is not set on outgoing messages.
|
||||
#[async_std::test]
|
||||
async fn test_outgoing_wants_mdn() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
let raw = br###"Date: Thu, 28 Jan 2021 00:26:57 +0000
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: <foobarbaz@example.org>
|
||||
To: Bob <bob@example.org>
|
||||
From: Alice <alice@example.com>
|
||||
Subject: subject
|
||||
Chat-Disposition-Notification-To: alice@example.com
|
||||
|
||||
Message.
|
||||
"###;
|
||||
|
||||
// Bob receives message.
|
||||
dc_receive_imf(&bob, raw, "INBOX", 1, 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", 1, 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());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,11 @@ impl Peerstate {
|
||||
}
|
||||
|
||||
/// Adds a warning to the chat corresponding to peerstate if fingerprint has changed.
|
||||
pub(crate) async fn handle_fingerprint_change(&self, context: &Context) -> Result<()> {
|
||||
pub(crate) async fn handle_fingerprint_change(
|
||||
&self,
|
||||
context: &Context,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
if self.fingerprint_changed {
|
||||
if let Some(contact_id) = context
|
||||
.sql
|
||||
@@ -273,7 +277,7 @@ impl Peerstate {
|
||||
|
||||
let msg = stock_str::contact_setup_changed(context, self.addr.clone()).await;
|
||||
|
||||
chat::add_info_msg(context, chat_id, msg).await;
|
||||
chat::add_info_msg(context, chat_id, msg, timestamp).await;
|
||||
emit_event!(context, EventType::ChatModified(chat_id));
|
||||
} else {
|
||||
bail!("contact with peerstate.addr {:?} not found", &self.addr);
|
||||
|
||||
@@ -585,8 +585,8 @@ static P_HEY_COM: Lazy<Provider> = Lazy::new(|| {
|
||||
// i.ua.md: i.ua
|
||||
static P_I_UA: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "i.ua",
|
||||
status: Status::Ok,
|
||||
before_login_hint: "",
|
||||
status: Status::Broken,
|
||||
before_login_hint: "Протокол IMAP не предоставляется и не планируется.",
|
||||
after_login_hint: "",
|
||||
overview_page: "https://providers.delta.chat/i-ua",
|
||||
server: vec![],
|
||||
@@ -686,6 +686,35 @@ static P_MAIL_RU: Lazy<Provider> = Lazy::new(|| {
|
||||
}
|
||||
});
|
||||
|
||||
// mail2tor.md: mail2tor.com
|
||||
static P_MAIL2TOR: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "mail2tor",
|
||||
status: Status::Preparation,
|
||||
before_login_hint: "Tor is needed to connect to the email servers.",
|
||||
after_login_hint: "",
|
||||
overview_page: "https://providers.delta.chat/mail2tor",
|
||||
server: vec![
|
||||
Server {
|
||||
protocol: Imap,
|
||||
socket: Plain,
|
||||
hostname: "g77kjrad6bafzzyldqvffq6kxlsgphcygptxhnn4xlnktfgaqshilmyd.onion",
|
||||
port: 143,
|
||||
username_pattern: Email,
|
||||
},
|
||||
Server {
|
||||
protocol: Smtp,
|
||||
socket: Plain,
|
||||
hostname: "xc7tgk2c5onxni2wsy76jslfsitxjbbptejnqhw6gy2ft7khpevhc7ad.onion",
|
||||
port: 25,
|
||||
username_pattern: Email,
|
||||
},
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
});
|
||||
|
||||
// mailbox.org.md: mailbox.org, secure.mailbox.org
|
||||
static P_MAILBOX_ORG: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "mailbox.org",
|
||||
@@ -1286,6 +1315,25 @@ static P_YANDEX_RU: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
oauth2_authorizer: Some(Oauth2Authorizer::Yandex),
|
||||
});
|
||||
|
||||
// yggmail.md: yggmail
|
||||
static P_YGGMAIL: Lazy<Provider> = Lazy::new(|| {
|
||||
Provider {
|
||||
id: "yggmail",
|
||||
status: Status::Preparation,
|
||||
before_login_hint: "An Yggmail companion app needs to be installed on your device to access the Yggmail network.",
|
||||
after_login_hint: "Make sure, the Yggmail companion app runs whenever you want to use this account. Note, that you usually cannot write from @yggmail addresses to normal e-mail-addresses (as @gmx.net). However, you can create another account in the normal e-mail-network for this purpose.",
|
||||
overview_page: "https://providers.delta.chat/yggmail",
|
||||
server: vec![
|
||||
Server { protocol: Imap, socket: Plain, hostname: "localhost", port: 1143, username_pattern: Email },
|
||||
Server { protocol: Smtp, socket: Plain, hostname: "localhost", port: 1025, username_pattern: Email },
|
||||
],
|
||||
config_defaults: None,
|
||||
strict_tls: true,
|
||||
max_smtp_rcpt_to: None,
|
||||
oauth2_authorizer: None,
|
||||
}
|
||||
});
|
||||
|
||||
// ziggo.nl.md: ziggo.nl
|
||||
static P_ZIGGO_NL: Lazy<Provider> = Lazy::new(|| Provider {
|
||||
id: "ziggo.nl",
|
||||
@@ -1395,6 +1443,7 @@ pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>>
|
||||
("internet.ru", &*P_MAIL_RU),
|
||||
("bk.ru", &*P_MAIL_RU),
|
||||
("list.ru", &*P_MAIL_RU),
|
||||
("mail2tor.com", &*P_MAIL2TOR),
|
||||
("mailbox.org", &*P_MAILBOX_ORG),
|
||||
("secure.mailbox.org", &*P_MAILBOX_ORG),
|
||||
("mailo.com", &*P_MAILO_COM),
|
||||
@@ -1528,6 +1577,7 @@ pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>>
|
||||
("yandex.ua", &*P_YANDEX_RU),
|
||||
("ya.ru", &*P_YANDEX_RU),
|
||||
("narod.ru", &*P_YANDEX_RU),
|
||||
("yggmail", &*P_YGGMAIL),
|
||||
("ziggo.nl", &*P_ZIGGO_NL),
|
||||
("zohomail.eu", &*P_ZOHO),
|
||||
("zoho.com", &*P_ZOHO),
|
||||
@@ -1568,6 +1618,7 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
|
||||
("kolst.com", &*P_KOLST_COM),
|
||||
("kontent.com", &*P_KONTENT_COM),
|
||||
("mail.ru", &*P_MAIL_RU),
|
||||
("mail2tor", &*P_MAIL2TOR),
|
||||
("mailbox.org", &*P_MAILBOX_ORG),
|
||||
("mailo.com", &*P_MAILO_COM),
|
||||
("nauta.cu", &*P_NAUTA_CU),
|
||||
@@ -1592,6 +1643,7 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
|
||||
("web.de", &*P_WEB_DE),
|
||||
("yahoo", &*P_YAHOO),
|
||||
("yandex.ru", &*P_YANDEX_RU),
|
||||
("yggmail", &*P_YGGMAIL),
|
||||
("ziggo.nl", &*P_ZIGGO_NL),
|
||||
("zoho", &*P_ZOHO),
|
||||
]
|
||||
@@ -1601,4 +1653,4 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
|
||||
});
|
||||
|
||||
pub static PROVIDER_UPDATED: Lazy<chrono::NaiveDate> =
|
||||
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 7, 28));
|
||||
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 8, 17));
|
||||
|
||||
@@ -63,7 +63,7 @@ def process_data(data, file):
|
||||
raise TypeError("no domains found")
|
||||
for domain in data["domains"]:
|
||||
domain = cleanstr(domain)
|
||||
if domain == "" or domain.count(".") < 1 or domain.lower() != domain:
|
||||
if domain == "" or domain.lower() != domain:
|
||||
raise TypeError("bad domain: " + domain)
|
||||
|
||||
global domains_set
|
||||
@@ -84,7 +84,7 @@ def process_data(data, file):
|
||||
for s in data["server"]:
|
||||
hostname = cleanstr(s.get("hostname", ""))
|
||||
port = int(s.get("port", ""))
|
||||
if hostname == "" or hostname.count(".") < 1 or port <= 0:
|
||||
if hostname == "" or hostname.lower() != hostname or port <= 0:
|
||||
raise TypeError("bad hostname or port")
|
||||
|
||||
protocol = s.get("type", "").upper()
|
||||
@@ -96,7 +96,7 @@ def process_data(data, file):
|
||||
raise TypeError("bad protocol")
|
||||
|
||||
socket = s.get("socket", "").upper()
|
||||
if socket != "STARTTLS" and socket != "SSL":
|
||||
if socket != "STARTTLS" and socket != "SSL" and socket != "PLAIN":
|
||||
raise TypeError("bad socket")
|
||||
|
||||
username_pattern = s.get("username_pattern", "EMAIL").upper()
|
||||
|
||||
17
src/qr.rs
17
src/qr.rs
@@ -11,6 +11,7 @@ use crate::config::Config;
|
||||
use crate::constants::Blocked;
|
||||
use crate::contact::{addr_normalize, may_be_valid_addr, Contact, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::key::Fingerprint;
|
||||
use crate::log::LogExt;
|
||||
use crate::lot::{Lot, LotState};
|
||||
@@ -160,7 +161,13 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
.await
|
||||
.log_err(context, "Failed to create (new) chat for contact")
|
||||
{
|
||||
chat::add_info_msg(context, chat.id, format!("{} verified.", peerstate.addr)).await;
|
||||
chat::add_info_msg(
|
||||
context,
|
||||
chat.id,
|
||||
format!("{} verified.", peerstate.addr),
|
||||
time(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else if let Some(addr) = addr {
|
||||
lot.state = LotState::QrFprMismatch;
|
||||
@@ -324,11 +331,9 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error
|
||||
let chat_id = if lot.state == LotState::QrReviveVerifyContact {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
get_chat_id_by_grpid(context, &lot.text2.unwrap_or_default())
|
||||
.await?
|
||||
.0,
|
||||
)
|
||||
get_chat_id_by_grpid(context, &lot.text2.unwrap_or_default())
|
||||
.await?
|
||||
.map(|(chat_id, _protected, _blocked)| chat_id)
|
||||
};
|
||||
token::save(
|
||||
context,
|
||||
|
||||
100
src/quota.rs
Normal file
100
src/quota.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_imap::types::{Quota, QuotaResource};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::imap::scan_folders::get_watched_folders;
|
||||
use crate::imap::Imap;
|
||||
use crate::job::{Action, Status};
|
||||
use crate::param::Params;
|
||||
use crate::{job, EventType};
|
||||
|
||||
/// warn about a nearly full mailbox after this usage percentage is reached.
|
||||
/// quota icon is "yellow".
|
||||
pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
|
||||
|
||||
// warning is already issued at QUOTA_WARN_THRESHOLD_PERCENTAGE,
|
||||
// this threshold only makes the quota icon "red".
|
||||
pub const QUOTA_ERROR_THRESHOLD_PERCENTAGE: u64 = 99;
|
||||
|
||||
// if recent quota is older,
|
||||
// it is re-fetched on dc_get_connectivity_html()
|
||||
pub const QUOTA_MAX_AGE_SECONDS: i64 = 60;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuotaInfo {
|
||||
/// Recently loaded quota information.
|
||||
/// set to `Err()` if the provider does not support quota or on other errors,
|
||||
/// set to `Ok()` for valid quota information.
|
||||
/// Updated by `Action::UpdateRecentQuota`
|
||||
pub(crate) recent: Result<IndexMap<String, Vec<QuotaResource>>>,
|
||||
|
||||
/// Timestamp when structure was modified.
|
||||
pub(crate) modified: i64,
|
||||
}
|
||||
|
||||
async fn get_unique_quota_roots_and_usage(
|
||||
folders: Vec<String>,
|
||||
imap: &mut Imap,
|
||||
) -> Result<IndexMap<String, Vec<QuotaResource>>> {
|
||||
let mut unique_quota_roots: IndexMap<String, Vec<QuotaResource>> = IndexMap::new();
|
||||
for folder in folders {
|
||||
let (quota_roots, quotas) = &imap.get_quota_roots(&folder).await?;
|
||||
// if there are new quota roots found in this imap folder, add them to the list
|
||||
for qr_entries in quota_roots {
|
||||
for quota_root_name in &qr_entries.quota_root_names {
|
||||
// the quota for that quota root
|
||||
let quota: Quota = quotas
|
||||
.iter()
|
||||
.find(|q| &q.root_name == quota_root_name)
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow!("quota_root should have a quota"))?;
|
||||
// replace old quotas, because between fetching quotaroots for folders,
|
||||
// messages could be recieved and so the usage could have been changed
|
||||
*unique_quota_roots
|
||||
.entry(quota_root_name.clone())
|
||||
.or_insert(vec![]) = quota.resources;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(unique_quota_roots)
|
||||
}
|
||||
|
||||
impl Context {
|
||||
// Adds a job to update `quota.recent`
|
||||
pub(crate) async fn schedule_quota_update(&self) {
|
||||
job::kill_action(self, Action::UpdateRecentQuota).await;
|
||||
job::add(
|
||||
self,
|
||||
job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Updates `quota.recent`, sets `quota.modified` to the current time
|
||||
/// and emits an event to let the UIs update connectivity view.
|
||||
///
|
||||
/// Called in response to `Action::UpdateRecentQuota`.
|
||||
pub(crate) async fn update_recent_quota(&self, imap: &mut Imap) -> Status {
|
||||
if let Err(err) = imap.prepare(self).await {
|
||||
warn!(self, "could not connect: {:?}", err);
|
||||
return Status::RetryNow;
|
||||
}
|
||||
|
||||
let quota = if imap.can_check_quota() {
|
||||
let folders = get_watched_folders(self).await;
|
||||
get_unique_quota_roots_and_usage(folders, imap).await
|
||||
} else {
|
||||
Err(anyhow!("Quota not supported by your provider."))
|
||||
};
|
||||
|
||||
*self.quota.write().await = Some(QuotaInfo {
|
||||
recent: quota,
|
||||
modified: time(),
|
||||
});
|
||||
|
||||
self.emit_event(EventType::ConnectivityChanged);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ impl Context {
|
||||
pub async fn maybe_network_lost(&self) {
|
||||
let lock = self.scheduler.read().await;
|
||||
lock.maybe_network_lost().await;
|
||||
connectivity::idle_interrupted(lock).await;
|
||||
connectivity::maybe_network_lost(self, lock).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn interrupt_inbox(&self, info: InterruptInfo) {
|
||||
@@ -298,7 +298,11 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
|
||||
None => {
|
||||
// Fake Idle
|
||||
info!(ctx, "smtp fake idle - started");
|
||||
connection.connectivity.set_connected(&ctx).await;
|
||||
match &connection.last_send_error {
|
||||
None => connection.connectivity.set_connected(&ctx).await,
|
||||
Some(err) => connection.connectivity.set_err(&ctx, err).await,
|
||||
}
|
||||
|
||||
interrupt_info = idle_interrupt_receiver.recv().await.unwrap_or_default();
|
||||
info!(ctx, "smtp fake idle - interrupted")
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use async_std::sync::{Mutex, RwLockReadGuard};
|
||||
|
||||
use crate::dc_tools::time;
|
||||
use crate::events::EventType;
|
||||
use crate::quota::{
|
||||
QUOTA_ERROR_THRESHOLD_PERCENTAGE, QUOTA_MAX_AGE_SECONDS, QUOTA_WARN_THRESHOLD_PERCENTAGE,
|
||||
};
|
||||
use crate::{config::Config, scheduler::Scheduler};
|
||||
use crate::{context::Context, log::LogExt};
|
||||
use humansize::{file_size_opts, FileSize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumProperty, PartialOrd, Ord)]
|
||||
pub enum Connectivity {
|
||||
@@ -20,7 +25,7 @@ pub enum Connectivity {
|
||||
// the top) take priority. This means that e.g. if any folder has an error - usually
|
||||
// because there is no internet connection - the connectivity for the whole
|
||||
// account will be `Notconnected`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty, PartialOrd)]
|
||||
enum DetailedConnectivity {
|
||||
Error(String),
|
||||
Uninitialized,
|
||||
@@ -193,6 +198,43 @@ pub(crate) async fn idle_interrupted(scheduler: RwLockReadGuard<'_, Scheduler>)
|
||||
// of what we do here.
|
||||
}
|
||||
|
||||
/// Set the connectivity to "Not connected" after a call to dc_maybe_network_lost().
|
||||
/// If we did not do this, the connectivity would stay "Connected" for quite a long time
|
||||
/// after `maybe_network_lost()` was called.
|
||||
pub(crate) async fn maybe_network_lost(
|
||||
context: &Context,
|
||||
scheduler: RwLockReadGuard<'_, Scheduler>,
|
||||
) {
|
||||
let stores = match &*scheduler {
|
||||
Scheduler::Running {
|
||||
inbox,
|
||||
mvbox,
|
||||
sentbox,
|
||||
..
|
||||
} => [
|
||||
inbox.state.connectivity.clone(),
|
||||
mvbox.state.connectivity.clone(),
|
||||
sentbox.state.connectivity.clone(),
|
||||
],
|
||||
Scheduler::Stopped => return,
|
||||
};
|
||||
drop(scheduler);
|
||||
|
||||
for store in &stores {
|
||||
let mut connectivity_lock = store.0.lock().await;
|
||||
if !matches!(
|
||||
*connectivity_lock,
|
||||
DetailedConnectivity::Uninitialized
|
||||
| DetailedConnectivity::Error(_)
|
||||
| DetailedConnectivity::NotConfigured,
|
||||
) {
|
||||
*connectivity_lock = DetailedConnectivity::Error("Connection lost".to_string());
|
||||
}
|
||||
drop(connectivity_lock);
|
||||
}
|
||||
context.emit_event(EventType::ConnectivityChanged);
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectivityStore {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(guard) = self.0.try_lock() {
|
||||
@@ -366,6 +408,81 @@ impl Context {
|
||||
ret += &*escaper::encode_minimal(&detailed.to_string_smtp(self));
|
||||
ret += "</li></ul>";
|
||||
|
||||
ret += "<h3>Quota</h3><ul>";
|
||||
let quota = self.quota.read().await;
|
||||
if let Some(quota) = &*quota {
|
||||
match "a.recent {
|
||||
Ok(quota) => {
|
||||
let roots_cnt = quota.len();
|
||||
for (root_name, resources) in quota {
|
||||
use async_imap::types::QuotaResourceName::*;
|
||||
for resource in resources {
|
||||
ret += "<li>";
|
||||
|
||||
let usage_percent = resource.get_usage_percentage();
|
||||
if usage_percent >= QUOTA_ERROR_THRESHOLD_PERCENTAGE {
|
||||
ret += "<span class=\"red dot\"></span> ";
|
||||
} else if usage_percent >= QUOTA_WARN_THRESHOLD_PERCENTAGE {
|
||||
ret += "<span class=\"yellow dot\"></span> ";
|
||||
} else {
|
||||
ret += "<span class=\"green dot\"></span> ";
|
||||
}
|
||||
|
||||
// root name is empty eg. for gmail and redundant eg. for riseup.
|
||||
// therefore, use it only if there are really several roots.
|
||||
if roots_cnt > 1 && !root_name.is_empty() {
|
||||
ret +=
|
||||
&format!("<b>{}:</b> ", &*escaper::encode_minimal(root_name));
|
||||
} else {
|
||||
info!(self, "connectivity: root name hidden: \"{}\"", root_name);
|
||||
}
|
||||
|
||||
ret += &match &resource.name {
|
||||
Atom(resource_name) => {
|
||||
format!(
|
||||
"<b>{}:</b> {} of {} used",
|
||||
&*escaper::encode_minimal(resource_name),
|
||||
resource.usage.to_string(),
|
||||
resource.limit.to_string(),
|
||||
)
|
||||
}
|
||||
Message => {
|
||||
format!(
|
||||
"<b>Messages:</b> {} of {} used",
|
||||
resource.usage.to_string(),
|
||||
resource.limit.to_string(),
|
||||
)
|
||||
}
|
||||
Storage => {
|
||||
let usage = (resource.usage * 1024)
|
||||
.file_size(file_size_opts::BINARY)
|
||||
.unwrap_or_default();
|
||||
let limit = (resource.limit * 1024)
|
||||
.file_size(file_size_opts::BINARY)
|
||||
.unwrap_or_default();
|
||||
format!("<b>Storage:</b> {} of {} used", usage, limit)
|
||||
}
|
||||
};
|
||||
ret += &format!(" ({}%)", usage_percent);
|
||||
|
||||
ret += "</li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
ret += format!("<li>{}</li>", e).as_str();
|
||||
}
|
||||
}
|
||||
|
||||
if quota.modified + QUOTA_MAX_AGE_SECONDS < time() {
|
||||
self.schedule_quota_update().await;
|
||||
}
|
||||
} else {
|
||||
ret += "<li>One moment...</li>";
|
||||
self.schedule_quota_update().await;
|
||||
}
|
||||
ret += "</ul>";
|
||||
|
||||
ret += "</body></html>\n";
|
||||
ret
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{bail, Context as _, Error, Result};
|
||||
use anyhow::{anyhow, bail, Context as _, Error, Result};
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::sync::Mutex;
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
@@ -14,6 +14,7 @@ use crate::config::Config;
|
||||
use crate::constants::{Blocked, Viewtype, DC_CONTACT_ID_LAST_SPECIAL};
|
||||
use crate::contact::{Contact, Origin, VerifiedStatus};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::e2ee::ensure_secret_key_exists;
|
||||
use crate::events::EventType;
|
||||
use crate::headerdef::HeaderDef;
|
||||
@@ -327,14 +328,14 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, JoinError> {
|
||||
let start = Instant::now();
|
||||
let chatid = loop {
|
||||
{
|
||||
match chat::get_chat_id_by_grpid(context, &group_id).await {
|
||||
Ok((chatid, _is_protected, _blocked)) => break chatid,
|
||||
Err(err) => {
|
||||
match chat::get_chat_id_by_grpid(context, &group_id).await? {
|
||||
Some((chatid, _is_protected, _blocked)) => break chatid,
|
||||
None => {
|
||||
if start.elapsed() > Duration::from_secs(7) {
|
||||
context.free_ongoing().await;
|
||||
return Err(err
|
||||
.context("Ongoing sender dropped (this is a bug)")
|
||||
.into());
|
||||
return Err(JoinError::Other(anyhow!(
|
||||
"Ongoing sender dropped (this is a bug)"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,8 +651,8 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
match chat::get_chat_id_by_grpid(context, field_grpid).await {
|
||||
Ok((group_chat_id, _, _)) => {
|
||||
match chat::get_chat_id_by_grpid(context, field_grpid).await? {
|
||||
Some((group_chat_id, _, _)) => {
|
||||
if let Err(err) =
|
||||
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
|
||||
.await
|
||||
@@ -659,12 +660,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
error!(context, "failed to add contact: {}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "Chat {} not found: {}", &field_grpid, err);
|
||||
return Err(
|
||||
err.context(format!("Chat for group {} not found", &field_grpid))
|
||||
);
|
||||
}
|
||||
None => bail!("Chat {} not found", &field_grpid),
|
||||
}
|
||||
} else {
|
||||
// Alice -> Bob
|
||||
@@ -864,7 +860,7 @@ async fn secure_connection_established(
|
||||
"?"
|
||||
};
|
||||
let msg = stock_str::contact_verified(context, addr).await;
|
||||
chat::add_info_msg(context, contact_chat_id, msg).await;
|
||||
chat::add_info_msg(context, contact_chat_id, msg, time()).await;
|
||||
emit_event!(context, EventType::ChatModified(contact_chat_id));
|
||||
info!(context, "StockMessage::ContactVerified posted to 1:1 chat");
|
||||
|
||||
@@ -888,7 +884,7 @@ async fn could_not_establish_secure_connection(
|
||||
)
|
||||
.await;
|
||||
|
||||
chat::add_info_msg(context, contact_chat_id, &msg).await;
|
||||
chat::add_info_msg(context, contact_chat_id, &msg, time()).await;
|
||||
error!(
|
||||
context,
|
||||
"StockMessage::ContactNotVerified posted to 1:1 chat ({})", details
|
||||
|
||||
@@ -12,7 +12,7 @@ use anyhow::{Error, Result};
|
||||
use async_std::sync::MutexGuard;
|
||||
|
||||
use crate::chat::{self, ChatId};
|
||||
use crate::constants::{Blocked, Viewtype};
|
||||
use crate::constants::Viewtype;
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
@@ -336,9 +336,10 @@ impl BobState {
|
||||
// the very handshake message we're handling now. But
|
||||
// only after we have returned. It does not impact
|
||||
// the security invariants of secure-join however.
|
||||
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, grpid)
|
||||
.await
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
||||
|
||||
let is_verified_group = chat::get_chat_id_by_grpid(context, grpid)
|
||||
.await?
|
||||
.map_or(false, |(_chat_id, is_protected, _blocked)| is_protected);
|
||||
// when joining a non-verified group
|
||||
// the vg-member-added message may be unencrypted
|
||||
// when not all group members have keys or prefer encryption.
|
||||
|
||||
25
src/smtp.rs
25
src/smtp.rs
@@ -54,6 +54,9 @@ pub(crate) struct Smtp {
|
||||
last_success: Option<SystemTime>,
|
||||
|
||||
pub(crate) connectivity: ConnectivityStore,
|
||||
|
||||
/// If sending the last message failed, contains the error message.
|
||||
pub(crate) last_send_error: Option<String>,
|
||||
}
|
||||
|
||||
impl Smtp {
|
||||
@@ -100,20 +103,14 @@ impl Smtp {
|
||||
|
||||
self.connectivity.set_connecting(context).await;
|
||||
let lp = LoginParam::from_database(context, "configured_").await?;
|
||||
let res = self
|
||||
.connect(
|
||||
context,
|
||||
&lp.smtp,
|
||||
&lp.addr,
|
||||
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
lp.provider.map_or(false, |provider| provider.strict_tls),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = &res {
|
||||
self.connectivity.set_err(context, err).await;
|
||||
}
|
||||
res
|
||||
self.connect(
|
||||
context,
|
||||
&lp.smtp,
|
||||
&lp.addr,
|
||||
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
lp.provider.map_or(false, |provider| provider.strict_tls),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Connect using the provided login params.
|
||||
|
||||
@@ -897,10 +897,6 @@ impl Context {
|
||||
|
||||
// add welcome-messages. by the label, this is done only once,
|
||||
// if the user has deleted the message or the chat, it is not added again.
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(device_messages_hint(self).await);
|
||||
chat::add_device_msg(self, Some("core-about-device-chat"), Some(&mut msg)).await?;
|
||||
|
||||
let image = include_bytes!("../assets/welcome-image.jpg");
|
||||
let blob = BlobObject::create(self, "welcome-image.jpg", image).await?;
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
|
||||
@@ -413,7 +413,7 @@ impl TestContext {
|
||||
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could
|
||||
// merge them to a public function in the `deltachat` crate.
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::clippy::indexing_slicing)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
pub async fn print_chat(&self, chat_id: ChatId) {
|
||||
let msglist = chat::get_chat_msgs(self, chat_id, 0x1, None).await.unwrap();
|
||||
let msglist: Vec<MsgId> = msglist
|
||||
|
||||
@@ -10,6 +10,7 @@ Text and Quote encoding | Fixed, Flowed ([RFC 3676](https://tools.ietf.
|
||||
Filename encoding | Encoded Words ([RFC 2047](https://tools.ietf.org/html/rfc2047)), Encoded Word Extensions ([RFC 2231](https://tools.ietf.org/html/rfc2231))
|
||||
Identify server folders | IMAP LIST Extension ([RFC 6154](https://tools.ietf.org/html/rfc6154))
|
||||
Push | IMAP IDLE ([RFC 2177](https://tools.ietf.org/html/rfc2177))
|
||||
Quota | IMAP QUOTA extension ([RFC 2087](https://tools.ietf.org/html/rfc2087))
|
||||
Authorization | OAuth2 ([RFC 6749](https://tools.ietf.org/html/rfc6749))
|
||||
End-to-end encryption | [Autocrypt Level 1](https://autocrypt.org/level1.html), OpenPGP ([RFC 4880](https://tools.ietf.org/html/rfc4880)), Security Multiparts for MIME ([RFC 1847](https://tools.ietf.org/html/rfc1847)) and [“Mixed Up” Encryption repairing](https://tools.ietf.org/id/draft-dkg-openpgp-pgpmime-message-mangling-00.html)
|
||||
Configuration assistance | [Autoconfigure](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover](https://technet.microsoft.com/library/bb124251(v=exchg.150).aspx)
|
||||
|
||||
70
test-data/message/gmx-forward.eml
Normal file
70
test-data/message/gmx-forward.eml
Normal file
@@ -0,0 +1,70 @@
|
||||
Return-Path: <alice@example.com>
|
||||
Delivered-To: bob@example.org
|
||||
Received: from hq5.merlinux.eu
|
||||
by hq5.merlinux.eu with LMTP
|
||||
id GJ4eNagpFWF5UwAAPzvFDg
|
||||
(envelope-from <alice@example.com>)
|
||||
for <bob@example.org>; Thu, 12 Aug 2021 16:01:12 +0200
|
||||
Received: from mout.gmx.net (mout.gmx.net [212.227.17.22])
|
||||
by hq5.merlinux.eu (Postfix) with ESMTPS id 3033227A0003
|
||||
for <bob@example.org>; Thu, 12 Aug 2021 16:01:12 +0200 (CEST)
|
||||
Authentication-Results: hq5.merlinux.eu;
|
||||
dkim=pass (1024-bit key; secure) header.d=gmx.net header.i=@gmx.net header.b="I/oyQzjt";
|
||||
dkim-atps=neutral
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net;
|
||||
s=badeba3b8450; t=1628776871;
|
||||
bh=yVNQf4XVEEjSt/PzPPM4F9JXYSv2/ynVmb/E4dc6Qpk=;
|
||||
h=X-UI-Sender-Class:From:To:Subject:Date;
|
||||
b=I/oyQzjtFVDJiKkKV2/9DimrUXwhNtrHc5sgFkO7HNz6sheW8t0+8WpL76AfLuUU2
|
||||
KZ/bCPyX3oItKl+31HZMoekrRnDyHiahsF1h3VrSzDXo3K0sk6nmZBjIQLuksGFW5i
|
||||
/+5TkQ+p79YB/HioYm08pewz08caHfCt3EqcuJik=
|
||||
X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c
|
||||
Received: from [193.96.224.73] ([193.96.224.73]) by web-mail.gmx.net
|
||||
(3c-app-gmx-bap57.server.lan [172.19.172.127]) (via HTTP); Thu, 12 Aug 2021
|
||||
16:01:11 +0200
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <trinity-18545f24-4f02-4dc8-9f80-8d2646646d03-1628776871644@3c-app-gmx-bap57>
|
||||
From: Alice <alice@example.com>
|
||||
To: bob@example.org
|
||||
Subject: Fw: subject
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Date: Thu, 12 Aug 2021 16:01:11 +0200
|
||||
Importance: normal
|
||||
Sensitivity: Normal
|
||||
X-UI-Message-Type: mail
|
||||
X-Priority: 3
|
||||
X-Provags-ID: V03:K1:vvJyGpka3W40JnBiz1FlxtNcdZw52KVodkX04BbaREoVau28F88gbimeb2Tm1t58pzQDe
|
||||
HPoPiTTgz3Roj8GM/iIs4FqZxzcPiekR59a/GwFr16mPZQj+1cq6QOk144bXysBz3PHroQrc7Ctx
|
||||
MtVLAY9w5+Lpuql24x9IqjA0eN2ytYrYYgX60d2FgU8CN/azK0bdEcsdyfwnAcf0bW9UY4ghE8Gt
|
||||
hRe8z4WV6qEEzlhU+cI1uAixvNdQ6MFoi1oT7LdvfbUdcm1CBytWbbieGF1LjMa5Y+D4MZ3zUiiY
|
||||
Ys=
|
||||
X-Spam-Flag: NO
|
||||
X-UI-Out-Filterresults: notjunk:1;V03:K0:pksZU4GoRZI=:jPKwLt7m9sSdgel28Ha/o7
|
||||
UdLaJvQkSOD2tUGBq7n9rGeKT3opdBO5SWDRhn/qWLn+muPPYIjwmyE0XiGIjgTxLDJbY/LHL
|
||||
bNWfcZ+geulQn9vH9muMcAW7ThwACRj3CCtWpc4y5ffTbo8VEinde4C4XFuhSUUdqyzu0GxYc
|
||||
FklFTMlpL9ELxn5Mo3MaOnzznwrchd/2ogGzFz9wOtYUot+llyK+VLaylMeSSTIWbSLHwmA7l
|
||||
MwsujGm4OvqP4VXSpVY2MecAGGwEvPsMQ/hfMgDsxRRm3sFsVZf6KFKcngZte0Nq6LZO9QU1x
|
||||
tAmMgjZYPfOE+YSFiuKJ8E3YlsMk58HYTw/ON+m5T+lXSJeWVLA7sOgk9NKBGi2VzvrRz3YSg
|
||||
MysXD8/h8PU7Rj7a2pttFyGxuN397xP3u1A+15LH5M2+AhUy4quzmxC0Ozb2chPdMJHgTO99e
|
||||
5tmLkyYeeREmSB89pFzyOHGghENBflocaDiCidgWm6pd1lfMMjMQ8bA3S/QpE8e913WGCWhVQ
|
||||
uecX4FBK1VEl8WkE/0GQhY8+2mzBE0+Jo1LCKJtAo9h8bG2fNJkujOpKKvUoududAYuajaHuq
|
||||
rVl6G/xOP8JB3FDDNhZQptleN3KU5qPqNYz0qYibUCJNadS6XlwrfkZReJOk3yHnbIUvB9IG1
|
||||
WGu+K/8WtQaYtzmNtZLD3c7YzQZT4v5xzxQ3TtROawkGNGk4gYJTnAd1ZWOkBHEjcSLsYFVYg
|
||||
nhkLeamJ3KnnkBMJromM0tc0PmSdb/hqD/8hkrWQFvK/nmdNm9+z8UCmCTSDV98UodcwpAkJB
|
||||
D+/kEFR3Y5K904h2dhgmSbnqZAVEziDNT2TylwBnxrpvIKX5Xw=
|
||||
|
||||
<html><head></head><body><div style="font-family: Verdana;font-size: 12.0px;"><div> </div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div data-darkreader-inline-border-left="" name="quote" style="margin: 10px 5px 5px 10px; padding: 10px 0px 10px 10px; border-left: 2px solid rgb(195, 217, 229); overflow-wrap: break-word; --darkreader-inline-border-left:#274759;">
|
||||
<div style="margin:0 0 10px 0;"><b>Gesendet:</b> Donnerstag, 12. August 2021 um 15:52 Uhr<br/>
|
||||
<b>Von:</b> "Claire" <claire@example.org><br/>
|
||||
<b>An:</b> alice@example.com<br/>
|
||||
<b>Betreff:</b> subject</div>
|
||||
|
||||
<div name="quoted-content">bodytext</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></div></body></html>
|
||||
|
||||
Reference in New Issue
Block a user