mirror of
https://github.com/chatmail/core.git
synced 2026-10-16 10:01:05 +03:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e699fa0a6 | ||
|
|
55cdbdc085 | ||
|
|
58620988d7 | ||
|
|
467f313091 | ||
|
|
091578573a | ||
|
|
62c1237024 | ||
|
|
8d41d02397 | ||
|
|
fce3f80654 | ||
|
|
2a0a51bea0 | ||
|
|
91d94d5920 | ||
|
|
c59f21230d | ||
|
|
828cc1fbd1 | ||
|
|
57f4958fc6 | ||
|
|
3aeb57b4df | ||
|
|
1b85614db9 | ||
|
|
57ecf49eb1 | ||
|
|
f279b0d1e5 | ||
|
|
32071297e6 | ||
|
|
1d98c38ff3 | ||
|
|
c09e0e2b65 | ||
|
|
0c8f967391 | ||
|
|
aca34379e0 |
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.74.0
|
||||
RUSTUP_TOOLCHAIN: 1.74.1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install rustfmt and clippy
|
||||
@@ -76,11 +76,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust: 1.74.0
|
||||
rust: 1.74.1
|
||||
- os: windows-latest
|
||||
rust: 1.74.0
|
||||
rust: 1.74.1
|
||||
- os: macos-latest
|
||||
rust: 1.74.0
|
||||
rust: 1.74.1
|
||||
|
||||
# Minimum Supported Rust Version = 1.70.0
|
||||
- os: ubuntu-latest
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## [1.132.1] - 2023-12-12
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Add "From:" to protected headers for signed-only messages.
|
||||
- Sync user actions for ad-hoc groups across devices ([#5065](https://github.com/deltachat/deltachat-core-rust/pull/5065)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Add padlock to empty part if the whole message is empty.
|
||||
- Renew IDLE timeout on keepalives and reduce it to 5 minutes.
|
||||
- connectivity: Return false from `all_work_done()` immediately after connecting (iOS notification fix).
|
||||
|
||||
### API-Changes
|
||||
|
||||
- deltachat-jsonrpc-client: add `Account.{import,export}_self_keys`.
|
||||
|
||||
### CI
|
||||
|
||||
- Update to Rust 1.74.1.
|
||||
|
||||
## [1.132.0] - 2023-12-06
|
||||
|
||||
### Features / Changes
|
||||
@@ -3334,3 +3355,5 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.131.7]: https://github.com/deltachat/deltachat-core-rust/compare/v1.131.6...v1.131.7
|
||||
[1.131.8]: https://github.com/deltachat/deltachat-core-rust/compare/v1.131.7...v1.131.8
|
||||
[1.131.9]: https://github.com/deltachat/deltachat-core-rust/compare/v1.131.8...v1.131.9
|
||||
[1.132.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.131.9...v1.132.0
|
||||
[1.132.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.132.0...v1.132.1
|
||||
|
||||
Generated
+83
-81
@@ -224,9 +224,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.9.4"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d736a74edf6c327b53dd9c932eae834253470ac5f0c55770e7e133bcbf986362"
|
||||
checksum = "a9d69fc1499878158750f644c4eb46aff55bb9d32d77e3dc4aecf8308d5c3ba6"
|
||||
dependencies = [
|
||||
"async-channel 2.1.1",
|
||||
"base64 0.21.5",
|
||||
@@ -290,7 +290,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -768,9 +768,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.5"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
@@ -890,9 +890,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
@@ -901,22 +901,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.15"
|
||||
version = "0.9.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -993,7 +992,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1091,7 +1090,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1169,7 +1168,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.1.1",
|
||||
@@ -1193,7 +1192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1208,7 +1207,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1228,12 +1227,12 @@ name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1437,7 +1436,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1730,7 +1729,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1743,7 +1742,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2057,7 +2056,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2291,9 +2290,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.3"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
@@ -2309,11 +2308,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.5"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2351,9 +2350,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.11",
|
||||
@@ -2436,7 +2435,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"h2 0.3.22",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.5",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
@@ -2622,7 +2621,8 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
[[package]]
|
||||
name = "iroh"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/n0-computer/iroh?branch=maint-0.4#9881b7886235035a1124e4371f7a4cd59379e51b"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85075391dcb8491a4939266334b28601052d418b37d20b33c58ffb5776adc912"
|
||||
dependencies = [
|
||||
"abao",
|
||||
"anyhow",
|
||||
@@ -2684,9 +2684,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
@@ -2756,9 +2756,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.150"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -3090,7 +3090,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3165,9 +3165,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@@ -3204,7 +3204,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3215,9 +3215,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.1.6+3.1.4"
|
||||
version = "300.2.1+3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085"
|
||||
checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -3441,7 +3441,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3987,7 +3987,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"h2 0.3.22",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.5",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.27",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
@@ -4174,9 +4174,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.26"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
@@ -4187,9 +4187,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.9"
|
||||
version = "0.21.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
|
||||
checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
|
||||
dependencies = [
|
||||
"ring 0.17.7",
|
||||
"rustls-webpki",
|
||||
@@ -4258,9 +4258,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "safemem"
|
||||
@@ -4437,7 +4437,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4738,7 +4738,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4760,9 +4760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
version = "2.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4852,9 +4852,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "testdir"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91fa189a0a10aa81fbf27a34463477d45d691b8c00838ab19e080b54c6947cb4"
|
||||
checksum = "ee79e927b64d193f5abb60d20a0eb56be0ee5a242fdeb8ce3bf054177006de52"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
@@ -4877,22 +4877,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.50"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.50"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4972,9 +4972,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.34.0"
|
||||
version = "1.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
|
||||
checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -5007,7 +5007,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5169,7 +5169,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5223,9 +5223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
@@ -5293,9 +5293,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -5457,7 +5457,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -5491,7 +5491,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -5751,9 +5751,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.25"
|
||||
version = "0.5.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e87b8dfbe3baffbe687eef2e164e32286eff31a5ee16463ce03d991643ec94"
|
||||
checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -5800,11 +5800,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.0.1"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985"
|
||||
checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5860,22 +5862,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.29"
|
||||
version = "0.7.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d075cf85bbb114e933343e087b92f2146bac0d55b534cbb8188becf0039948e"
|
||||
checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.29"
|
||||
version = "0.7.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86cd5ca076997b97ef09d3ad65efe811fa68c9e874cb636ccb211223a813b0c2"
|
||||
checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5895,5 +5897,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.70"
|
||||
@@ -40,7 +40,7 @@ ratelimit = { path = "./deltachat-ratelimit" }
|
||||
|
||||
anyhow = "1"
|
||||
async-channel = "2.0.0"
|
||||
async-imap = { version = "0.9.1", default-features = false, features = ["runtime-tokio"] }
|
||||
async-imap = { version = "0.9.5", default-features = false, features = ["runtime-tokio"] }
|
||||
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
|
||||
@@ -59,7 +59,7 @@ hex = "0.4.0"
|
||||
hickory-resolver = "0.24"
|
||||
humansize = "2"
|
||||
image = { version = "0.24.7", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh = { git = "https://github.com/n0-computer/iroh", branch = "maint-0.4", default-features = false }
|
||||
iroh = { version = "0.4.2", default-features = false }
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
|
||||
@@ -53,5 +53,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.132.0"
|
||||
"version": "1.132.1"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -300,3 +300,13 @@ class Account:
|
||||
def import_backup(self, path, passphrase: str = "") -> None:
|
||||
"""Import backup."""
|
||||
self._rpc.import_backup(self.id, str(path), passphrase)
|
||||
|
||||
def export_self_keys(self, path) -> None:
|
||||
"""Export keys."""
|
||||
passphrase = "" # Setting passphrase is currently not supported.
|
||||
self._rpc.export_self_keys(self.id, str(path), passphrase)
|
||||
|
||||
def import_self_keys(self, path) -> None:
|
||||
"""Import keys."""
|
||||
passphrase = "" # Importing passphrase-protected keys is currently not supported.
|
||||
self._rpc.import_self_keys(self.id, str(path), passphrase)
|
||||
|
||||
@@ -331,7 +331,7 @@ def test_wait_next_messages(acfactory) -> None:
|
||||
next_messages_task = executor.submit(bot.wait_next_messages)
|
||||
|
||||
bot_addr = bot.get_config("addr")
|
||||
alice_contact_bot = alice.create_contact(bot_addr, "Bob")
|
||||
alice_contact_bot = alice.create_contact(bot_addr, "Bot")
|
||||
alice_chat_bot = alice_contact_bot.create_chat()
|
||||
alice_chat_bot.send_text("Hello!")
|
||||
|
||||
@@ -341,7 +341,7 @@ def test_wait_next_messages(acfactory) -> None:
|
||||
assert snapshot.text == "Hello!"
|
||||
|
||||
|
||||
def test_import_export(acfactory, tmp_path) -> None:
|
||||
def test_import_export_backup(acfactory, tmp_path) -> None:
|
||||
alice = acfactory.new_configured_account()
|
||||
alice.export_backup(tmp_path)
|
||||
|
||||
@@ -352,6 +352,31 @@ def test_import_export(acfactory, tmp_path) -> None:
|
||||
assert alice2.manager.get_system_info()
|
||||
|
||||
|
||||
def test_import_export_keys(acfactory, tmp_path) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
bob_addr = bob.get_config("addr")
|
||||
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
alice_chat_bob.send_text("Hello Bob!")
|
||||
|
||||
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Hello Bob!"
|
||||
|
||||
# Alice resetups account, but keeps the key.
|
||||
alice_keys_path = tmp_path / "alice_keys"
|
||||
alice_keys_path.mkdir()
|
||||
alice.export_self_keys(alice_keys_path)
|
||||
alice = acfactory.resetup_account(alice)
|
||||
alice.import_self_keys(alice_keys_path)
|
||||
|
||||
snapshot.chat.accept()
|
||||
snapshot.chat.send_text("Hello Alice!")
|
||||
snapshot = alice.get_message_by_id(alice.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Hello Alice!"
|
||||
assert snapshot.show_padlock
|
||||
|
||||
|
||||
def test_openrpc_command_line() -> None:
|
||||
"""Test that "deltachat-rpc-server --openrpc" command returns an OpenRPC specification."""
|
||||
out = subprocess.run(["deltachat-rpc-server", "--openrpc"], capture_output=True, check=True).stdout
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.132.0"
|
||||
version = "1.132.1"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -102,5 +102,4 @@ github = [
|
||||
"async-email",
|
||||
"deltachat",
|
||||
"djc",
|
||||
"n0-computer", # iroh
|
||||
]
|
||||
|
||||
+1
-1
@@ -56,5 +56,5 @@
|
||||
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
|
||||
},
|
||||
"types": "node/dist/index.d.ts",
|
||||
"version": "1.132.0"
|
||||
"version": "1.132.1"
|
||||
}
|
||||
|
||||
@@ -1979,6 +1979,32 @@ def test_connectivity(acfactory, lp):
|
||||
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
|
||||
|
||||
|
||||
def test_all_work_done(acfactory, lp):
|
||||
"""
|
||||
Tests that calling start_io() immediately followed by maybe_network()
|
||||
and then waiting for all_work_done() reliably fetches the messages
|
||||
delivered while account was offline.
|
||||
In other words, connectivity should not change to a state
|
||||
where all_work_done() returns true until IMAP connection goes idle.
|
||||
"""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
ac1.stop_io()
|
||||
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
|
||||
|
||||
ac1.direct_imap.select_config_folder("inbox")
|
||||
with ac1.direct_imap.idle() as idle1:
|
||||
ac2.create_chat(ac1).send_text("Hi")
|
||||
idle1.wait_for_new_message()
|
||||
|
||||
ac1.start_io()
|
||||
ac1.maybe_network()
|
||||
ac1._evtracker.wait_for_all_work_done()
|
||||
msgs = ac1.create_chat(ac2).get_messages()
|
||||
assert len(msgs) == 1
|
||||
assert msgs[0].text == "Hi"
|
||||
|
||||
|
||||
def test_fetch_deleted_msg(acfactory, lp):
|
||||
"""This is a regression test: Messages with \\Deleted flag were downloaded again and again,
|
||||
hundreds of times, because uid_next was not updated.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2023-12-06
|
||||
2023-12-12
|
||||
+222
-48
@@ -209,6 +209,30 @@ impl ChatId {
|
||||
self == DC_CHAT_ID_ALLDONE_HINT
|
||||
}
|
||||
|
||||
/// Returns [`ChatId`] of a chat that `msg` belongs to.
|
||||
///
|
||||
/// Checks that `msg` is assigned to the right chat.
|
||||
pub(crate) fn lookup_by_message(msg: &Message) -> Option<Self> {
|
||||
if msg.chat_id == DC_CHAT_ID_TRASH {
|
||||
return None;
|
||||
}
|
||||
if msg.download_state != DownloadState::Done
|
||||
// TODO (2023-09-12): Added for backward compatibility with versions that did not have
|
||||
// `DownloadState::Undecipherable`. Remove eventually with the comment in
|
||||
// `MimeMessage::from_bytes()`.
|
||||
|| msg
|
||||
.error
|
||||
.as_ref()
|
||||
.filter(|e| e.starts_with("Decrypting failed:"))
|
||||
.is_some()
|
||||
{
|
||||
// If `msg` is not fully downloaded or undecipherable, it may have been assigned to the
|
||||
// wrong chat (they often get assigned to the 1:1 chat with the sender).
|
||||
return None;
|
||||
}
|
||||
Some(msg.chat_id)
|
||||
}
|
||||
|
||||
/// Returns the [`ChatId`] for the 1:1 chat with `contact_id`
|
||||
/// if it exists and is not blocked.
|
||||
///
|
||||
@@ -374,6 +398,7 @@ impl ChatId {
|
||||
|
||||
pub(crate) async fn block_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
let mut delete = false;
|
||||
|
||||
match chat.typ {
|
||||
Chattype::Broadcast => {
|
||||
@@ -392,7 +417,7 @@ impl ChatId {
|
||||
}
|
||||
Chattype::Group => {
|
||||
info!(context, "Can't block groups yet, deleting the chat.");
|
||||
self.delete(context).await?;
|
||||
delete = true;
|
||||
}
|
||||
Chattype::Mailinglist => {
|
||||
if self.set_blocked(context, Blocked::Yes).await? {
|
||||
@@ -408,6 +433,9 @@ impl ChatId {
|
||||
.log_err(context)
|
||||
.ok();
|
||||
}
|
||||
if delete {
|
||||
self.delete(context).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -550,7 +578,7 @@ impl ChatId {
|
||||
///
|
||||
/// `timestamp_sort` is used as the timestamp of the added message
|
||||
/// and should be the timestamp of the change happening.
|
||||
pub(crate) async fn set_protection(
|
||||
async fn set_protection_for_timestamp_sort(
|
||||
self,
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
@@ -572,6 +600,24 @@ impl ChatId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets protection and sends or adds a message.
|
||||
///
|
||||
/// `timestamp_sent` is the "sent" timestamp of a message caused the protection state change.
|
||||
pub(crate) async fn set_protection(
|
||||
self,
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
timestamp_sent: i64,
|
||||
contact_id: Option<ContactId>,
|
||||
) -> Result<()> {
|
||||
let sort_to_bottom = true;
|
||||
let ts = self
|
||||
.calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, false)
|
||||
.await?;
|
||||
self.set_protection_for_timestamp_sort(context, protect, ts, contact_id)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the 1:1 chat with the given address to ProtectionStatus::Protected,
|
||||
/// and posts a `SystemMessage::ChatProtectionEnabled` into it.
|
||||
///
|
||||
@@ -579,6 +625,7 @@ impl ChatId {
|
||||
pub(crate) async fn set_protection_for_contact(
|
||||
context: &Context,
|
||||
contact_id: ContactId,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
let chat_id = ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Yes)
|
||||
.await
|
||||
@@ -587,7 +634,7 @@ impl ChatId {
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
smeared_time(context),
|
||||
timestamp,
|
||||
Some(contact_id),
|
||||
)
|
||||
.await?;
|
||||
@@ -1124,47 +1171,46 @@ impl ChatId {
|
||||
Ok(self.get_param(context).await?.exists(Param::Devicetalk))
|
||||
}
|
||||
|
||||
async fn parent_query<T, F>(self, context: &Context, fields: &str, f: F) -> Result<Option<T>>
|
||||
async fn parent_query<T, F>(
|
||||
self,
|
||||
context: &Context,
|
||||
fields: &str,
|
||||
state_out_min: MessageState,
|
||||
f: F,
|
||||
) -> Result<Option<T>>
|
||||
where
|
||||
F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let sql = &context.sql;
|
||||
// Do not reply to not fully downloaded messages. Such a message could be a group chat
|
||||
// message that we assigned to 1:1 chat.
|
||||
let query = format!(
|
||||
"SELECT {fields} \
|
||||
FROM msgs WHERE chat_id=? AND state NOT IN (?, ?) AND NOT hidden AND download_state={} \
|
||||
FROM msgs \
|
||||
WHERE chat_id=? \
|
||||
AND ((state BETWEEN {} AND {}) OR (state >= {})) \
|
||||
AND NOT hidden \
|
||||
AND download_state={} \
|
||||
ORDER BY timestamp DESC, id DESC \
|
||||
LIMIT 1;",
|
||||
DownloadState::Done as u32,
|
||||
MessageState::InFresh as u32,
|
||||
MessageState::InSeen as u32,
|
||||
state_out_min as u32,
|
||||
// Do not reply to not fully downloaded messages. Such a message could be a group chat
|
||||
// message that we assigned to 1:1 chat.
|
||||
DownloadState::Done as u32,
|
||||
);
|
||||
let row = sql
|
||||
.query_row_optional(
|
||||
&query,
|
||||
(
|
||||
self,
|
||||
MessageState::OutPreparing,
|
||||
MessageState::OutDraft,
|
||||
// We don't filter `OutPending` and `OutFailed` messages because the new message
|
||||
// for which `parent_query()` is done may assume that it will be received in a
|
||||
// context affected by those messages, e.g. they could add new members to a
|
||||
// group and the new message will contain them in "To:". Anyway recipients must
|
||||
// be prepared to orphaned references.
|
||||
),
|
||||
f,
|
||||
)
|
||||
.await?;
|
||||
Ok(row)
|
||||
sql.query_row_optional(&query, (self,), f).await
|
||||
}
|
||||
|
||||
async fn get_parent_mime_headers(
|
||||
self,
|
||||
context: &Context,
|
||||
state_out_min: MessageState,
|
||||
) -> Result<Option<(String, String, String)>> {
|
||||
self.parent_query(
|
||||
context,
|
||||
"rfc724_mid, mime_in_reply_to, mime_references",
|
||||
state_out_min,
|
||||
|row: &rusqlite::Row| {
|
||||
let rfc724_mid: String = row.get(0)?;
|
||||
let mime_in_reply_to: String = row.get(1)?;
|
||||
@@ -1291,6 +1337,64 @@ impl ChatId {
|
||||
.unwrap_or_default();
|
||||
Ok(protection_status)
|
||||
}
|
||||
|
||||
/// Returns the sort timestamp for a new message in the chat.
|
||||
///
|
||||
/// `message_timestamp` should be either the message "sent" timestamp or a timestamp of the
|
||||
/// corresponding event in case of a system message (usually the current system time).
|
||||
/// `always_sort_to_bottom` makes this ajust the returned timestamp up so that the message goes
|
||||
/// to the chat bottom.
|
||||
/// `incoming` -- whether the message is incoming.
|
||||
pub(crate) async fn calc_sort_timestamp(
|
||||
self,
|
||||
context: &Context,
|
||||
message_timestamp: i64,
|
||||
always_sort_to_bottom: bool,
|
||||
incoming: bool,
|
||||
) -> Result<i64> {
|
||||
let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
|
||||
|
||||
let last_msg_time: Option<i64> = if always_sort_to_bottom {
|
||||
// get newest message for this chat
|
||||
|
||||
// Let hidden messages also be ordered with protection messages because hidden messages
|
||||
// also can be or not be verified, so let's preserve this information -- even it's not
|
||||
// used currently, it can be useful in the future versions.
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state!=?",
|
||||
(self, MessageState::OutDraft),
|
||||
)
|
||||
.await?
|
||||
} else if incoming {
|
||||
// get newest non fresh message for this chat.
|
||||
|
||||
// If a user hasn't been online for some time, the Inbox is fetched first and then the
|
||||
// Sentbox. In order for Inbox and Sent messages to be allowed to mingle, outgoing
|
||||
// messages are purely sorted by their sent timestamp. NB: The Inbox must be fetched
|
||||
// first otherwise Inbox messages would be always below old Sentbox messages. We could
|
||||
// take in the query below only incoming messages, but then new incoming messages would
|
||||
// mingle with just sent outgoing ones and apear somewhere in the middle of the chat.
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND hidden=0 AND state>?",
|
||||
(self, MessageState::InFresh),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(last_msg_time) = last_msg_time {
|
||||
if last_msg_time > sort_timestamp {
|
||||
sort_timestamp = last_msg_time;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(sort_timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ChatId {
|
||||
@@ -1741,7 +1845,15 @@ impl Chat {
|
||||
// we do not set In-Reply-To/References in this case.
|
||||
if !self.is_self_talk() {
|
||||
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
|
||||
self.id.get_parent_mime_headers(context).await?
|
||||
// We don't filter `OutPending` and `OutFailed` messages because the new message for
|
||||
// which `parent_query()` is done may assume that it will be received in a context
|
||||
// affected by those messages, e.g. they could add new members to a group and the
|
||||
// new message will contain them in "To:". Anyway recipients must be prepared to
|
||||
// orphaned references.
|
||||
self
|
||||
.id
|
||||
.get_parent_mime_headers(context, MessageState::OutPending)
|
||||
.await?
|
||||
{
|
||||
// "In-Reply-To:" is not changed if it is set manually.
|
||||
// This does not affect "References:" header, it will contain "default parent" (the
|
||||
@@ -1959,10 +2071,26 @@ impl Chat {
|
||||
Ok(r)
|
||||
}
|
||||
Chattype::Broadcast | Chattype::Group | Chattype::Mailinglist => {
|
||||
if self.grpid.is_empty() {
|
||||
return Ok(None);
|
||||
if !self.grpid.is_empty() {
|
||||
return Ok(Some(SyncId::Grpid(self.grpid.clone())));
|
||||
}
|
||||
Ok(Some(SyncId::Grpid(self.grpid.clone())))
|
||||
|
||||
let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
|
||||
.id
|
||||
.get_parent_mime_headers(context, MessageState::OutDelivered)
|
||||
.await?
|
||||
else {
|
||||
warn!(
|
||||
context,
|
||||
"Chat::get_sync_id({}): No good message identifying the chat found.",
|
||||
self.id
|
||||
);
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(SyncId::Msgids(vec![
|
||||
parent_in_reply_to,
|
||||
parent_rfc724_mid,
|
||||
])))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3207,7 +3335,7 @@ pub async fn create_group_chat(
|
||||
|
||||
if protect == ProtectionStatus::Protected {
|
||||
chat_id
|
||||
.set_protection(context, protect, timestamp, None)
|
||||
.set_protection_for_timestamp_sort(context, protect, timestamp, None)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -4242,8 +4370,8 @@ async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String])
|
||||
pub(crate) enum SyncId {
|
||||
ContactAddr(String),
|
||||
Grpid(String),
|
||||
// NOTE: Ad-hoc groups lack an identifier that can be used across devices so
|
||||
// block/mute/etc. actions on them are not synchronized to other devices.
|
||||
/// "Message-ID"-s, from oldest to latest. Used for ad-hoc groups.
|
||||
Msgids(Vec<String>),
|
||||
}
|
||||
|
||||
/// An action synchronised to other devices.
|
||||
@@ -4266,12 +4394,9 @@ impl Context {
|
||||
pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
|
||||
let chat_id = match id {
|
||||
SyncId::ContactAddr(addr) => {
|
||||
let Some(contact_id) =
|
||||
Contact::lookup_id_by_addr_ex(self, addr, Origin::Unknown, None).await?
|
||||
else {
|
||||
warn!(self, "sync_alter_chat: No contact for addr '{addr}'.");
|
||||
return Ok(());
|
||||
};
|
||||
let contact_id = Contact::lookup_id_by_addr_ex(self, addr, Origin::Unknown, None)
|
||||
.await?
|
||||
.with_context(|| format!("No contact for addr '{addr}'"))?;
|
||||
match action {
|
||||
SyncAction::Block => {
|
||||
return contact::set_blocked(self, Nosync, contact_id, true).await
|
||||
@@ -4281,22 +4406,26 @@ impl Context {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let Some(chat_id) = ChatId::lookup_by_contact(self, contact_id).await? else {
|
||||
warn!(self, "sync_alter_chat: No chat for addr '{addr}'.");
|
||||
return Ok(());
|
||||
};
|
||||
chat_id
|
||||
ChatId::lookup_by_contact(self, contact_id)
|
||||
.await?
|
||||
.with_context(|| format!("No chat for addr '{addr}'"))?
|
||||
}
|
||||
SyncId::Grpid(grpid) => {
|
||||
if let SyncAction::CreateBroadcast(name) = action {
|
||||
create_broadcast_list_ex(self, Nosync, grpid.clone(), name.clone()).await?;
|
||||
return Ok(());
|
||||
}
|
||||
let Some((chat_id, ..)) = get_chat_id_by_grpid(self, grpid).await? else {
|
||||
warn!(self, "sync_alter_chat: No chat for grpid '{grpid}'.");
|
||||
return Ok(());
|
||||
};
|
||||
chat_id
|
||||
get_chat_id_by_grpid(self, grpid)
|
||||
.await?
|
||||
.with_context(|| format!("No chat for grpid '{grpid}'"))?
|
||||
.0
|
||||
}
|
||||
SyncId::Msgids(msgids) => {
|
||||
let msg = message::get_latest_by_rfc724_mids(self, msgids)
|
||||
.await?
|
||||
.with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
|
||||
ChatId::lookup_by_message(&msg)
|
||||
.with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
|
||||
}
|
||||
};
|
||||
match action {
|
||||
@@ -6941,6 +7070,51 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_sync_adhoc_grp() -> Result<()> {
|
||||
let alice0 = &TestContext::new_alice().await;
|
||||
let alice1 = &TestContext::new_alice().await;
|
||||
for a in [alice0, alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
|
||||
let mut chat_ids = Vec::new();
|
||||
for a in [alice0, alice1] {
|
||||
let msg = receive_imf(
|
||||
a,
|
||||
b"Subject: =?utf-8?q?Message_from_alice=40example=2Eorg?=\r\n\
|
||||
From: alice@example.org\r\n\
|
||||
To: <bob@example.net>, <fiona@example.org> \r\n\
|
||||
Date: Mon, 2 Dec 2023 16:59:39 +0000\r\n\
|
||||
Message-ID: <Mr.alices_original_mail@example.org>\r\n\
|
||||
Chat-Version: 1.0\r\n\
|
||||
\r\n\
|
||||
hi\r\n",
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.unwrap();
|
||||
chat_ids.push(msg.chat_id);
|
||||
}
|
||||
let chat1 = Chat::load_from_db(alice1, chat_ids[1]).await?;
|
||||
assert_eq!(chat1.typ, Chattype::Group);
|
||||
assert!(chat1.grpid.is_empty());
|
||||
|
||||
// Test synchronisation on chat blocking because it causes chat deletion currently and thus
|
||||
// requires generating a sync message in advance.
|
||||
chat_ids[0].block(alice0).await?;
|
||||
sync(alice0, alice1).await;
|
||||
assert!(Chat::load_from_db(alice1, chat_ids[1]).await.is_err());
|
||||
assert!(
|
||||
!alice1
|
||||
.sql
|
||||
.exists("SELECT COUNT(*) FROM chats WHERE id=?", (chat_ids[1],))
|
||||
.await?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests syncing of chat visibility on a self-chat. This way we test:
|
||||
/// - Self-chat synchronisation.
|
||||
/// - That sync messages don't unarchive the self-chat.
|
||||
|
||||
+5
-14
@@ -18,7 +18,7 @@ use crate::log::LogExt;
|
||||
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
use crate::sync::{self, Sync::*, SyncData};
|
||||
use crate::tools::{get_abs_path, improve_single_line_input, EmailAddress};
|
||||
use crate::tools::{get_abs_path, improve_single_line_input};
|
||||
|
||||
/// The available configuration keys.
|
||||
#[derive(
|
||||
@@ -343,6 +343,10 @@ pub enum Config {
|
||||
/// until `chat_id.accept()` is called.
|
||||
#[strum(props(default = "0"))]
|
||||
VerifiedOneOnOneChats,
|
||||
|
||||
/// Row ID of the key in the `keypairs` table
|
||||
/// used for signatures, encryption to self and included in `Autocrypt` header.
|
||||
KeyId,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -627,8 +631,6 @@ impl Context {
|
||||
///
|
||||
/// This should only be used by test code and during configure.
|
||||
pub(crate) async fn set_primary_self_addr(&self, primary_new: &str) -> Result<()> {
|
||||
let old_addr = self.get_config(Config::ConfiguredAddr).await?;
|
||||
|
||||
// add old primary address (if exists) to secondary addresses
|
||||
let mut secondary_addrs = self.get_all_self_addrs().await?;
|
||||
// never store a primary address also as a secondary
|
||||
@@ -642,17 +644,6 @@ impl Context {
|
||||
self.set_config(Config::ConfiguredAddr, Some(primary_new))
|
||||
.await?;
|
||||
|
||||
if let Some(old_addr) = old_addr {
|
||||
let old_addr = EmailAddress::new(&old_addr)?;
|
||||
let old_keypair = crate::key::load_keypair(self, &old_addr).await?;
|
||||
|
||||
if let Some(mut old_keypair) = old_keypair {
|
||||
old_keypair.addr = EmailAddress::new(primary_new)?;
|
||||
crate::key::store_self_keypair(self, &old_keypair, crate::key::KeyPairUse::Default)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1318,6 +1318,7 @@ mod tests {
|
||||
"socks5_port",
|
||||
"socks5_user",
|
||||
"socks5_password",
|
||||
"key_id",
|
||||
];
|
||||
let t = TestContext::new().await;
|
||||
let info = t.get_info().await.unwrap();
|
||||
|
||||
+23
-32
@@ -169,11 +169,10 @@ pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chat;
|
||||
use crate::key::DcKey;
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::param::Param;
|
||||
use crate::test_utils::{bob_keypair, TestContext};
|
||||
use crate::test_utils::{bob_keypair, TestContext, TestContextManager};
|
||||
|
||||
mod ensure_secret_key_exists {
|
||||
use super::*;
|
||||
@@ -217,37 +216,35 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_encrypted_no_autocrypt() -> anyhow::Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let chat_alice = alice.create_chat(&bob).await.id;
|
||||
let chat_bob = bob.create_chat(&alice).await.id;
|
||||
|
||||
// Alice sends unencrypted message to Bob
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
chat::prepare_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
chat::send_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let sent = alice.send_msg(chat_alice, &mut msg).await;
|
||||
|
||||
// Bob receives unencrypted message from Alice
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(!msg.was_encrypted());
|
||||
let msg = bob.recv_msg(&sent).await;
|
||||
assert!(!msg.get_showpadlock());
|
||||
|
||||
// Parsing a message is enough to update peerstate
|
||||
let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
|
||||
.await?
|
||||
.expect("no peerstate found in the database");
|
||||
assert_eq!(peerstate_alice.prefer_encrypt, EncryptPreference::Mutual);
|
||||
|
||||
// Bob sends encrypted message to Alice
|
||||
// Bob sends empty encrypted message to Alice
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
chat::prepare_msg(&bob.ctx, chat_bob, &mut msg).await?;
|
||||
chat::send_msg(&bob.ctx, chat_bob, &mut msg).await?;
|
||||
let sent = bob.pop_sent_msg().await;
|
||||
let sent = bob.send_msg(chat_bob, &mut msg).await;
|
||||
|
||||
// Alice receives encrypted message from Bob
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
// Alice receives an empty encrypted message from Bob.
|
||||
// This is also a regression test for previously existing bug
|
||||
// that resulted in no padlock on encrypted empty messages.
|
||||
let msg = alice.recv_msg(&sent).await;
|
||||
assert!(msg.get_showpadlock());
|
||||
|
||||
let peerstate_bob = Peerstate::from_addr(&alice.ctx, "bob@example.net")
|
||||
.await?
|
||||
@@ -259,12 +256,10 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
// Alice sends encrypted message without Autocrypt header.
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.param.set_int(Param::SkipAutocrypt, 1);
|
||||
chat::prepare_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
chat::send_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let sent = alice.send_msg(chat_alice, &mut msg).await;
|
||||
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(msg.was_encrypted());
|
||||
let msg = bob.recv_msg(&sent).await;
|
||||
assert!(msg.get_showpadlock());
|
||||
let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
|
||||
.await?
|
||||
.expect("no peerstate found in the database");
|
||||
@@ -273,12 +268,10 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
// Alice sends plaintext message with Autocrypt header.
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.force_plaintext();
|
||||
chat::prepare_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
chat::send_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let sent = alice.send_msg(chat_alice, &mut msg).await;
|
||||
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(!msg.was_encrypted());
|
||||
let msg = bob.recv_msg(&sent).await;
|
||||
assert!(!msg.get_showpadlock());
|
||||
let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
|
||||
.await?
|
||||
.expect("no peerstate found in the database");
|
||||
@@ -288,12 +281,10 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.force_plaintext();
|
||||
msg.param.set_int(Param::SkipAutocrypt, 1);
|
||||
chat::prepare_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
chat::send_msg(&alice.ctx, chat_alice, &mut msg).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let sent = alice.send_msg(chat_alice, &mut msg).await;
|
||||
|
||||
let msg = bob.parse_msg(&sent).await;
|
||||
assert!(!msg.was_encrypted());
|
||||
let msg = bob.recv_msg(&sent).await;
|
||||
assert!(!msg.get_showpadlock());
|
||||
let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
|
||||
.await?
|
||||
.expect("no peerstate found in the database");
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::chat::ChatId;
|
||||
use crate::contact::ContactId;
|
||||
use crate::ephemeral::Timer as EphemeralTimer;
|
||||
use crate::message::MsgId;
|
||||
use crate::scheduler::connectivity::DetailedConnectivity;
|
||||
use crate::webxdc::StatusUpdateSerial;
|
||||
|
||||
/// Event payload.
|
||||
@@ -258,7 +259,9 @@ pub enum EventType {
|
||||
/// This means that you should refresh the connectivity view
|
||||
/// and possibly the connectivtiy HTML; see dc_get_connectivity() and
|
||||
/// dc_get_connectivity_html() for details.
|
||||
ConnectivityChanged,
|
||||
ConnectivityChanged {
|
||||
connectivity: Option<DetailedConnectivity>,
|
||||
},
|
||||
|
||||
/// The user's avatar changed.
|
||||
SelfavatarChanged,
|
||||
|
||||
+7
-1
@@ -12,7 +12,13 @@ use crate::context::Context;
|
||||
use crate::imap::{client::IMAP_TIMEOUT, FolderMeaning};
|
||||
use crate::log::LogExt;
|
||||
|
||||
const IDLE_TIMEOUT: Duration = Duration::from_secs(23 * 60);
|
||||
/// Timeout after which IDLE is finished
|
||||
/// if there are no responses from the server.
|
||||
///
|
||||
/// If `* OK Still here` keepalives are sent more frequently
|
||||
/// than this duration, timeout should never be triggered.
|
||||
/// For example, Dovecot sends keepalives every 2 minutes by default.
|
||||
const IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
impl Session {
|
||||
pub async fn idle(
|
||||
|
||||
+1
-1
@@ -670,7 +670,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> {
|
||||
let keys = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||
"SELECT id, public_key, private_key, id=(SELECT value FROM config WHERE keyname='key_id') FROM keypairs;",
|
||||
(),
|
||||
|row| {
|
||||
let id = row.get(0)?;
|
||||
|
||||
+36
-40
@@ -18,7 +18,7 @@ use crate::constants::KeyGenType;
|
||||
use crate::context::Context;
|
||||
use crate::log::LogExt;
|
||||
use crate::pgp::KeyPair;
|
||||
use crate::tools::{time, EmailAddress};
|
||||
use crate::tools::EmailAddress;
|
||||
|
||||
/// Convenience trait for working with keys.
|
||||
///
|
||||
@@ -82,10 +82,9 @@ pub(crate) async fn load_self_public_key(context: &Context) -> Result<SignedPubl
|
||||
match context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
r#"SELECT public_key
|
||||
FROM keypairs
|
||||
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
||||
AND is_default=1"#,
|
||||
"SELECT public_key
|
||||
FROM keypairs
|
||||
WHERE id=(SELECT value FROM config WHERE keyname='key_id')",
|
||||
(),
|
||||
|row| {
|
||||
let bytes: Vec<u8> = row.get(0)?;
|
||||
@@ -106,10 +105,9 @@ pub(crate) async fn load_self_secret_key(context: &Context) -> Result<SignedSecr
|
||||
match context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
r#"SELECT private_key
|
||||
FROM keypairs
|
||||
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
||||
AND is_default=1"#,
|
||||
"SELECT private_key
|
||||
FROM keypairs
|
||||
WHERE id=(SELECT value FROM config WHERE keyname='key_id')",
|
||||
(),
|
||||
|row| {
|
||||
let bytes: Vec<u8> = row.get(0)?;
|
||||
@@ -132,8 +130,7 @@ pub(crate) async fn load_self_secret_keyring(context: &Context) -> Result<Vec<Si
|
||||
.query_map(
|
||||
r#"SELECT private_key
|
||||
FROM keypairs
|
||||
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
||||
ORDER BY is_default DESC"#,
|
||||
ORDER BY id=(SELECT value FROM config WHERE keyname='key_id') DESC"#,
|
||||
(),
|
||||
|row| row.get::<_, Vec<u8>>(0),
|
||||
|keys| keys.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
@@ -233,13 +230,10 @@ pub(crate) async fn load_keypair(
|
||||
let res = context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
r#"
|
||||
SELECT public_key, private_key
|
||||
FROM keypairs
|
||||
WHERE addr=?1
|
||||
AND is_default=1;
|
||||
"#,
|
||||
(addr,),
|
||||
"SELECT public_key, private_key
|
||||
FROM keypairs
|
||||
WHERE id=(SELECT value FROM config WHERE keyname='key_id')",
|
||||
(),
|
||||
|row| {
|
||||
let pub_bytes: Vec<u8> = row.get(0)?;
|
||||
let sec_bytes: Vec<u8> = row.get(1)?;
|
||||
@@ -288,42 +282,44 @@ pub async fn store_self_keypair(
|
||||
keypair: &KeyPair,
|
||||
default: KeyPairUse,
|
||||
) -> Result<()> {
|
||||
context
|
||||
let mut config_cache_lock = context.sql.config_cache.write().await;
|
||||
let new_key_id = context
|
||||
.sql
|
||||
.transaction(|transaction| {
|
||||
let public_key = DcKey::to_bytes(&keypair.public);
|
||||
let secret_key = DcKey::to_bytes(&keypair.secret);
|
||||
transaction
|
||||
.execute(
|
||||
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
|
||||
(&public_key, &secret_key),
|
||||
)
|
||||
.context("failed to remove old use of key")?;
|
||||
if default == KeyPairUse::Default {
|
||||
transaction
|
||||
.execute("UPDATE keypairs SET is_default=0;", ())
|
||||
.context("failed to clear default")?;
|
||||
}
|
||||
|
||||
let is_default = match default {
|
||||
KeyPairUse::Default => i32::from(true),
|
||||
KeyPairUse::ReadOnly => i32::from(false),
|
||||
KeyPairUse::Default => true,
|
||||
KeyPairUse::ReadOnly => false,
|
||||
};
|
||||
|
||||
let addr = keypair.addr.to_string();
|
||||
let t = time();
|
||||
|
||||
transaction
|
||||
.execute(
|
||||
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created)
|
||||
VALUES (?,?,?,?,?);",
|
||||
(addr, is_default, &public_key, &secret_key, t),
|
||||
"INSERT OR REPLACE INTO keypairs (public_key, private_key)
|
||||
VALUES (?,?)",
|
||||
(&public_key, &secret_key),
|
||||
)
|
||||
.context("failed to insert keypair")?;
|
||||
.context("Failed to insert keypair")?;
|
||||
|
||||
Ok(())
|
||||
if is_default {
|
||||
let new_key_id = transaction.last_insert_rowid();
|
||||
transaction.execute(
|
||||
"INSERT OR REPLACE INTO config (keyname, value) VALUES ('key_id', ?)",
|
||||
(new_key_id,),
|
||||
)?;
|
||||
Ok(Some(new_key_id))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
if let Some(new_key_id) = new_key_id {
|
||||
// Update config cache if transaction succeeded and changed current default key.
|
||||
config_cache_lock.insert("key_id".to_string(), Some(new_key_id.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1842,6 +1842,24 @@ pub(crate) async fn rfc724_mid_exists_and(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Given a list of Message-IDs, returns the latest message found in the database.
|
||||
///
|
||||
/// Only messages that are not in the trash chat are considered.
|
||||
pub(crate) async fn get_latest_by_rfc724_mids(
|
||||
context: &Context,
|
||||
mids: &[String],
|
||||
) -> Result<Option<Message>> {
|
||||
for id in mids.iter().rev() {
|
||||
if let Some(msg_id) = rfc724_mid_exists(context, id).await? {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
if msg.chat_id != DC_CHAT_ID_TRASH {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// How a message is primarily displayed.
|
||||
#[derive(
|
||||
Debug,
|
||||
|
||||
+16
-4
@@ -1,5 +1,6 @@
|
||||
//! # MIME message production.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
@@ -517,6 +518,7 @@ impl<'a> MimeFactory<'a> {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc5322#appendix-A.1.1>.
|
||||
let from_header = Header::new_with_value("From".into(), vec![from]).unwrap();
|
||||
headers.unprotected.push(from_header.clone());
|
||||
headers.protected.push(from_header);
|
||||
|
||||
if let Some(sender_displayname) = &self.sender_displayname {
|
||||
let sender =
|
||||
@@ -704,8 +706,6 @@ impl<'a> MimeFactory<'a> {
|
||||
)
|
||||
};
|
||||
let outer_message = if is_encrypted {
|
||||
headers.protected.push(from_header);
|
||||
|
||||
// Store protected headers in the inner message.
|
||||
let message = headers
|
||||
.protected
|
||||
@@ -800,12 +800,18 @@ impl<'a> MimeFactory<'a> {
|
||||
// Store protected headers in the outer message.
|
||||
let message = headers
|
||||
.protected
|
||||
.into_iter()
|
||||
.fold(message, |message, header| message.header(header));
|
||||
.iter()
|
||||
.fold(message, |message, header| message.header(header.clone()));
|
||||
|
||||
if self.should_skip_autocrypt()
|
||||
|| !context.get_config_bool(Config::SignUnencrypted).await?
|
||||
{
|
||||
let protected: HashSet<Header> = HashSet::from_iter(headers.protected.into_iter());
|
||||
for h in headers.unprotected.split_off(0) {
|
||||
if !protected.contains(&h) {
|
||||
headers.unprotected.push(h);
|
||||
}
|
||||
}
|
||||
message
|
||||
} else {
|
||||
let message = message.header(get_content_type_directives_header());
|
||||
@@ -2216,6 +2222,7 @@ mod tests {
|
||||
|
||||
let part = payload.next().unwrap();
|
||||
assert_eq!(part.match_indices("multipart/signed").count(), 1);
|
||||
assert_eq!(part.match_indices("From:").count(), 1);
|
||||
assert_eq!(part.match_indices("Subject:").count(), 0);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 1);
|
||||
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
|
||||
@@ -2226,12 +2233,14 @@ mod tests {
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(part.match_indices("From:").count(), 1);
|
||||
assert_eq!(part.match_indices("Subject:").count(), 1);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 0);
|
||||
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
|
||||
|
||||
let part = payload.next().unwrap();
|
||||
assert_eq!(part.match_indices("text/plain").count(), 1);
|
||||
assert_eq!(part.match_indices("From:").count(), 0);
|
||||
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 1);
|
||||
assert_eq!(part.match_indices("Subject:").count(), 0);
|
||||
|
||||
@@ -2258,12 +2267,14 @@ mod tests {
|
||||
|
||||
let part = payload.next().unwrap();
|
||||
assert_eq!(part.match_indices("multipart/signed").count(), 1);
|
||||
assert_eq!(part.match_indices("From:").count(), 1);
|
||||
assert_eq!(part.match_indices("Subject:").count(), 0);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 1);
|
||||
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
|
||||
|
||||
let part = payload.next().unwrap();
|
||||
assert_eq!(part.match_indices("text/plain").count(), 1);
|
||||
assert_eq!(part.match_indices("From:").count(), 1);
|
||||
assert_eq!(part.match_indices("Subject:").count(), 1);
|
||||
assert_eq!(part.match_indices("Autocrypt:").count(), 0);
|
||||
assert_eq!(part.match_indices("multipart/mixed").count(), 0);
|
||||
@@ -2272,6 +2283,7 @@ mod tests {
|
||||
let body = payload.next().unwrap();
|
||||
assert_eq!(body.match_indices("this is the text!").count(), 1);
|
||||
assert_eq!(body.match_indices("text/plain").count(), 0);
|
||||
assert_eq!(body.match_indices("From:").count(), 0);
|
||||
assert_eq!(body.match_indices("Chat-User-Avatar:").count(), 0);
|
||||
assert_eq!(body.match_indices("Subject:").count(), 0);
|
||||
|
||||
|
||||
+20
-25
@@ -1,5 +1,6 @@
|
||||
//! # MIME message parsing module.
|
||||
|
||||
use std::cmp::min;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
@@ -37,8 +38,8 @@ use crate::simplify::{simplify, SimplifiedText};
|
||||
use crate::stock_str;
|
||||
use crate::sync::SyncItems;
|
||||
use crate::tools::{
|
||||
create_smeared_timestamp, get_filemeta, parse_receive_headers, strip_rtlo_characters,
|
||||
truncate_by_lines,
|
||||
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time,
|
||||
strip_rtlo_characters, truncate_by_lines,
|
||||
};
|
||||
use crate::{location, tools};
|
||||
|
||||
@@ -118,6 +119,12 @@ pub(crate) struct MimeMessage {
|
||||
|
||||
/// Whether the contact sending this should be marked as bot.
|
||||
pub(crate) is_bot: bool,
|
||||
|
||||
/// When the message was received, in secs since epoch.
|
||||
pub(crate) timestamp_rcvd: i64,
|
||||
/// Sender timestamp in secs since epoch. Allowed to be in the future due to unsynchronized
|
||||
/// clocks, but not too much.
|
||||
pub(crate) timestamp_sent: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -386,6 +393,12 @@ impl MimeMessage {
|
||||
// Auto-submitted is also set by holiday-notices so we also check `chat-version`
|
||||
let is_bot = headers.contains_key("auto-submitted") && headers.contains_key("chat-version");
|
||||
|
||||
let timestamp_rcvd = smeared_time(context);
|
||||
let timestamp_sent = headers
|
||||
.get(HeaderDef::Date.get_headername())
|
||||
.and_then(|value| mailparse::dateparse(value).ok())
|
||||
.map_or(timestamp_rcvd, |value| min(value, timestamp_rcvd + 60));
|
||||
|
||||
let mut parser = MimeMessage {
|
||||
parts: Vec::new(),
|
||||
headers,
|
||||
@@ -415,6 +428,8 @@ impl MimeMessage {
|
||||
decoded_data: Vec::new(),
|
||||
hop_info,
|
||||
is_bot,
|
||||
timestamp_rcvd,
|
||||
timestamp_sent,
|
||||
};
|
||||
|
||||
match partial {
|
||||
@@ -461,20 +476,6 @@ impl MimeMessage {
|
||||
parser.decoded_data = mail_raw;
|
||||
}
|
||||
|
||||
crate::peerstate::maybe_do_aeap_transition(context, &mut parser).await?;
|
||||
if let Some(peerstate) = &parser.decryption_info.peerstate {
|
||||
peerstate
|
||||
.handle_fingerprint_change(context, message_time)
|
||||
.await?;
|
||||
// When peerstate is set to Mutual, it's saved immediately to not lose that fact in case
|
||||
// of an error. Otherwise we don't save peerstate until get here to reduce the number of
|
||||
// calls to save_to_db() and not to degrade encryption if a mail wasn't parsed
|
||||
// successfully.
|
||||
if peerstate.prefer_encrypt != EncryptPreference::Mutual {
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parser)
|
||||
}
|
||||
|
||||
@@ -694,7 +695,7 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
self.parts.push(part);
|
||||
self.do_add_single_part(part);
|
||||
}
|
||||
|
||||
if self.headers.contains_key("auto-submitted") {
|
||||
@@ -1644,13 +1645,7 @@ impl MimeMessage {
|
||||
/// Handle reports
|
||||
/// (MDNs = Message Disposition Notification, the message was read
|
||||
/// and NDNs = Non delivery notification, the message could not be delivered)
|
||||
pub async fn handle_reports(
|
||||
&self,
|
||||
context: &Context,
|
||||
from_id: ContactId,
|
||||
sent_timestamp: i64,
|
||||
parts: &[Part],
|
||||
) {
|
||||
pub async fn handle_reports(&self, context: &Context, from_id: ContactId, parts: &[Part]) {
|
||||
for report in &self.mdn_reports {
|
||||
for original_message_id in report
|
||||
.original_message_id
|
||||
@@ -1658,7 +1653,7 @@ impl MimeMessage {
|
||||
.chain(&report.additional_message_ids)
|
||||
{
|
||||
if let Err(err) =
|
||||
handle_mdn(context, from_id, original_message_id, sent_timestamp).await
|
||||
handle_mdn(context, from_id, original_message_id, self.timestamp_sent).await
|
||||
{
|
||||
warn!(context, "Could not handle MDN: {err:#}.");
|
||||
}
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ impl Context {
|
||||
modified: time(),
|
||||
});
|
||||
|
||||
self.emit_event(EventType::ConnectivityChanged);
|
||||
self.emit_event(EventType::ConnectivityChanged { connectivity: None });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+86
-136
@@ -1,6 +1,5 @@
|
||||
//! Internet Message Format reception pipeline.
|
||||
|
||||
use std::cmp::min;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
@@ -10,6 +9,7 @@ use num_traits::FromPrimitive;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{Blocked, Chattype, ShowEmails, DC_CHAT_ID_TRASH};
|
||||
@@ -38,9 +38,7 @@ use crate::simplify;
|
||||
use crate::sql;
|
||||
use crate::stock_str;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::tools::{
|
||||
buf_compress, extract_grpid_from_rfc724_mid, smeared_time, strip_rtlo_characters,
|
||||
};
|
||||
use crate::tools::{buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters};
|
||||
use crate::{contact, imap};
|
||||
|
||||
/// This is the struct that is returned after receiving one email (aka MIME message).
|
||||
@@ -171,6 +169,20 @@ pub(crate) async fn receive_imf_inner(
|
||||
Ok(mime_parser) => mime_parser,
|
||||
};
|
||||
|
||||
crate::peerstate::maybe_do_aeap_transition(context, &mut mime_parser).await?;
|
||||
if let Some(peerstate) = &mime_parser.decryption_info.peerstate {
|
||||
peerstate
|
||||
.handle_fingerprint_change(context, mime_parser.timestamp_sent)
|
||||
.await?;
|
||||
// When peerstate is set to Mutual, it's saved immediately to not lose that fact in case
|
||||
// of an error. Otherwise we don't save peerstate until get here to reduce the number of
|
||||
// calls to save_to_db() and not to degrade encryption if a mail wasn't parsed
|
||||
// successfully.
|
||||
if peerstate.prefer_encrypt != EncryptPreference::Mutual {
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
}
|
||||
}
|
||||
|
||||
info!(context, "Receiving message {rfc724_mid:?}, seen={seen}...");
|
||||
|
||||
// check, if the mail is already in our database.
|
||||
@@ -232,15 +244,6 @@ pub(crate) async fn receive_imf_inner(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let rcvd_timestamp = smeared_time(context);
|
||||
|
||||
// Sender timestamp is allowed to be a bit in the future due to
|
||||
// unsynchronized clocks, but not too much.
|
||||
let sent_timestamp = mime_parser
|
||||
.get_header(HeaderDef::Date)
|
||||
.and_then(|value| mailparse::dateparse(value).ok())
|
||||
.map_or(rcvd_timestamp, |value| min(value, rcvd_timestamp + 60));
|
||||
|
||||
update_verified_keys(context, &mut mime_parser, from_id).await?;
|
||||
|
||||
let received_msg;
|
||||
@@ -269,7 +272,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
received_msg = Some(ReceivedMsg {
|
||||
chat_id: DC_CHAT_ID_TRASH,
|
||||
state: MessageState::InSeen,
|
||||
sort_timestamp: sent_timestamp,
|
||||
sort_timestamp: mime_parser.timestamp_sent,
|
||||
msg_ids: vec![msg_id],
|
||||
needs_delete_job: res == securejoin::HandshakeMessage::Done,
|
||||
#[cfg(test)]
|
||||
@@ -298,8 +301,6 @@ pub(crate) async fn receive_imf_inner(
|
||||
incoming,
|
||||
&to_ids,
|
||||
rfc724_mid,
|
||||
sent_timestamp,
|
||||
rcvd_timestamp,
|
||||
from_id,
|
||||
seen || replace_partial_download.is_some(),
|
||||
is_partial_download,
|
||||
@@ -313,7 +314,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
};
|
||||
|
||||
if !from_id.is_special() {
|
||||
contact::update_last_seen(context, from_id, sent_timestamp).await?;
|
||||
contact::update_last_seen(context, from_id, mime_parser.timestamp_sent).await?;
|
||||
}
|
||||
|
||||
// Update gossiped timestamp for the chat if someone else or our other device sent
|
||||
@@ -330,9 +331,9 @@ pub(crate) async fn receive_imf_inner(
|
||||
context,
|
||||
"Received message contains Autocrypt-Gossip for all members of {chat_id}, updating timestamp."
|
||||
);
|
||||
if chat_id.get_gossiped_timestamp(context).await? < sent_timestamp {
|
||||
if chat_id.get_gossiped_timestamp(context).await? < mime_parser.timestamp_sent {
|
||||
chat_id
|
||||
.set_gossiped_timestamp(context, sent_timestamp)
|
||||
.set_gossiped_timestamp(context, mime_parser.timestamp_sent)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
@@ -369,7 +370,11 @@ pub(crate) async fn receive_imf_inner(
|
||||
if let Some(avatar_action) = &mime_parser.user_avatar {
|
||||
if from_id != ContactId::UNDEFINED
|
||||
&& context
|
||||
.update_contacts_timestamp(from_id, Param::AvatarTimestamp, sent_timestamp)
|
||||
.update_contacts_timestamp(
|
||||
from_id,
|
||||
Param::AvatarTimestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
if let Err(err) = contact::set_profile_image(
|
||||
@@ -390,7 +395,11 @@ pub(crate) async fn receive_imf_inner(
|
||||
if !mime_parser.is_mailinglist_message()
|
||||
&& from_id != ContactId::UNDEFINED
|
||||
&& context
|
||||
.update_contacts_timestamp(from_id, Param::StatusTimestamp, sent_timestamp)
|
||||
.update_contacts_timestamp(
|
||||
from_id,
|
||||
Param::StatusTimestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
if let Err(err) = contact::set_status(
|
||||
@@ -439,7 +448,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
context.new_msgs_notify.notify_one();
|
||||
|
||||
mime_parser
|
||||
.handle_reports(context, from_id, sent_timestamp, &mime_parser.parts)
|
||||
.handle_reports(context, from_id, &mime_parser.parts)
|
||||
.await;
|
||||
|
||||
from_id.mark_bot(context, mime_parser.is_bot).await?;
|
||||
@@ -510,8 +519,6 @@ async fn add_parts(
|
||||
incoming: bool,
|
||||
to_ids: &[ContactId],
|
||||
rfc724_mid: &str,
|
||||
sent_timestamp: i64,
|
||||
rcvd_timestamp: i64,
|
||||
from_id: ContactId,
|
||||
seen: bool,
|
||||
is_partial_download: Option<u32>,
|
||||
@@ -650,7 +657,6 @@ async fn add_parts(
|
||||
from_id,
|
||||
to_ids,
|
||||
&verified_encryption,
|
||||
sent_timestamp,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -696,7 +702,6 @@ async fn add_parts(
|
||||
group_changes_msgs = apply_group_changes(
|
||||
context,
|
||||
mime_parser,
|
||||
sent_timestamp,
|
||||
group_chat_id,
|
||||
from_id,
|
||||
to_ids,
|
||||
@@ -714,7 +719,6 @@ async fn add_parts(
|
||||
allow_creation,
|
||||
mailinglist_header,
|
||||
mime_parser,
|
||||
sent_timestamp,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -725,7 +729,7 @@ async fn add_parts(
|
||||
}
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
apply_mailinglist_changes(context, mime_parser, sent_timestamp, chat_id).await?;
|
||||
apply_mailinglist_changes(context, mime_parser, chat_id).await?;
|
||||
}
|
||||
|
||||
// if contact renaming is prevented (for mailinglists and bots),
|
||||
@@ -811,11 +815,13 @@ async fn add_parts(
|
||||
// The message itself will be sorted under the device message since the device
|
||||
// message is `MessageState::InNoticed`, which means that all following
|
||||
// messages are sorted under it.
|
||||
let sort_timestamp =
|
||||
calc_sort_timestamp(context, sent_timestamp, chat_id, true, incoming)
|
||||
.await?;
|
||||
chat_id
|
||||
.set_protection(context, new_protection, sort_timestamp, Some(from_id))
|
||||
.set_protection(
|
||||
context,
|
||||
new_protection,
|
||||
mime_parser.timestamp_sent,
|
||||
Some(from_id),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
@@ -883,7 +889,6 @@ async fn add_parts(
|
||||
from_id,
|
||||
to_ids,
|
||||
&verified_encryption,
|
||||
sent_timestamp,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -921,7 +926,6 @@ async fn add_parts(
|
||||
group_changes_msgs = apply_group_changes(
|
||||
context,
|
||||
mime_parser,
|
||||
sent_timestamp,
|
||||
chat_id,
|
||||
from_id,
|
||||
to_ids,
|
||||
@@ -1010,8 +1014,15 @@ async fn add_parts(
|
||||
};
|
||||
|
||||
let in_fresh = state == MessageState::InFresh;
|
||||
let sort_timestamp =
|
||||
calc_sort_timestamp(context, sent_timestamp, chat_id, false, incoming).await?;
|
||||
let sort_to_bottom = false;
|
||||
let sort_timestamp = chat_id
|
||||
.calc_sort_timestamp(
|
||||
context,
|
||||
mime_parser.timestamp_sent,
|
||||
sort_to_bottom,
|
||||
incoming,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Apply ephemeral timer changes to the chat.
|
||||
//
|
||||
@@ -1041,7 +1052,11 @@ async fn add_parts(
|
||||
"Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
|
||||
);
|
||||
} else if chat_id
|
||||
.update_timestamp(context, Param::EphemeralSettingsTimestamp, sent_timestamp)
|
||||
.update_timestamp(
|
||||
context,
|
||||
Param::EphemeralSettingsTimestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
if let Err(err) = chat_id
|
||||
@@ -1244,7 +1259,7 @@ async fn add_parts(
|
||||
match ephemeral_timer {
|
||||
EphemeralTimer::Disabled => 0,
|
||||
EphemeralTimer::Enabled { duration } => {
|
||||
rcvd_timestamp.saturating_add(duration.into())
|
||||
mime_parser.timestamp_rcvd.saturating_add(duration.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1297,8 +1312,8 @@ RETURNING id
|
||||
if trash { ContactId::UNDEFINED } else { from_id },
|
||||
if trash { ContactId::UNDEFINED } else { to_id },
|
||||
sort_timestamp,
|
||||
sent_timestamp,
|
||||
rcvd_timestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
mime_parser.timestamp_rcvd,
|
||||
typ,
|
||||
state,
|
||||
is_dc_message,
|
||||
@@ -1470,53 +1485,6 @@ async fn save_locations(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn calc_sort_timestamp(
|
||||
context: &Context,
|
||||
message_timestamp: i64,
|
||||
chat_id: ChatId,
|
||||
always_sort_to_bottom: bool,
|
||||
incoming: bool,
|
||||
) -> Result<i64> {
|
||||
let mut sort_timestamp = min(message_timestamp, smeared_time(context));
|
||||
|
||||
let last_msg_time: Option<i64> = if always_sort_to_bottom {
|
||||
// get newest message for this chat
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=?",
|
||||
(chat_id,),
|
||||
)
|
||||
.await?
|
||||
} else if incoming {
|
||||
// get newest non fresh message for this chat.
|
||||
|
||||
// If a user hasn't been online for some time, the Inbox is fetched first and then the
|
||||
// Sentbox. In order for Inbox and Sent messages to be allowed to mingle, outgoing messages
|
||||
// are purely sorted by their sent timestamp. NB: The Inbox must be fetched first otherwise
|
||||
// Inbox messages would be always below old Sentbox messages. We could take in the query
|
||||
// below only incoming messages, but then new incoming messages would mingle with just sent
|
||||
// outgoing ones and apear somewhere in the middle of the chat.
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?",
|
||||
(chat_id, MessageState::InFresh),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(last_msg_time) = last_msg_time {
|
||||
if last_msg_time > sort_timestamp {
|
||||
sort_timestamp = last_msg_time;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(sort_timestamp)
|
||||
}
|
||||
|
||||
async fn lookup_chat_by_reply(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
@@ -1529,27 +1497,10 @@ async fn lookup_chat_by_reply(
|
||||
let Some(parent) = parent else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let parent_chat = Chat::load_from_db(context, parent.chat_id).await?;
|
||||
|
||||
if parent.download_state != DownloadState::Done
|
||||
// TODO (2023-09-12): Added for backward compatibility with versions that did not have
|
||||
// `DownloadState::Undecipherable`. Remove eventually with the comment in
|
||||
// `MimeMessage::from_bytes()`.
|
||||
|| parent
|
||||
.error
|
||||
.as_ref()
|
||||
.filter(|e| e.starts_with("Decrypting failed:"))
|
||||
.is_some()
|
||||
{
|
||||
// If the parent msg is not fully downloaded or undecipherable, it may have been
|
||||
// assigned to the wrong chat (they often get assigned to the 1:1 chat with the sender).
|
||||
let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if parent_chat.id == DC_CHAT_ID_TRASH {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let parent_chat = Chat::load_from_db(context, parent_chat_id).await?;
|
||||
|
||||
// If this was a private message just to self, it was probably a private reply.
|
||||
// It should not go into the group then, but into the private chat.
|
||||
@@ -1622,7 +1573,6 @@ async fn create_or_lookup_group(
|
||||
from_id: ContactId,
|
||||
to_ids: &[ContactId],
|
||||
verified_encryption: &VerifiedEncryption,
|
||||
timestamp: i64,
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
let grpid = if let Some(grpid) = try_getting_grpid(mime_parser) {
|
||||
grpid
|
||||
@@ -1635,7 +1585,7 @@ async fn create_or_lookup_group(
|
||||
member_ids.push(ContactId::SELF);
|
||||
}
|
||||
|
||||
let res = create_adhoc_group(context, mime_parser, create_blocked, &member_ids, timestamp)
|
||||
let res = create_adhoc_group(context, mime_parser, create_blocked, &member_ids)
|
||||
.await
|
||||
.context("could not create ad hoc group")?
|
||||
.map(|chat_id| (chat_id, create_blocked));
|
||||
@@ -1717,7 +1667,7 @@ async fn create_or_lookup_group(
|
||||
create_blocked,
|
||||
create_protected,
|
||||
None,
|
||||
timestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("Failed to create group '{grpname}' for grpid={grpid}"))?;
|
||||
@@ -1764,7 +1714,6 @@ async fn create_or_lookup_group(
|
||||
async fn apply_group_changes(
|
||||
context: &Context,
|
||||
mime_parser: &mut MimeMessage,
|
||||
sent_timestamp: i64,
|
||||
chat_id: ChatId,
|
||||
from_id: ContactId,
|
||||
to_ids: &[ContactId],
|
||||
@@ -1802,7 +1751,11 @@ async fn apply_group_changes(
|
||||
let allow_member_list_changes = !is_partial_download
|
||||
&& is_from_in_chat
|
||||
&& chat_id
|
||||
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
|
||||
.update_timestamp(
|
||||
context,
|
||||
Param::MemberListTimestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Whether to rebuild the member list from scratch.
|
||||
@@ -1841,7 +1794,7 @@ async fn apply_group_changes(
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
smeared_time(context),
|
||||
mime_parser.timestamp_sent,
|
||||
Some(from_id),
|
||||
)
|
||||
.await?;
|
||||
@@ -1895,7 +1848,11 @@ async fn apply_group_changes(
|
||||
.filter(|grpname| grpname.len() < 200)
|
||||
{
|
||||
if chat_id
|
||||
.update_timestamp(context, Param::GroupNameTimestamp, sent_timestamp)
|
||||
.update_timestamp(
|
||||
context,
|
||||
Param::GroupNameTimestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
info!(context, "Updating grpname for chat {chat_id}.");
|
||||
@@ -2002,7 +1959,7 @@ async fn apply_group_changes(
|
||||
info!(context, "Group-avatar change for {chat_id}.");
|
||||
if chat
|
||||
.param
|
||||
.update_timestamp(Param::AvatarTimestamp, sent_timestamp)?
|
||||
.update_timestamp(Param::AvatarTimestamp, mime_parser.timestamp_sent)?
|
||||
{
|
||||
match avatar_action {
|
||||
AvatarAction::Change(profile_image) => {
|
||||
@@ -2051,7 +2008,6 @@ async fn create_or_lookup_mailinglist(
|
||||
allow_creation: bool,
|
||||
list_id_header: &str,
|
||||
mime_parser: &MimeMessage,
|
||||
timestamp: i64,
|
||||
) -> Result<Option<(ChatId, Blocked)>> {
|
||||
let listid = mailinglist_header_listid(list_id_header)?;
|
||||
|
||||
@@ -2083,7 +2039,7 @@ async fn create_or_lookup_mailinglist(
|
||||
blocked,
|
||||
ProtectionStatus::Unprotected,
|
||||
param,
|
||||
timestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
@@ -2171,7 +2127,6 @@ fn compute_mailinglist_name(
|
||||
async fn apply_mailinglist_changes(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
sent_timestamp: i64,
|
||||
chat_id: ChatId,
|
||||
) -> Result<()> {
|
||||
let Some(mailinglist_header) = mime_parser.get_mailinglist_header() else {
|
||||
@@ -2187,7 +2142,11 @@ async fn apply_mailinglist_changes(
|
||||
let new_name = compute_mailinglist_name(mailinglist_header, listid, mime_parser);
|
||||
if chat.name != new_name
|
||||
&& chat_id
|
||||
.update_timestamp(context, Param::GroupNameTimestamp, sent_timestamp)
|
||||
.update_timestamp(
|
||||
context,
|
||||
Param::GroupNameTimestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
info!(context, "Updating listname for chat {chat_id}.");
|
||||
@@ -2268,7 +2227,6 @@ async fn create_adhoc_group(
|
||||
mime_parser: &MimeMessage,
|
||||
create_blocked: Blocked,
|
||||
member_ids: &[ContactId],
|
||||
timestamp: i64,
|
||||
) -> Result<Option<ChatId>> {
|
||||
if mime_parser.is_mailinglist_message() {
|
||||
return Ok(None);
|
||||
@@ -2307,7 +2265,7 @@ async fn create_adhoc_group(
|
||||
create_blocked,
|
||||
ProtectionStatus::Unprotected,
|
||||
None,
|
||||
timestamp,
|
||||
mime_parser.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -2504,7 +2462,12 @@ async fn mark_recipients_as_verified(
|
||||
Origin::Hidden,
|
||||
)
|
||||
.await?;
|
||||
ChatId::set_protection_for_contact(context, to_contact_id).await?;
|
||||
ChatId::set_protection_for_contact(
|
||||
context,
|
||||
to_contact_id,
|
||||
mimeparser.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -2543,20 +2506,7 @@ async fn get_previous_message(
|
||||
///
|
||||
/// Only messages that are not in the trash chat are considered.
|
||||
async fn get_rfc724_mid_in_list(context: &Context, mid_list: &str) -> Result<Option<Message>> {
|
||||
if mid_list.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
for id in parse_message_ids(mid_list).iter().rev() {
|
||||
if let Some(msg_id) = rfc724_mid_exists(context, id).await? {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
if msg.chat_id != DC_CHAT_ID_TRASH {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
message::get_latest_by_rfc724_mids(context, &parse_message_ids(mid_list)).await
|
||||
}
|
||||
|
||||
/// Returns the last message referenced from References: header found in the database.
|
||||
|
||||
+2
-2
@@ -590,7 +590,7 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder_meaning: Folder
|
||||
.log_err(ctx)
|
||||
.ok();
|
||||
|
||||
connection.connectivity.set_connected(ctx).await;
|
||||
connection.connectivity.set_idle(ctx).await;
|
||||
|
||||
ctx.emit_event(EventType::ImapInboxIdle);
|
||||
let Some(session) = connection.session.take() else {
|
||||
@@ -727,7 +727,7 @@ async fn smtp_loop(
|
||||
// Fake Idle
|
||||
info!(ctx, "smtp fake idle - started");
|
||||
match &connection.last_send_error {
|
||||
None => connection.connectivity.set_connected(&ctx).await,
|
||||
None => connection.connectivity.set_idle(&ctx).await,
|
||||
Some(err) => connection.connectivity.set_err(&ctx, err).await,
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::{iter::once, ops::Deref, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use humansize::{format_size, BINARY};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::events::EventType;
|
||||
@@ -14,7 +15,9 @@ use crate::{stock_str, tools};
|
||||
|
||||
use super::InnerSchedulerState;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumProperty, PartialOrd, Ord)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, EnumProperty, PartialOrd, Ord, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum Connectivity {
|
||||
NotConnected = 1000,
|
||||
Connecting = 2000,
|
||||
@@ -27,16 +30,27 @@ 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, Default, Clone, PartialEq, Eq, EnumProperty, PartialOrd)]
|
||||
enum DetailedConnectivity {
|
||||
#[derive(
|
||||
Debug, Default, Clone, PartialEq, Eq, EnumProperty, PartialOrd, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum DetailedConnectivity {
|
||||
Error(String),
|
||||
#[default]
|
||||
Uninitialized,
|
||||
Connecting,
|
||||
Working,
|
||||
InterruptingIdle,
|
||||
|
||||
/// Connection is just established, but there may be work to do.
|
||||
Connected,
|
||||
|
||||
/// There is actual work to do, e.g. there are messages in SMTP queue
|
||||
/// or we detected a message that should be downloaded.
|
||||
Working,
|
||||
|
||||
InterruptingIdle,
|
||||
|
||||
/// Connection is established and is idle.
|
||||
Idle,
|
||||
|
||||
/// The folder was configured not to be watched or configured_*_folder is not set
|
||||
NotConfigured,
|
||||
}
|
||||
@@ -48,12 +62,14 @@ impl DetailedConnectivity {
|
||||
DetailedConnectivity::Uninitialized => Some(Connectivity::NotConnected),
|
||||
DetailedConnectivity::Connecting => Some(Connectivity::Connecting),
|
||||
DetailedConnectivity::Working => Some(Connectivity::Working),
|
||||
DetailedConnectivity::InterruptingIdle => Some(Connectivity::Connected),
|
||||
DetailedConnectivity::InterruptingIdle => Some(Connectivity::Connecting),
|
||||
DetailedConnectivity::Connected => Some(Connectivity::Connected),
|
||||
|
||||
// Just don't return a connectivity, probably the folder is configured not to be
|
||||
// watched or there is e.g. no "Sent" folder, so we are not interested in it
|
||||
DetailedConnectivity::NotConfigured => None,
|
||||
|
||||
DetailedConnectivity::Idle => Some(Connectivity::Connected),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +81,8 @@ impl DetailedConnectivity {
|
||||
DetailedConnectivity::Connecting => "<span class=\"yellow dot\"></span>".to_string(),
|
||||
DetailedConnectivity::Working
|
||||
| DetailedConnectivity::InterruptingIdle
|
||||
| DetailedConnectivity::Connected => "<span class=\"green dot\"></span>".to_string(),
|
||||
| DetailedConnectivity::Connected
|
||||
| DetailedConnectivity::Idle => "<span class=\"green dot\"></span>".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +92,9 @@ impl DetailedConnectivity {
|
||||
DetailedConnectivity::Uninitialized => "Not started".to_string(),
|
||||
DetailedConnectivity::Connecting => stock_str::connecting(context).await,
|
||||
DetailedConnectivity::Working => stock_str::updating(context).await,
|
||||
DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Connected => {
|
||||
stock_str::connected(context).await
|
||||
}
|
||||
DetailedConnectivity::InterruptingIdle
|
||||
| DetailedConnectivity::Connected
|
||||
| DetailedConnectivity::Idle => stock_str::connected(context).await,
|
||||
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -94,9 +111,9 @@ impl DetailedConnectivity {
|
||||
// We don't know any more than that the last message was sent successfully;
|
||||
// since sending the last message, connectivity could have changed, which we don't notice
|
||||
// until another message is sent
|
||||
DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Connected => {
|
||||
stock_str::last_msg_sent_successfully(context).await
|
||||
}
|
||||
DetailedConnectivity::InterruptingIdle
|
||||
| DetailedConnectivity::Connected
|
||||
| DetailedConnectivity::Idle => stock_str::last_msg_sent_successfully(context).await,
|
||||
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -108,8 +125,9 @@ impl DetailedConnectivity {
|
||||
DetailedConnectivity::Connecting => false,
|
||||
DetailedConnectivity::Working => false,
|
||||
DetailedConnectivity::InterruptingIdle => false,
|
||||
DetailedConnectivity::Connected => true,
|
||||
DetailedConnectivity::Connected => false, // Just connected, there may still be work to do.
|
||||
DetailedConnectivity::NotConfigured => true,
|
||||
DetailedConnectivity::Idle => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,11 +136,13 @@ impl DetailedConnectivity {
|
||||
pub(crate) struct ConnectivityStore(Arc<Mutex<DetailedConnectivity>>);
|
||||
|
||||
impl ConnectivityStore {
|
||||
async fn set(&self, context: &Context, v: DetailedConnectivity) {
|
||||
async fn set(&self, context: &Context, detailed_connectivity: DetailedConnectivity) {
|
||||
{
|
||||
*self.0.lock().await = v;
|
||||
*self.0.lock().await = detailed_connectivity.clone();
|
||||
}
|
||||
context.emit_event(EventType::ConnectivityChanged);
|
||||
context.emit_event(EventType::ConnectivityChanged {
|
||||
connectivity: Some(detailed_connectivity),
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) async fn set_err(&self, context: &Context, e: impl ToString) {
|
||||
@@ -141,6 +161,9 @@ impl ConnectivityStore {
|
||||
pub(crate) async fn set_not_configured(&self, context: &Context) {
|
||||
self.set(context, DetailedConnectivity::NotConfigured).await;
|
||||
}
|
||||
pub(crate) async fn set_idle(&self, context: &Context) {
|
||||
self.set(context, DetailedConnectivity::Idle).await;
|
||||
}
|
||||
|
||||
async fn get_detailed(&self) -> DetailedConnectivity {
|
||||
self.0.lock().await.deref().clone()
|
||||
@@ -164,6 +187,7 @@ pub(crate) async fn idle_interrupted(inbox: ConnectivityStore, oboxes: Vec<Conne
|
||||
// return Connected until DC is completely done with fetching folders; this also
|
||||
// includes scan_folders() which happens on the inbox thread.
|
||||
if *connectivity_lock == DetailedConnectivity::Connected
|
||||
|| *connectivity_lock == DetailedConnectivity::Idle
|
||||
|| *connectivity_lock == DetailedConnectivity::NotConfigured
|
||||
{
|
||||
*connectivity_lock = DetailedConnectivity::InterruptingIdle;
|
||||
@@ -172,7 +196,9 @@ pub(crate) async fn idle_interrupted(inbox: ConnectivityStore, oboxes: Vec<Conne
|
||||
|
||||
for state in oboxes {
|
||||
let mut connectivity_lock = state.0.lock().await;
|
||||
if *connectivity_lock == DetailedConnectivity::Connected {
|
||||
if *connectivity_lock == DetailedConnectivity::Connected
|
||||
|| *connectivity_lock == DetailedConnectivity::Idle
|
||||
{
|
||||
*connectivity_lock = DetailedConnectivity::InterruptingIdle;
|
||||
}
|
||||
}
|
||||
@@ -196,7 +222,7 @@ pub(crate) async fn maybe_network_lost(context: &Context, stores: Vec<Connectivi
|
||||
}
|
||||
drop(connectivity_lock);
|
||||
}
|
||||
context.emit_event(EventType::ConnectivityChanged);
|
||||
context.emit_event(EventType::ConnectivityChanged { connectivity: None });
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectivityStore {
|
||||
|
||||
+16
-3
@@ -441,7 +441,13 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
};
|
||||
match chat::get_chat_id_by_grpid(context, field_grpid).await? {
|
||||
Some((group_chat_id, _, _)) => {
|
||||
secure_connection_established(context, contact_id, group_chat_id).await?;
|
||||
secure_connection_established(
|
||||
context,
|
||||
contact_id,
|
||||
group_chat_id,
|
||||
mime_message.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
chat::add_contact_to_chat_ex(
|
||||
context,
|
||||
Nosync,
|
||||
@@ -461,6 +467,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
mime_message.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
send_alice_handshake_msg(
|
||||
@@ -609,7 +616,12 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.save_to_db(&context.sql).await.unwrap_or_default();
|
||||
|
||||
ChatId::set_protection_for_contact(context, contact_id).await?;
|
||||
ChatId::set_protection_for_contact(
|
||||
context,
|
||||
contact_id,
|
||||
mime_message.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
} else if let Some(fingerprint) =
|
||||
mime_message.get_header(HeaderDef::SecureJoinFingerprint)
|
||||
{
|
||||
@@ -675,6 +687,7 @@ async fn secure_connection_established(
|
||||
context: &Context,
|
||||
contact_id: ContactId,
|
||||
chat_id: ChatId,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
if context
|
||||
.get_config_bool(Config::VerifiedOneOnOneChats)
|
||||
@@ -687,7 +700,7 @@ async fn secure_connection_established(
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
time(),
|
||||
timestamp,
|
||||
Some(contact_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -130,7 +130,9 @@ pub(super) async fn handle_contact_confirm(
|
||||
// Note this goes to the 1:1 chat, as when joining a group we implicitly also
|
||||
// verify both contacts (this could be a bug/security issue, see
|
||||
// e.g. https://github.com/deltachat/deltachat-core-rust/issues/1177).
|
||||
bobstate.notify_peer_verified(context).await?;
|
||||
bobstate
|
||||
.notify_peer_verified(context, message.timestamp_sent)
|
||||
.await?;
|
||||
bobstate.emit_progress(context, JoinerProgress::Succeeded);
|
||||
Ok(retval)
|
||||
}
|
||||
@@ -220,7 +222,7 @@ impl BobState {
|
||||
/// Notifies the user that the SecureJoin peer is verified.
|
||||
///
|
||||
/// This creates an info message in the chat being joined.
|
||||
async fn notify_peer_verified(&self, context: &Context) -> Result<()> {
|
||||
async fn notify_peer_verified(&self, context: &Context, timestamp: i64) -> Result<()> {
|
||||
let contact = Contact::get_by_id(context, self.invite().contact_id()).await?;
|
||||
let chat_id = self.joining_chat_id(context).await?;
|
||||
|
||||
@@ -232,7 +234,7 @@ impl BobState {
|
||||
.set_protection(
|
||||
context,
|
||||
ProtectionStatus::Protected,
|
||||
time(),
|
||||
timestamp,
|
||||
Some(contact.id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
+7
-16
@@ -572,22 +572,13 @@ impl Sql {
|
||||
pub async fn set_raw_config(&self, key: &str, value: Option<&str>) -> Result<()> {
|
||||
let mut lock = self.config_cache.write().await;
|
||||
if let Some(value) = value {
|
||||
let exists = self
|
||||
.exists("SELECT COUNT(*) FROM config WHERE keyname=?;", (key,))
|
||||
.await?;
|
||||
|
||||
if exists {
|
||||
self.execute("UPDATE config SET value=? WHERE keyname=?;", (value, key))
|
||||
.await?;
|
||||
} else {
|
||||
self.execute(
|
||||
"INSERT INTO config (keyname, value) VALUES (?, ?);",
|
||||
(key, value),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
self.execute(
|
||||
"INSERT OR REPLACE INTO config (keyname, value) VALUES (?, ?)",
|
||||
(key, value),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
self.execute("DELETE FROM config WHERE keyname=?;", (key,))
|
||||
self.execute("DELETE FROM config WHERE keyname=?", (key,))
|
||||
.await?;
|
||||
}
|
||||
lock.insert(key.to_string(), value.map(|s| s.to_string()));
|
||||
@@ -608,7 +599,7 @@ impl Sql {
|
||||
|
||||
let mut lock = self.config_cache.write().await;
|
||||
let value = self
|
||||
.query_get_value("SELECT value FROM config WHERE keyname=?;", (key,))
|
||||
.query_get_value("SELECT value FROM config WHERE keyname=?", (key,))
|
||||
.await
|
||||
.context(format!("failed to fetch raw config: {key}"))?;
|
||||
lock.insert(key.to_string(), value.clone());
|
||||
|
||||
@@ -785,6 +785,55 @@ CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
|
||||
.await?;
|
||||
}
|
||||
|
||||
if dbversion < 106 {
|
||||
// Recreate `config` table with UNIQUE constraint on `keyname`.
|
||||
sql.execute_migration(
|
||||
"CREATE TABLE new_config (
|
||||
id INTEGER PRIMARY KEY,
|
||||
keyname TEXT UNIQUE,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
INSERT OR IGNORE INTO new_config SELECT
|
||||
id, keyname, value
|
||||
FROM config;
|
||||
DROP TABLE config;
|
||||
ALTER TABLE new_config RENAME TO config;
|
||||
CREATE INDEX config_index1 ON config (keyname);",
|
||||
106,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if dbversion < 107 {
|
||||
sql.execute_migration(
|
||||
"CREATE TABLE new_keypairs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
private_key UNIQUE NOT NULL,
|
||||
public_key UNIQUE NOT NULL
|
||||
);
|
||||
INSERT OR IGNORE INTO new_keypairs SELECT id, private_key, public_key FROM keypairs;
|
||||
|
||||
INSERT OR IGNORE
|
||||
INTO config (keyname, value)
|
||||
VALUES
|
||||
('key_id', (SELECT id FROM new_keypairs
|
||||
WHERE private_key=
|
||||
(SELECT private_key FROM keypairs
|
||||
WHERE addr=(SELECT value FROM config WHERE keyname='configured_addr')
|
||||
AND is_default=1)));
|
||||
|
||||
-- We do not drop the old `keypairs` table for now,
|
||||
-- but move it to `old_keypairs`. We can remove it later
|
||||
-- in next migrations. This may be needed for recovery
|
||||
-- in case something is wrong with the migration.
|
||||
ALTER TABLE keypairs RENAME TO old_keypairs;
|
||||
ALTER TABLE new_keypairs RENAME TO keypairs;
|
||||
",
|
||||
107,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
|
||||
Reference in New Issue
Block a user