Compare commits

...
Author SHA1 Message Date
link2xt 9e699fa0a6 feat: log actual connectivity changes as ConnectivityChanged payload 2023-12-17 14:14:55 +00:00
link2xt 55cdbdc085 refactor(sql): recreate keypairs table
Removed unused `addr` and `created` field.
`is_default` boolean flag is moved into `config` row
pointing to the current default key.
2023-12-17 14:13:54 +00:00
link2xt 58620988d7 refactor(sql): recreate config table with UNIQUE constraint 2023-12-17 14:13:54 +00:00
link2xt 467f313091 chore: cargo update 2023-12-17 13:31:20 +00:00
dependabot[bot] 091578573a chore(cargo): bump zerocopy from 0.7.29 to 0.7.31
Bumps [zerocopy](https://github.com/google/zerocopy) from 0.7.29 to 0.7.31.
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.7.29...v0.7.31)

---
updated-dependencies:
- dependency-name: zerocopy
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-15 16:17:23 -03:00
iequidoo 62c1237024 refactor: Move calc_{protection_msg_,}sort_timestamp() to impl ChatId 2023-12-13 20:29:47 -03:00
iequidoo 8d41d02397 fix: calc_sort_timestamp: Skip messages that mustn't affect sorting of a new message (#5088)
Drafts mustn't affect sorting of any other messages, they aren't even displayed in the chat
window. Also hidden messages mustn't affect sorting of usual messages. But let hidden messages sort
together with protection messages because hidden messages also can be or not be verified, so let's
preserve this information -- even it's not useful currently, it can be useful in the future
versions.
2023-12-13 20:29:47 -03:00
iequidoo fce3f80654 fix: Always pass the correct sort timestamp to ChatId::set_protection() (#5088)
Before in some places it was correctly calculated by passing the "sent" timestamp to
`calc_sort_timestamp()`, but in other places just the system time was used. In some complex
scenarios like #5088 (restoration of a backup made before a contact verification) it led to wrong
sort timestamps of protection messages and also messages following by them.

But to reduce number of args passed to functions needing to calculate the sort timestamp, add
message timestamps to `struct MimeMessage` which is anyway passed everywhere.
2023-12-13 20:29:47 -03:00
link2xt 2a0a51bea0 chore: remove n0-computer from deny.toml 2023-12-12 19:57:24 +00:00
link2xt 91d94d5920 build: use released version of iroh 0.4.2 2023-12-12 14:56:38 +00:00
link2xt c59f21230d chore(release): prepare for 1.132.1 2023-12-12 02:58:29 +00:00
link2xt 828cc1fbd1 fix(connectivity): return false from all_work_done() immediately after connecting
We do not want all_work_done() to return true immediately
after calling start_io(), but only when connection goes idle.

"Connected" state is set immediately after connecting to the server,
but it does not mean there is nothing to do.

This change make all_work_done() return false
from the Connected state and introduces a new Idle
connectivity state that is only set before connection
actually goes idle. For idle state all_work_done() returns true.

From the user point of view both old Connected state
and new Idle state look the same.
2023-12-12 02:19:51 +00:00
link2xt 57f4958fc6 test(deltachat-rpc-client): test Account.{import,export}_self_keys 2023-12-11 06:43:10 +00:00
link2xt 3aeb57b4df api(deltachat-jsonrpc-client): add Account.{import,export}_self_keys 2023-12-11 06:43:10 +00:00
link2xt 1b85614db9 fix: renew IDLE timeout on keepalives and reduce it to 5 minutes
This change depends on async-imap update that resets the timeout
every time an `* OK Still here` is received.

Reducing timeout allows to detect lost connections
not later than 6 minutes
because Delta Chat will attempt to finish IDLE with DONE
after 5 minutes without keepalives
and will either get TCP RST directly
or, worst case, wait another minute for TCP socket read timeout.
2023-12-11 06:32:13 +00:00
link2xt 57ecf49eb1 chore: fix typo ("Bot" instead of "Bob") 2023-12-11 04:08:45 +00:00
iequidoo f279b0d1e5 feat: Sync user actions for ad-hoc groups across devices (#5065)
Ad-hoc groups don't have grpid-s that can be used to identify them across devices and thus wasn't
synced until now.

The same problem already exists for assigning messages to ad-hoc groups and this assignment is done
by `get_parent_message()` and `lookup_chat_by_reply()`. Let's reuse this logic for the
synchronisation, it works well enough and this way we have less surprises than if we try to
implement grpids for ad-hoc groups. I.e. add an `Msgids` variant to `chat::SyncId` analogous to the
"References" header in messages and put two following Message-IDs to a sync message:
- The latest message A having `DownloadState::Done` and the state to be one of `InFresh, InNoticed,
  InSeen, OutDelivered, OutMdnRcvd`.
- The message that A references in `In-Reply-To`.

This way the logic is almost the same to what we have in `Chat::prepare_msg_raw()` (the difference
is that we don't use the oldest Message-ID) and it's easier to reuse the existing code.

NOTE: If a chat has only an OutPending message f.e., the synchronisation wouldn't work, but trying
to work in such a corner case has no significant value and isn't worth complicating the code.
2023-12-09 01:41:07 -03:00
iequidoo 32071297e6 feat: Add "From:" to protected headers for signed-only messages 2023-12-09 00:57:35 -03:00
link2xt 1d98c38ff3 ci: update to Rust 1.74.1 2023-12-08 23:52:36 +00:00
link2xt c09e0e2b65 refactor: move AEAP and peerstate save from mimeparser to receive_imf()
Ideally mimeparser should be functional
and have no side effects such as modifying a peerstate in the database.
2023-12-08 23:32:03 +00:00
link2xt 0c8f967391 test: refine test_encrypted_no_autocrypt()
- Use TestContextManager
- Actually run receive_imf rather than only mimeparser on "received" messages
- Check that received message parts actually have a padlock
2023-12-08 23:32:03 +00:00
link2xt aca34379e0 fix: add padlock to empty part if the whole message is empty
parse_mime_recursive() skips empty text parts,
so there may be no parts as the result of parsing.
In this case an empty part is added.
However, because it is added with parts.push()
rather than add_single_part(),
it is added without a padlock even if the message is encrypted.
`do_add_single_part()` adds padlock (GuaranteeE2EE param)
and should be used to add parts instead.
2023-12-07 03:38:20 +00:00
34 changed files with 747 additions and 444 deletions
+4 -4
View File
@@ -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
+23
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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"
+1 -1
View File
@@ -53,5 +53,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.132.0"
"version": "1.132.1"
}
+1 -1
View File
@@ -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)
+27 -2
View File
@@ -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 -1
View File
@@ -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"
-1
View File
@@ -102,5 +102,4 @@ github = [
"async-email",
"deltachat",
"djc",
"n0-computer", # iroh
]
+1 -1
View File
@@ -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"
}
+26
View File
@@ -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
View File
@@ -1 +1 @@
2023-12-06
2023-12-12
+222 -48
View File
@@ -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
View File
@@ -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(())
}
+1
View File
@@ -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
View File
@@ -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");
+4 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(())
}
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -150,7 +150,7 @@ impl Context {
modified: time(),
});
self.emit_event(EventType::ConnectivityChanged);
self.emit_event(EventType::ConnectivityChanged { connectivity: None });
Ok(())
}
}
+86 -136
View File
@@ -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
View File
@@ -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,
}
+45 -19
View File
@@ -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
View File
@@ -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?;
+5 -3
View File
@@ -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
View File
@@ -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());
+49
View File
@@ -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?