mirror of
https://github.com/chatmail/core.git
synced 2026-06-30 11:36:35 +03:00
Compare commits
4 Commits
governor
...
draft-dl-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd57253cf3 | ||
|
|
25ee17cc6a | ||
|
|
1df131c6ae | ||
|
|
aa942b0c2a |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,29 +1,4 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Changes
|
||||
- `Accounts` is not cloneable anymore #2654 #2658
|
||||
- always check certificates strictly when connecting over SOCKS5 in Automatic mode #2657
|
||||
- update chat/contact data only when there was no newer update #2642
|
||||
- improve Doxygen documentation style #2647
|
||||
|
||||
### Fixes
|
||||
- ignore MDNs sent to self #2674
|
||||
- fix pkg-config file #2660
|
||||
|
||||
## 1.60.0
|
||||
|
||||
### Added
|
||||
- add device message to warn about QUOTA #2621
|
||||
- add SOCKS5 support #2474 #2620
|
||||
|
||||
### Changes
|
||||
- don't emit multiple events with the same import/export progress number #2639
|
||||
- reduce message length limit to 5000 chars #2615
|
||||
|
||||
### Fixes
|
||||
- keep event emitter from closing when there are no accounts #2636
|
||||
# Changelog
|
||||
|
||||
## 1.59.0
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(deltachat LANGUAGES C)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_program(CARGO cargo)
|
||||
|
||||
@@ -36,6 +35,7 @@ add_custom_target(
|
||||
"target/release/pkgconfig/deltachat.pc"
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "target/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "target/release/libdeltachat.so" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
337
Cargo.lock
generated
337
Cargo.lock
generated
@@ -32,14 +32,25 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd2bc6d3f370b5666245ff421e231cba4353df936e26986d2918e61a8fd6aef6"
|
||||
dependencies = [
|
||||
"aes-soft 0.5.0",
|
||||
"aesni 0.8.0",
|
||||
"block-cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
|
||||
dependencies = [
|
||||
"aes-soft",
|
||||
"aesni",
|
||||
"aes-soft 0.6.4",
|
||||
"aesni 0.10.0",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
@@ -50,13 +61,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"aes 0.6.0",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-soft"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63dd91889c49327ad7ef3b500fd1109dbd3c509a03db0d4a9ce413b79f575cb6"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"byteorder",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-soft"
|
||||
version = "0.6.4"
|
||||
@@ -67,6 +89,16 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aesni"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6fe808308bb07d393e2ea47780043ec47683fcf19cf5efc8ca51c50cc8c68a"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aesni"
|
||||
version = "0.10.0"
|
||||
@@ -77,12 +109,6 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.2"
|
||||
@@ -114,9 +140,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.43"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
|
||||
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
@@ -320,9 +346,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-std"
|
||||
version = "1.10.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952"
|
||||
checksum = "d9f06685bad74e0570f5213741bea82158279a4103d988e57bfada11ad230341"
|
||||
dependencies = [
|
||||
"async-attributes",
|
||||
"async-channel",
|
||||
@@ -383,9 +409,9 @@ checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.51"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
|
||||
checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -492,9 +518,9 @@ checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@@ -559,12 +585,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "blowfish"
|
||||
version = "0.7.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b"
|
||||
checksum = "0f06850ba969bc59388b2cc0a4f186fc6d9d37208863b15b84ae3866ac90ac06"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"byteorder",
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
@@ -641,12 +667,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cast5"
|
||||
version = "0.9.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1285caf81ea1f1ece6b24414c521e625ad0ec94d880625c20f2e65d8d3f78823"
|
||||
checksum = "e3ed1e6b53a3de8bafcce4b88867893c234e57f91686a4726d8e803771f0b55b"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"byteorder",
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
@@ -658,11 +684,11 @@ checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfb-mode"
|
||||
version = "0.6.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d6975e91054798d325f85f50115056d7deccf6817fe7f947c438ee45b119632"
|
||||
checksum = "8fa76b7293f89734378d27057d169dc68077ad34b21dbcabf1c0a646a9462592"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"stream-cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -813,9 +839,9 @@ checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -843,16 +869,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.5"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
|
||||
checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools",
|
||||
"itertools 0.10.1",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
@@ -869,12 +895,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.4.4"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
|
||||
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
"itertools 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1095,7 +1121,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.60.0"
|
||||
version = "1.59.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1120,12 +1146,11 @@ dependencies = [
|
||||
"fast-socks5",
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"governor",
|
||||
"hex",
|
||||
"humansize",
|
||||
"image",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"itertools 0.10.1",
|
||||
"kamadak-exif",
|
||||
"lettre_email",
|
||||
"libc",
|
||||
@@ -1176,7 +1201,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.60.0"
|
||||
version = "1.59.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
@@ -1216,12 +1241,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "des"
|
||||
version = "0.6.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b24e7c748888aa2fa8bce21d8c64a52efc810663285315ac7476f7197a982fae"
|
||||
checksum = "e084b5048dec677e6c9f27d7abc551dde7d127cf4127fea82323c98a30d7fa0d"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"byteorder",
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
@@ -1494,9 +1519,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fast-socks5"
|
||||
version = "0.4.3"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba337793c1ee49731350a8d971d791651ed51d6e814ab4ddabd79c12b5366140"
|
||||
checksum = "6c1955b65d95243f547eb1d1ee6e5ce75ecf6daaeb72b08cd6c66e549d6d88e1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
@@ -1596,9 +1621,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
|
||||
checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1611,9 +1636,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
|
||||
checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -1621,15 +1646,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
|
||||
checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
|
||||
checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -1638,9 +1663,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
|
||||
checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
@@ -1659,9 +1684,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
|
||||
checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"proc-macro-hack",
|
||||
@@ -1672,27 +1697,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
|
||||
checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.17"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
|
||||
checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"futures-channel",
|
||||
@@ -1780,46 +1799,19 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "governor"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c5d2f987ee8f6dff3fa1a352058dc59b990e447e4c7846aa7d804971314f7b"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"no-std-compat",
|
||||
"nonzero_ext",
|
||||
"parking_lot",
|
||||
"quanta",
|
||||
"rand 0.8.3",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25"
|
||||
dependencies = [
|
||||
"ahash 0.3.8",
|
||||
"autocfg 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash 0.7.2",
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1828,7 +1820,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1898,9 +1890,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-client"
|
||||
version = "6.5.1"
|
||||
version = "6.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea880b03c18a7e981d7fb3608b8904a98425d53c440758fcebf7d934aa56547c"
|
||||
checksum = "5566ecc26bc6b04e773e680d66141fced78e091ad818e420d726c152b05a64ff"
|
||||
dependencies = [
|
||||
"async-h1",
|
||||
"async-native-tls",
|
||||
@@ -2022,7 +2014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2058,6 +2050,15 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.1"
|
||||
@@ -2081,9 +2082,9 @@ checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.51"
|
||||
version = "0.3.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
|
||||
checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -2160,9 +2161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.101"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2217,9 +2218,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mailparse"
|
||||
version = "0.13.6"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee6e1ca1c8396da58f8128176f6980dd57bec84c8670a479519d3655f2d6734"
|
||||
checksum = "c06f526fc13a50f46a3689a6f438cb833c59817c898bb40a3954f341ddf74ce1"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"charset",
|
||||
@@ -2313,9 +2314,9 @@ checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.8"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
|
||||
checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
@@ -2360,15 +2361,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
dependencies = [
|
||||
"hashbrown 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "4.2.3"
|
||||
@@ -2403,12 +2395,6 @@ dependencies = [
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonzero_ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44a1290799eababa63ea60af0cbc3f03363e328e58f32fb0294798ed3e85f444"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
@@ -2645,11 +2631,11 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pgp"
|
||||
version = "0.7.2"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "856124b4d0a95badd3e1ad353edd7157fc6c6995767b78ef62848f3b296405ff"
|
||||
checksum = "501f8c2834bc16a23ae40932b9f924c6c5fc1d7cd1cc3536a532f37e81f603ed"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes 0.5.0",
|
||||
"base64 0.12.3",
|
||||
"bitfield",
|
||||
"block-modes",
|
||||
@@ -2660,7 +2646,6 @@ dependencies = [
|
||||
"cast5",
|
||||
"cfb-mode",
|
||||
"chrono",
|
||||
"cipher",
|
||||
"circular",
|
||||
"clear_on_drop",
|
||||
"crc24",
|
||||
@@ -2753,9 +2738,9 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
|
||||
checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
@@ -2884,16 +2869,6 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d98dc777a7a39b76b1a26ae9d3f691f4c1bc0455090aa0b64dfa8cb7fc34c135"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@@ -2926,9 +2901,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quoted_printable"
|
||||
version = "0.4.3"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1238256b09923649ec89b08104c4dfe9f6cb2fea734a5db5384e44916d59e9c5"
|
||||
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
|
||||
|
||||
[[package]]
|
||||
name = "r2d2"
|
||||
@@ -3375,9 +3350,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3394,9 +3369,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3405,9 +3380,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.67"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
|
||||
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -3440,9 +3415,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.8"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
|
||||
checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -3459,9 +3434,9 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -3652,6 +3627,16 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
|
||||
|
||||
[[package]]
|
||||
name = "stream-cipher"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c80e15f898d8d8f25db24c253ea615cc14acf418ff307822995814e7d42cfa89"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
@@ -3684,20 +3669,18 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||
|
||||
[[package]]
|
||||
name = "surf"
|
||||
version = "2.3.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f856d60bdb4679fc9ec516c34093484e963431b5016a8429f85a8e74b5ccaa"
|
||||
checksum = "2a154d33ca6b5e1fe6fd1c760e5a5cc1202425f6cca2e13229f16a69009f6328"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"cfg-if 1.0.0",
|
||||
"futures-util",
|
||||
"getrandom 0.2.2",
|
||||
"http-client",
|
||||
"http-types",
|
||||
"log",
|
||||
"mime_guess",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3705,9 +3688,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.76"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
|
||||
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3766,18 +3749,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.29"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
|
||||
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.29"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
|
||||
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3931,12 +3914,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "twofish"
|
||||
version = "0.5.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0028f5982f23ecc9a1bc3008ead4c664f843ed5d78acd3d213b99ff50c441bc2"
|
||||
checksum = "e7a30db256d7388f6e08efa0a8e9e62ee34dd1af59706c76c9e8c97c2a500f12"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"byteorder",
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
@@ -4103,9 +4086,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.74"
|
||||
version = "0.2.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
|
||||
checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -4113,9 +4096,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.74"
|
||||
version = "0.2.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
|
||||
checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
@@ -4140,9 +4123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.74"
|
||||
version = "0.2.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
|
||||
checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -4150,9 +4133,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.74"
|
||||
version = "0.2.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
|
||||
checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4163,15 +4146,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.74"
|
||||
version = "0.2.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
|
||||
checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.51"
|
||||
version = "0.3.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
|
||||
checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -4275,9 +4258,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.3.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
||||
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
33
Cargo.toml
33
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.60.0"
|
||||
version = "1.59.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
@@ -15,41 +15,40 @@ lto = true
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
|
||||
ansi_term = { version = "0.12.1", optional = true }
|
||||
anyhow = "1.0.43"
|
||||
anyhow = "1.0.42"
|
||||
async-imap = { git = "https://github.com/async-email/async-imap" }
|
||||
async-native-tls = { version = "0.3.3" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", branch="master", features = ["socks5"] }
|
||||
async-std-resolver = "0.20.3"
|
||||
async-std = { version = "~1.10.0", features = ["unstable"] }
|
||||
async-std = { version = "~1.9.0", features = ["unstable"] }
|
||||
async-tar = "0.3.0"
|
||||
async-trait = "0.1.51"
|
||||
async-trait = "0.1.50"
|
||||
backtrace = "0.3.59"
|
||||
base64 = "0.13"
|
||||
bitflags = "1.3.2"
|
||||
bitflags = "1.3.1"
|
||||
byteorder = "1.3.1"
|
||||
chrono = "0.4.6"
|
||||
dirs = { version = "3.0.2", optional=true }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
escaper = "0.1.1"
|
||||
futures = "0.3.17"
|
||||
governor = "0.3.2"
|
||||
futures = "0.3.16"
|
||||
hex = "0.4.0"
|
||||
image = { version = "0.23.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
indexmap = "1.7.0"
|
||||
itertools = "0.10.1"
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2.101"
|
||||
libc = "0.2.98"
|
||||
log = {version = "0.4.8", optional = true }
|
||||
mailparse = "0.13.6"
|
||||
native-tls = "0.2.8"
|
||||
mailparse = "0.13.5"
|
||||
native-tls = "0.2.3"
|
||||
num_cpus = "1.13.0"
|
||||
num-derive = "0.3.0"
|
||||
num-traits = "0.2.6"
|
||||
once_cell = "1.8.0"
|
||||
percent-encoding = "2.0"
|
||||
pgp = { version = "0.7.2", default-features = false }
|
||||
pgp = { version = "0.7.0", default-features = false }
|
||||
pretty_env_logger = { version = "0.4.0", optional = true }
|
||||
quick-xml = "0.22.0"
|
||||
r2d2 = "0.8.9"
|
||||
@@ -62,23 +61,23 @@ rustyline = { version = "8.2.0", optional = true }
|
||||
sanitize-filename = "0.3.0"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sha-1 = "0.9.8"
|
||||
sha2 = "0.9.8"
|
||||
sha-1 = "0.9.7"
|
||||
sha2 = "0.9.5"
|
||||
smallvec = "1.0.0"
|
||||
stop-token = "0.2.0"
|
||||
strum = "0.21.0"
|
||||
strum_macros = "0.21.1"
|
||||
surf = { version = "2.3.1", default-features = false, features = ["h1-client"] }
|
||||
thiserror = "1.0.29"
|
||||
surf = { version = "2.0.0-alpha.4", default-features = false, features = ["h1-client"] }
|
||||
thiserror = "1.0.26"
|
||||
toml = "0.5.6"
|
||||
url = "2.2.2"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
fast-socks5 = "0.4.3"
|
||||
fast-socks5 = "0.4.2"
|
||||
humansize = "1.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
async-std = { version = "1.10.0", features = ["unstable", "attributes"] }
|
||||
async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
|
||||
criterion = "0.3"
|
||||
futures-lite = "1.12.0"
|
||||
log = "0.4.11"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.60.0"
|
||||
version = "1.59.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
@@ -20,9 +20,9 @@ libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
serde_json = "1.0"
|
||||
async-std = "1.10.0"
|
||||
anyhow = "1.0.43"
|
||||
thiserror = "1.0.29"
|
||||
async-std = "1.9.0"
|
||||
anyhow = "1.0.42"
|
||||
thiserror = "1.0.26"
|
||||
rand = "0.7.3"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -4,16 +4,4 @@ div.fragment {
|
||||
background-color: #e0e0e0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #e0e0e0;
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
@@ -377,9 +377,6 @@ int dc_set_config (dc_context_t* context, const char*
|
||||
* an error (no warning as it should be shown to the user) is logged but the attachment is sent anyway.
|
||||
* - `sys.config_keys` = get a space-separated list of all config-keys available.
|
||||
* The config-keys are the keys that can be passed to the parameter `key` of this function.
|
||||
* - `quota_exceeding` = 0: quota is unknown or in normal range;
|
||||
* >=80: quota is about to exceed, the value is the concrete percentage,
|
||||
* a device message is added when that happens, however, that value may still be interesting for bots.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object. For querying system values, this can be NULL.
|
||||
@@ -586,14 +583,6 @@ void dc_configure (dc_context_t* context);
|
||||
* Typically, for unconfigured accounts, the user is prompted
|
||||
* to enter some settings and dc_configure() is called in a thread then.
|
||||
*
|
||||
* A once successfully configured context cannot become unconfigured again;
|
||||
* if a subsequent call to dc_configure() fails,
|
||||
* the prior configuration is used.
|
||||
*
|
||||
* However, of course, also a configuration may stop working,
|
||||
* as eg. the password was changed on the server.
|
||||
* To check that use eg. dc_get_connectivity().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @return 1=context is configured and can be used;
|
||||
@@ -3914,6 +3903,54 @@ int dc_msg_get_videochat_type (const dc_msg_t* msg);
|
||||
int dc_msg_has_html (dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Check if the message is completely downloaded
|
||||
* or if some further action is needed.
|
||||
*
|
||||
* The function returns one of:
|
||||
* - @ref DC_DOWNLOAD_NO_URL - The message does not need any further download action
|
||||
* and should be rendered as usual.
|
||||
* - @ref DC_DOWNLOAD_AVAILABLE - There is additional content to download.
|
||||
* Tn addition to the usual message rendering,
|
||||
* the UI shall show a download button that starts dc_schedule_download()
|
||||
* - @ref DC_DOWNLOAD_IN_PROGRESS - Download was started with dc_schedule_download() and is still in progress.
|
||||
* On progress changes and if the download fails or succeeds,
|
||||
* the event @ref DC_EVENT_DOWNLOAD_PROGRESS will be emitted.
|
||||
* - @ref DC_DOWNLOAD_DONE - Download finished successfully
|
||||
* - @ref DC_DOWNLOAD_FAILURE - Download error, the user may start over calling dc_schedule_download() again.
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return One of the @ref DC_DOWNLOAD values
|
||||
*/
|
||||
int dc_msg_download_status(const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Advices the core to start downloading a message.
|
||||
* This function is typically called when the user hits the "Download" button
|
||||
* that is shown by the UI in case dc_msg_download_status()
|
||||
* returns @ref DC_DOWNLOAD_AVAILABLE or @ref DC_DOWNLOAD_FAILURE.
|
||||
*
|
||||
* The UI may want to show a file selector and let the user chose a download location.
|
||||
* The file name in the file selector may be prefilled using dc_msg_get_filename().
|
||||
*
|
||||
* During the download, the progress, errors and success
|
||||
* are reported using @ref DC_EVENT_DOWNLOAD_PROGRESS.
|
||||
*
|
||||
* Once the @ref DC_EVENT_DOWNLOAD_PROGRESS reports success,
|
||||
* The file can be accessed as usual using dc_msg_get_file().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param path Path to the destination file.
|
||||
* You can specify NULL here to download
|
||||
* to a reasonable file name in the internal blob-directory.
|
||||
* @param msg_id Message-ID to download the content for.
|
||||
*/
|
||||
void dc_schedule_download(dc_context_t* context, int msg_id, const char* path);
|
||||
|
||||
|
||||
/**
|
||||
* Set the text of a message object.
|
||||
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
||||
@@ -5164,6 +5201,16 @@ void dc_event_unref(dc_event_t* event);
|
||||
*/
|
||||
#define DC_EVENT_CONNECTIVITY_CHANGED 2100
|
||||
|
||||
|
||||
/**
|
||||
* Inform about the progress of a download started by dc_schedule_download().
|
||||
*
|
||||
* @param data1 (int) Message-ID the progress is reported for.
|
||||
* @param data2 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
*/
|
||||
#define DC_EVENT_DOWNLOAD_PROGRESS 2120
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
@@ -5294,6 +5341,31 @@ void dc_event_unref(dc_event_t* event);
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_DOWNLOAD DC_DOWNLOAD
|
||||
*
|
||||
* These constants describe the download state of a message.
|
||||
* The download state can be retrieved using dc_msg_download_status()
|
||||
* and usually changes after calling dc_schedule_download().
|
||||
*
|
||||
* @addtogroup DC_DOWNLOAD
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define DC_DOWNLOAD_NO_URL 10 ///< Download not needed, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_AVAILABLE 20 ///< Download available, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_IN_PROGRESS 30 ///< Download in progress, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_DONE 40 ///< Download done, see dc_msg_download_status() for details.
|
||||
#define DC_DOWNLOAD_FAILURE 50 ///< Download failed, see dc_msg_download_status() for details.
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* TODO: Strings need some doumentation about used placeholders.
|
||||
*
|
||||
* @defgroup DC_STR DC_STR
|
||||
*
|
||||
* These constants are used to define strings using dc_set_stock_translation().
|
||||
@@ -5686,13 +5758,6 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// Used in message summary text for notifications and chatlist.
|
||||
#define DC_STR_FORWARDED 97
|
||||
|
||||
/// "Quota exceeding, already %1$s%% used."
|
||||
///
|
||||
/// Used as device message text.
|
||||
///
|
||||
/// `%1$s` will be replaced by the percentage used
|
||||
#define DC_STR_QUOTA_EXCEEDING_MSG_BODY 98
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -17,13 +17,11 @@ extern crate serde_json;
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Write;
|
||||
use std::ops::Deref;
|
||||
use std::ptr;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use async_std::sync::RwLock;
|
||||
use async_std::task::{block_on, spawn};
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
|
||||
@@ -1572,8 +1570,6 @@ pub unsafe extern "C" fn dc_delete_msgs(
|
||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
||||
|
||||
block_on(message::delete_msgs(ctx, &msg_ids))
|
||||
.log_err(ctx, "failed dc_delete_msgs() call")
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2051,9 +2047,7 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
|
||||
ctx,
|
||||
ChatId::new(chat_id),
|
||||
seconds as i64,
|
||||
))
|
||||
.log_err(ctx, "Failed dc_send_locations_to_chat()")
|
||||
.ok();
|
||||
));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2072,8 +2066,7 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat(
|
||||
Some(ChatId::new(chat_id))
|
||||
};
|
||||
|
||||
block_on(location::is_sending_locations_to_chat(ctx, chat_id))
|
||||
.unwrap_or_log_default(ctx, "Failed dc_is_sending_locations_to_chat()") as libc::c_int
|
||||
block_on(location::is_sending_locations_to_chat(ctx, chat_id)) as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2404,13 +2397,13 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
|
||||
let ctx = &*ffi_list.context;
|
||||
|
||||
block_on(async move {
|
||||
let summary = ffi_list
|
||||
let lot = ffi_list
|
||||
.list
|
||||
.get_summary(ctx, index as usize, maybe_chat)
|
||||
.await
|
||||
.log_err(ctx, "get_summary failed")
|
||||
.unwrap_or_default();
|
||||
Box::into_raw(Box::new(summary.into()))
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2430,15 +2423,13 @@ pub unsafe extern "C" fn dc_chatlist_get_summary2(
|
||||
} else {
|
||||
Some(MsgId::new(msg_id))
|
||||
};
|
||||
let summary = block_on(Chatlist::get_summary2(
|
||||
ctx,
|
||||
ChatId::new(chat_id),
|
||||
msg_id,
|
||||
None,
|
||||
))
|
||||
.log_err(ctx, "get_summary2 failed")
|
||||
.unwrap_or_default();
|
||||
Box::into_raw(Box::new(summary.into()))
|
||||
block_on(async move {
|
||||
let lot = Chatlist::get_summary2(ctx, ChatId::new(chat_id), msg_id, None)
|
||||
.await
|
||||
.log_err(ctx, "get_summary2 failed")
|
||||
.unwrap_or_default();
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2980,10 +2971,10 @@ pub unsafe extern "C" fn dc_msg_get_summary(
|
||||
let ffi_msg = &mut *msg;
|
||||
let ctx = &*ffi_msg.context;
|
||||
|
||||
let summary = block_on(async move { ffi_msg.message.get_summary(ctx, maybe_chat).await })
|
||||
.log_err(ctx, "dc_msg_get_summary failed")
|
||||
.unwrap_or_default();
|
||||
Box::into_raw(Box::new(summary.into()))
|
||||
block_on(async move {
|
||||
let lot = ffi_msg.message.get_summary(ctx, maybe_chat).await;
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3692,29 +3683,8 @@ pub unsafe extern "C" fn dc_provider_unref(provider: *mut dc_provider_t) {
|
||||
|
||||
// -- Accounts
|
||||
|
||||
/// Reader-writer lock wrapper for accounts manager to guarantee thread safety when using
|
||||
/// `dc_accounts_t` in multiple threads at once.
|
||||
pub struct AccountsWrapper {
|
||||
inner: RwLock<Accounts>,
|
||||
}
|
||||
|
||||
impl Deref for AccountsWrapper {
|
||||
type Target = RwLock<Accounts>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountsWrapper {
|
||||
fn new(accounts: Accounts) -> Self {
|
||||
let inner = RwLock::new(accounts);
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing a list of deltachat accounts.
|
||||
pub type dc_accounts_t = AccountsWrapper;
|
||||
pub type dc_accounts_t = Accounts;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_accounts_new(
|
||||
@@ -3737,7 +3707,7 @@ pub unsafe extern "C" fn dc_accounts_new(
|
||||
let accs = block_on(Accounts::new(os_name, as_path(dbfile).to_path_buf().into()));
|
||||
|
||||
match accs {
|
||||
Ok(accs) => Box::into_raw(Box::new(AccountsWrapper::new(accs))),
|
||||
Ok(accs) => Box::into_raw(Box::new(accs)),
|
||||
Err(err) => {
|
||||
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
|
||||
eprintln!("failed to create accounts: {:#}", err);
|
||||
@@ -3769,7 +3739,7 @@ pub unsafe extern "C" fn dc_accounts_get_account(
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.read().await.get_account(id).await })
|
||||
block_on(accounts.get_account(id))
|
||||
.map(|ctx| Box::into_raw(Box::new(ctx)))
|
||||
.unwrap_or_else(std::ptr::null_mut)
|
||||
}
|
||||
@@ -3784,7 +3754,7 @@ pub unsafe extern "C" fn dc_accounts_get_selected_account(
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.read().await.get_selected_account().await })
|
||||
block_on(accounts.get_selected_account())
|
||||
.map(|ctx| Box::into_raw(Box::new(ctx)))
|
||||
.unwrap_or_else(std::ptr::null_mut)
|
||||
}
|
||||
@@ -3800,7 +3770,7 @@ pub unsafe extern "C" fn dc_accounts_select_account(
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.write().await.select_account(id).await })
|
||||
block_on(accounts.select_account(id))
|
||||
.map(|_| 1)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
@@ -3812,9 +3782,9 @@ pub unsafe extern "C" fn dc_accounts_add_account(accounts: *mut dc_accounts_t) -
|
||||
return 0;
|
||||
}
|
||||
|
||||
let accounts = &mut *accounts;
|
||||
let accounts = &*accounts;
|
||||
|
||||
block_on(async move { accounts.write().await.add_account().await }).unwrap_or(0)
|
||||
block_on(accounts.add_account()).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3827,9 +3797,9 @@ pub unsafe extern "C" fn dc_accounts_remove_account(
|
||||
return 0;
|
||||
}
|
||||
|
||||
let accounts = &mut *accounts;
|
||||
let accounts = &*accounts;
|
||||
|
||||
block_on(async move { accounts.write().await.remove_account(id).await })
|
||||
block_on(accounts.remove_account(id))
|
||||
.map(|_| 1)
|
||||
.unwrap_or_else(|_| 0)
|
||||
}
|
||||
@@ -3844,18 +3814,12 @@ pub unsafe extern "C" fn dc_accounts_migrate_account(
|
||||
return 0;
|
||||
}
|
||||
|
||||
let accounts = &mut *accounts;
|
||||
let accounts = &*accounts;
|
||||
let dbfile = to_string_lossy(dbfile);
|
||||
|
||||
block_on(async move {
|
||||
accounts
|
||||
.write()
|
||||
.await
|
||||
.migrate_account(async_std::path::PathBuf::from(dbfile))
|
||||
.await
|
||||
})
|
||||
.map(|_| 1)
|
||||
.unwrap_or_else(|_| 0)
|
||||
block_on(accounts.migrate_account(async_std::path::PathBuf::from(dbfile)))
|
||||
.map(|_| 1)
|
||||
.unwrap_or_else(|_| 0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3866,7 +3830,7 @@ pub unsafe extern "C" fn dc_accounts_get_all(accounts: *mut dc_accounts_t) -> *m
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
let list = block_on(async move { accounts.read().await.get_all().await });
|
||||
let list = block_on(accounts.get_all());
|
||||
let array: dc_array_t = list.into();
|
||||
|
||||
Box::into_raw(Box::new(array))
|
||||
@@ -3879,7 +3843,7 @@ pub unsafe extern "C" fn dc_accounts_all_work_done(accounts: *mut dc_accounts_t)
|
||||
return 0;
|
||||
}
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.read().await.all_work_done().await as libc::c_int })
|
||||
block_on(async move { accounts.all_work_done().await as libc::c_int })
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3890,7 +3854,7 @@ pub unsafe extern "C" fn dc_accounts_start_io(accounts: *mut dc_accounts_t) {
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.read().await.start_io().await });
|
||||
block_on(accounts.start_io());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3901,7 +3865,7 @@ pub unsafe extern "C" fn dc_accounts_stop_io(accounts: *mut dc_accounts_t) {
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.read().await.stop_io().await });
|
||||
block_on(accounts.stop_io());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3912,7 +3876,7 @@ pub unsafe extern "C" fn dc_accounts_maybe_network(accounts: *mut dc_accounts_t)
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.read().await.maybe_network().await });
|
||||
block_on(accounts.maybe_network());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -3923,7 +3887,7 @@ pub unsafe extern "C" fn dc_accounts_maybe_network_lost(accounts: *mut dc_accoun
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
block_on(async move { accounts.write().await.maybe_network_lost().await });
|
||||
block_on(accounts.maybe_network_lost());
|
||||
}
|
||||
|
||||
pub type dc_accounts_event_emitter_t = deltachat::accounts::EventEmitter;
|
||||
@@ -3938,7 +3902,7 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
|
||||
}
|
||||
|
||||
let accounts = &*accounts;
|
||||
let emitter = block_on(async move { accounts.read().await.get_event_emitter().await });
|
||||
let emitter = block_on(accounts.get_event_emitter());
|
||||
|
||||
Box::into_raw(Box::new(emitter))
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ license = "MPL-2.0"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "1.0.76"
|
||||
syn = "1.0.74"
|
||||
quote = "1.0.2"
|
||||
|
||||
@@ -16,6 +16,7 @@ use deltachat::dc_tools::*;
|
||||
use deltachat::imex::*;
|
||||
use deltachat::location;
|
||||
use deltachat::log::LogExt;
|
||||
use deltachat::lot::LotState;
|
||||
use deltachat::message::{self, Message, MessageState, MsgId};
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
@@ -568,25 +569,26 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
""
|
||||
},
|
||||
);
|
||||
let summary = chatlist.get_summary(&context, i, Some(&chat)).await?;
|
||||
let lot = chatlist.get_summary(&context, i, Some(&chat)).await?;
|
||||
let statestr = if chat.visibility == ChatVisibility::Archived {
|
||||
" [Archived]"
|
||||
} else {
|
||||
match summary.state {
|
||||
MessageState::OutPending => " o",
|
||||
MessageState::OutDelivered => " √",
|
||||
MessageState::OutMdnRcvd => " √√",
|
||||
MessageState::OutFailed => " !!",
|
||||
match lot.get_state() {
|
||||
LotState::MsgOutPending => " o",
|
||||
LotState::MsgOutDelivered => " √",
|
||||
LotState::MsgOutMdnRcvd => " √√",
|
||||
LotState::MsgOutFailed => " !!",
|
||||
_ => "",
|
||||
}
|
||||
};
|
||||
let timestr = dc_timestamp_to_str(summary.timestamp);
|
||||
let timestr = dc_timestamp_to_str(lot.get_timestamp());
|
||||
let text1 = lot.get_text1();
|
||||
let text2 = lot.get_text2();
|
||||
println!(
|
||||
"{}{}{} [{}]{}",
|
||||
summary
|
||||
.prefix
|
||||
.map_or_else(String::new, |prefix| format!("{}: ", prefix)),
|
||||
summary.text,
|
||||
"{}{}{}{} [{}]{}",
|
||||
text1.unwrap_or(""),
|
||||
if text1.is_some() { ": " } else { "" },
|
||||
text2.unwrap_or(""),
|
||||
statestr,
|
||||
×tr,
|
||||
if chat.is_sending_locations() {
|
||||
@@ -600,7 +602,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
);
|
||||
}
|
||||
}
|
||||
if location::is_sending_locations_to_chat(&context, None).await? {
|
||||
if location::is_sending_locations_to_chat(&context, None).await {
|
||||
println!("Location streaming enabled.");
|
||||
}
|
||||
println!("{} chats", cnt);
|
||||
@@ -771,7 +773,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
&context,
|
||||
Some(sel_chat.as_ref().unwrap().get_id())
|
||||
)
|
||||
.await?,
|
||||
.await,
|
||||
);
|
||||
}
|
||||
"getlocations" => {
|
||||
@@ -816,7 +818,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
seconds,
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
println!(
|
||||
"Locations will be sent to Chat#{} for {} seconds. Use 'setlocation <lat> <lng>' to play around.",
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
@@ -1065,7 +1067,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let mut ids = [MsgId::new(0); 1];
|
||||
ids[0] = MsgId::new(arg1.parse()?);
|
||||
message::delete_msgs(&context, &ids).await?;
|
||||
message::delete_msgs(&context, &ids).await;
|
||||
}
|
||||
"listcontacts" | "contacts" | "listverified" => {
|
||||
let contacts = Contact::get_all(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if ! command -v grcov >/dev/null; then
|
||||
if ! which grcov 2>/dev/null 1>&2; then
|
||||
echo >&2 '`grcov` not found. Check README at https://github.com/mozilla/grcov for setup instructions.'
|
||||
echo >&2 'Run `cargo install grcov` to build `grcov` from source.'
|
||||
exit 1
|
||||
|
||||
210
src/accounts.rs
210
src/accounts.rs
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use async_std::channel::{self, Receiver, Sender};
|
||||
use async_std::channel::{Receiver, Sender};
|
||||
use async_std::fs;
|
||||
use async_std::path::PathBuf;
|
||||
use async_std::prelude::*;
|
||||
@@ -16,19 +16,12 @@ use crate::context::Context;
|
||||
use crate::events::Event;
|
||||
|
||||
/// Account manager, that can handle multiple accounts in a single place.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Accounts {
|
||||
dir: PathBuf,
|
||||
config: Config,
|
||||
accounts: BTreeMap<u32, Context>,
|
||||
accounts: Arc<RwLock<BTreeMap<u32, Context>>>,
|
||||
emitter: EventEmitter,
|
||||
|
||||
/// Sender side of the fake event channel.
|
||||
///
|
||||
/// We never send any events over this channel, but hold it during the account manager lifetime
|
||||
/// to prevent `EventEmitter` from returning `None` as long as account manager is alive, even if
|
||||
/// it holds no accounts which could emit events.
|
||||
fake_sender: Sender<crate::events::Event>,
|
||||
}
|
||||
|
||||
impl Accounts {
|
||||
@@ -64,11 +57,6 @@ impl Accounts {
|
||||
let accounts = config.load_accounts().await?;
|
||||
|
||||
let emitter = EventEmitter::new();
|
||||
|
||||
// Fake event stream to prevent event emitter from closing.
|
||||
let (fake_sender, fake_receiver) = channel::bounded(1);
|
||||
emitter.sender.send(fake_receiver).await?;
|
||||
|
||||
for account in accounts.values() {
|
||||
emitter.add_account(account).await?;
|
||||
}
|
||||
@@ -76,21 +64,20 @@ impl Accounts {
|
||||
Ok(Self {
|
||||
dir,
|
||||
config,
|
||||
accounts,
|
||||
accounts: Arc::new(RwLock::new(accounts)),
|
||||
emitter,
|
||||
fake_sender,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get an account by its `id`:
|
||||
pub async fn get_account(&self, id: u32) -> Option<Context> {
|
||||
self.accounts.get(&id).cloned()
|
||||
self.accounts.read().await.get(&id).cloned()
|
||||
}
|
||||
|
||||
/// Get the currently selected account.
|
||||
pub async fn get_selected_account(&self) -> Option<Context> {
|
||||
let id = self.config.get_selected_account().await;
|
||||
self.accounts.get(&id).cloned()
|
||||
self.accounts.read().await.get(&id).cloned()
|
||||
}
|
||||
|
||||
/// Returns the currently selected account's id or None if no account is selected.
|
||||
@@ -102,27 +89,27 @@ impl Accounts {
|
||||
}
|
||||
|
||||
/// Select the given account.
|
||||
pub async fn select_account(&mut self, id: u32) -> Result<()> {
|
||||
pub async fn select_account(&self, id: u32) -> Result<()> {
|
||||
self.config.select_account(id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a new account.
|
||||
pub async fn add_account(&mut self) -> Result<u32> {
|
||||
pub async fn add_account(&self) -> Result<u32> {
|
||||
let os_name = self.config.os_name().await;
|
||||
let account_config = self.config.new_account(&self.dir).await?;
|
||||
|
||||
let ctx = Context::new(os_name, account_config.dbfile().into(), account_config.id).await?;
|
||||
self.emitter.add_account(&ctx).await?;
|
||||
self.accounts.insert(account_config.id, ctx);
|
||||
self.accounts.write().await.insert(account_config.id, ctx);
|
||||
|
||||
Ok(account_config.id)
|
||||
}
|
||||
|
||||
/// Remove an account.
|
||||
pub async fn remove_account(&mut self, id: u32) -> Result<()> {
|
||||
let ctx = self.accounts.remove(&id);
|
||||
pub async fn remove_account(&self, id: u32) -> Result<()> {
|
||||
let ctx = self.accounts.write().await.remove(&id);
|
||||
ensure!(ctx.is_some(), "no account with this id: {}", id);
|
||||
let ctx = ctx.unwrap();
|
||||
ctx.stop_io().await;
|
||||
@@ -139,7 +126,7 @@ impl Accounts {
|
||||
}
|
||||
|
||||
/// Migrate an existing account into this structure.
|
||||
pub async fn migrate_account(&mut self, dbfile: PathBuf) -> Result<u32> {
|
||||
pub async fn migrate_account(&self, dbfile: PathBuf) -> Result<u32> {
|
||||
let blobdir = Context::derive_blobdir(&dbfile);
|
||||
let walfile = Context::derive_walfile(&dbfile);
|
||||
|
||||
@@ -195,7 +182,7 @@ impl Accounts {
|
||||
)
|
||||
.await?;
|
||||
self.emitter.add_account(&ctx).await?;
|
||||
self.accounts.insert(account_config.id, ctx);
|
||||
self.accounts.write().await.insert(account_config.id, ctx);
|
||||
Ok(account_config.id)
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -216,7 +203,7 @@ impl Accounts {
|
||||
|
||||
/// Get a list of all account ids.
|
||||
pub async fn get_all(&self) -> Vec<u32> {
|
||||
self.accounts.keys().copied().collect()
|
||||
self.accounts.read().await.keys().copied().collect()
|
||||
}
|
||||
|
||||
/// This is meant especially for iOS, because iOS needs to tell the system when its background work is done.
|
||||
@@ -230,7 +217,7 @@ impl Accounts {
|
||||
/// - while dc_accounts_all_work_done() returns false:
|
||||
/// - Wait for DC_EVENT_CONNECTIVITY_CHANGED
|
||||
pub async fn all_work_done(&self) -> bool {
|
||||
for account in self.accounts.values() {
|
||||
for account in self.accounts.read().await.values() {
|
||||
if !account.all_work_done().await {
|
||||
return false;
|
||||
}
|
||||
@@ -239,25 +226,29 @@ impl Accounts {
|
||||
}
|
||||
|
||||
pub async fn start_io(&self) {
|
||||
for account in self.accounts.values() {
|
||||
let accounts = &*self.accounts.read().await;
|
||||
for account in accounts.values() {
|
||||
account.start_io().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stop_io(&self) {
|
||||
for account in self.accounts.values() {
|
||||
let accounts = &*self.accounts.read().await;
|
||||
for account in accounts.values() {
|
||||
account.stop_io().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn maybe_network(&self) {
|
||||
for account in self.accounts.values() {
|
||||
let accounts = &*self.accounts.read().await;
|
||||
for account in accounts.values() {
|
||||
account.maybe_network().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn maybe_network_lost(&self) {
|
||||
for account in self.accounts.values() {
|
||||
let accounts = &*self.accounts.read().await;
|
||||
for account in accounts.values() {
|
||||
account.maybe_network_lost().await;
|
||||
}
|
||||
}
|
||||
@@ -272,18 +263,18 @@ impl Accounts {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventEmitter {
|
||||
/// Aggregate stream of events from all accounts.
|
||||
stream: Arc<RwLock<futures::stream::SelectAll<Receiver<crate::events::Event>>>>,
|
||||
stream: Arc<RwLock<futures::stream::SelectAll<crate::events::EventEmitter>>>,
|
||||
|
||||
/// Sender for the channel where new account emitters will be pushed.
|
||||
sender: Sender<Receiver<crate::events::Event>>,
|
||||
sender: Sender<crate::events::EventEmitter>,
|
||||
|
||||
/// Receiver for the channel where new account emitters will be pushed.
|
||||
receiver: Receiver<Receiver<crate::events::Event>>,
|
||||
receiver: Receiver<crate::events::EventEmitter>,
|
||||
}
|
||||
|
||||
impl EventEmitter {
|
||||
pub fn new() -> Self {
|
||||
let (sender, receiver) = channel::unbounded();
|
||||
let (sender, receiver) = async_std::channel::unbounded();
|
||||
Self {
|
||||
stream: Arc::new(RwLock::new(futures::stream::SelectAll::new())),
|
||||
sender,
|
||||
@@ -311,9 +302,7 @@ impl EventEmitter {
|
||||
|
||||
/// Add event emitter of a new account to the aggregate event emitter.
|
||||
pub async fn add_account(&self, context: &Context) -> Result<()> {
|
||||
self.sender
|
||||
.send(context.get_event_emitter().into_inner())
|
||||
.await?;
|
||||
self.sender.send(context.get_event_emitter()).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -339,15 +328,12 @@ pub const CONFIG_NAME: &str = "accounts.toml";
|
||||
pub const DB_NAME: &str = "dc.db";
|
||||
|
||||
/// Account manager configuration file.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
file: PathBuf,
|
||||
inner: InnerConfig,
|
||||
inner: Arc<RwLock<InnerConfig>>,
|
||||
}
|
||||
|
||||
/// Account manager configuration file contents.
|
||||
///
|
||||
/// This is serialized into TOML.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
struct InnerConfig {
|
||||
pub os_name: String,
|
||||
@@ -359,15 +345,14 @@ struct InnerConfig {
|
||||
|
||||
impl Config {
|
||||
pub async fn new(os_name: String, dir: &PathBuf) -> Result<Self> {
|
||||
let inner = InnerConfig {
|
||||
os_name,
|
||||
accounts: Vec::new(),
|
||||
selected_account: 0,
|
||||
next_id: 1,
|
||||
};
|
||||
let cfg = Config {
|
||||
file: dir.join(CONFIG_NAME),
|
||||
inner,
|
||||
inner: Arc::new(RwLock::new(InnerConfig {
|
||||
os_name,
|
||||
accounts: Vec::new(),
|
||||
selected_account: 0,
|
||||
next_id: 1,
|
||||
})),
|
||||
};
|
||||
|
||||
cfg.sync().await?;
|
||||
@@ -376,14 +361,17 @@ impl Config {
|
||||
}
|
||||
|
||||
pub async fn os_name(&self) -> String {
|
||||
self.inner.os_name.clone()
|
||||
self.inner.read().await.os_name.clone()
|
||||
}
|
||||
|
||||
/// Sync the inmemory representation to disk.
|
||||
async fn sync(&self) -> Result<()> {
|
||||
fs::write(&self.file, toml::to_string_pretty(&self.inner)?)
|
||||
.await
|
||||
.context("failed to write config")
|
||||
fs::write(
|
||||
&self.file,
|
||||
toml::to_string_pretty(&*self.inner.read().await)?,
|
||||
)
|
||||
.await
|
||||
.context("failed to write config")
|
||||
}
|
||||
|
||||
/// Read a configuration from the given file into memory.
|
||||
@@ -391,14 +379,18 @@ impl Config {
|
||||
let bytes = fs::read(&file).await.context("failed to read file")?;
|
||||
let inner: InnerConfig = toml::from_slice(&bytes).context("failed to parse config")?;
|
||||
|
||||
Ok(Config { file, inner })
|
||||
Ok(Config {
|
||||
file,
|
||||
inner: Arc::new(RwLock::new(inner)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_accounts(&self) -> Result<BTreeMap<u32, Context>> {
|
||||
let cfg = &*self.inner.read().await;
|
||||
let mut accounts = BTreeMap::new();
|
||||
for account_config in &self.inner.accounts {
|
||||
for account_config in &cfg.accounts {
|
||||
let ctx = Context::new(
|
||||
self.inner.os_name.clone(),
|
||||
cfg.os_name.clone(),
|
||||
account_config.dbfile().into(),
|
||||
account_config.id,
|
||||
)
|
||||
@@ -410,18 +402,19 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Create a new account in the given root directory.
|
||||
async fn new_account(&mut self, dir: &PathBuf) -> Result<AccountConfig> {
|
||||
async fn new_account(&self, dir: &PathBuf) -> Result<AccountConfig> {
|
||||
let id = {
|
||||
let id = self.inner.next_id;
|
||||
let inner = &mut self.inner.write().await;
|
||||
let id = inner.next_id;
|
||||
let uuid = Uuid::new_v4();
|
||||
let target_dir = dir.join(uuid.to_simple_ref().to_string());
|
||||
|
||||
self.inner.accounts.push(AccountConfig {
|
||||
inner.accounts.push(AccountConfig {
|
||||
id,
|
||||
dir: target_dir.into(),
|
||||
uuid,
|
||||
});
|
||||
self.inner.next_id += 1;
|
||||
inner.next_id += 1;
|
||||
id
|
||||
};
|
||||
|
||||
@@ -433,16 +426,16 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Removes an existing acccount entirely.
|
||||
pub async fn remove_account(&mut self, id: u32) -> Result<()> {
|
||||
pub async fn remove_account(&self, id: u32) -> Result<()> {
|
||||
{
|
||||
if let Some(idx) = self.inner.accounts.iter().position(|e| e.id == id) {
|
||||
let inner = &mut *self.inner.write().await;
|
||||
if let Some(idx) = inner.accounts.iter().position(|e| e.id == id) {
|
||||
// remove account from the configs
|
||||
self.inner.accounts.remove(idx);
|
||||
inner.accounts.remove(idx);
|
||||
}
|
||||
if self.inner.selected_account == id {
|
||||
if inner.selected_account == id {
|
||||
// reset selected account
|
||||
self.inner.selected_account =
|
||||
self.inner.accounts.get(0).map(|e| e.id).unwrap_or_default();
|
||||
inner.selected_account = inner.accounts.get(0).map(|e| e.id).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,22 +443,29 @@ impl Config {
|
||||
}
|
||||
|
||||
async fn get_account(&self, id: u32) -> Option<AccountConfig> {
|
||||
self.inner.accounts.iter().find(|e| e.id == id).cloned()
|
||||
self.inner
|
||||
.read()
|
||||
.await
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|e| e.id == id)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub async fn get_selected_account(&self) -> u32 {
|
||||
self.inner.selected_account
|
||||
self.inner.read().await.selected_account
|
||||
}
|
||||
|
||||
pub async fn select_account(&mut self, id: u32) -> Result<()> {
|
||||
pub async fn select_account(&self, id: u32) -> Result<()> {
|
||||
{
|
||||
let inner = &mut *self.inner.write().await;
|
||||
ensure!(
|
||||
self.inner.accounts.iter().any(|e| e.id == id),
|
||||
inner.accounts.iter().any(|e| e.id == id),
|
||||
"invalid account id: {}",
|
||||
id
|
||||
);
|
||||
|
||||
self.inner.selected_account = id;
|
||||
inner.selected_account = id;
|
||||
}
|
||||
|
||||
self.sync().await?;
|
||||
@@ -499,17 +499,23 @@ mod tests {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts1").into();
|
||||
|
||||
let mut accounts1 = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
let accounts1 = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
accounts1.add_account().await.unwrap();
|
||||
|
||||
let accounts2 = Accounts::open(p).await.unwrap();
|
||||
|
||||
assert_eq!(accounts1.accounts.len(), 1);
|
||||
assert_eq!(accounts1.accounts.read().await.len(), 1);
|
||||
assert_eq!(accounts1.config.get_selected_account().await, 1);
|
||||
|
||||
assert_eq!(accounts1.dir, accounts2.dir);
|
||||
assert_eq!(accounts1.config, accounts2.config,);
|
||||
assert_eq!(accounts1.accounts.len(), accounts2.accounts.len());
|
||||
assert_eq!(
|
||||
&*accounts1.config.inner.read().await,
|
||||
&*accounts2.config.inner.read().await,
|
||||
);
|
||||
assert_eq!(
|
||||
accounts1.accounts.read().await.len(),
|
||||
accounts2.accounts.read().await.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@@ -517,26 +523,26 @@ mod tests {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
assert_eq!(accounts.accounts.len(), 0);
|
||||
let accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
assert_eq!(accounts.accounts.read().await.len(), 0);
|
||||
assert_eq!(accounts.config.get_selected_account().await, 0);
|
||||
|
||||
let id = accounts.add_account().await.unwrap();
|
||||
assert_eq!(id, 1);
|
||||
assert_eq!(accounts.accounts.len(), 1);
|
||||
assert_eq!(accounts.accounts.read().await.len(), 1);
|
||||
assert_eq!(accounts.config.get_selected_account().await, 1);
|
||||
|
||||
let id = accounts.add_account().await.unwrap();
|
||||
assert_eq!(id, 2);
|
||||
assert_eq!(accounts.config.get_selected_account().await, id);
|
||||
assert_eq!(accounts.accounts.len(), 2);
|
||||
assert_eq!(accounts.accounts.read().await.len(), 2);
|
||||
|
||||
accounts.select_account(1).await.unwrap();
|
||||
assert_eq!(accounts.config.get_selected_account().await, 1);
|
||||
|
||||
accounts.remove_account(1).await.unwrap();
|
||||
assert_eq!(accounts.config.get_selected_account().await, 2);
|
||||
assert_eq!(accounts.accounts.len(), 1);
|
||||
assert_eq!(accounts.accounts.read().await.len(), 1);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@@ -544,14 +550,14 @@ mod tests {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new("my_os".into(), p.clone()).await?;
|
||||
let accounts = Accounts::new("my_os".into(), p.clone()).await?;
|
||||
assert!(accounts.get_selected_account().await.is_none());
|
||||
assert_eq!(accounts.config.get_selected_account().await, 0);
|
||||
|
||||
let id = accounts.add_account().await?;
|
||||
assert!(accounts.get_selected_account().await.is_some());
|
||||
assert_eq!(id, 1);
|
||||
assert_eq!(accounts.accounts.len(), 1);
|
||||
assert_eq!(accounts.accounts.read().await.len(), 1);
|
||||
assert_eq!(accounts.config.get_selected_account().await, id);
|
||||
|
||||
accounts.remove_account(id).await?;
|
||||
@@ -565,8 +571,8 @@ mod tests {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
assert_eq!(accounts.accounts.len(), 0);
|
||||
let accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
assert_eq!(accounts.accounts.read().await.len(), 0);
|
||||
assert_eq!(accounts.config.get_selected_account().await, 0);
|
||||
|
||||
let extern_dbfile: PathBuf = dir.path().join("other").into();
|
||||
@@ -583,7 +589,7 @@ mod tests {
|
||||
.migrate_account(extern_dbfile.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(accounts.accounts.len(), 1);
|
||||
assert_eq!(accounts.accounts.read().await.len(), 1);
|
||||
assert_eq!(accounts.config.get_selected_account().await, 1);
|
||||
|
||||
let ctx = accounts.get_selected_account().await.unwrap();
|
||||
@@ -602,7 +608,7 @@ mod tests {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
let accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
|
||||
for expected_id in 1..10 {
|
||||
let id = accounts.add_account().await.unwrap();
|
||||
@@ -622,7 +628,7 @@ mod tests {
|
||||
let dummy_accounts = 10;
|
||||
|
||||
let (id0, id1, id2) = {
|
||||
let mut accounts = Accounts::new("my_os".into(), p.clone()).await?;
|
||||
let accounts = Accounts::new("my_os".into(), p.clone()).await?;
|
||||
accounts.add_account().await?;
|
||||
let ids = accounts.get_all().await;
|
||||
assert_eq!(ids.len(), 1);
|
||||
@@ -696,30 +702,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_no_accounts_event_emitter() -> Result<()> {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let accounts = Accounts::new("my_os".into(), p.clone()).await?;
|
||||
|
||||
// Make sure there are no accounts.
|
||||
assert_eq!(accounts.accounts.len(), 0);
|
||||
|
||||
// Create event emitter.
|
||||
let mut event_emitter = accounts.get_event_emitter().await;
|
||||
|
||||
// Test that event emitter does not return `None` immediately.
|
||||
let duration = std::time::Duration::from_millis(1);
|
||||
assert!(async_std::future::timeout(duration, event_emitter.recv())
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
// When account manager is dropped, event emitter is exhausted.
|
||||
drop(accounts);
|
||||
assert_eq!(event_emitter.recv().await?, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
76
src/chat.rs
76
src/chat.rs
@@ -183,7 +183,7 @@ impl ChatId {
|
||||
|| contact_id == DC_CONTACT_ID_SELF
|
||||
{
|
||||
let chat_id = ChatId::get_for_contact(context, contact_id).await?;
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat).await?;
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat).await;
|
||||
chat_id
|
||||
} else {
|
||||
warn!(
|
||||
@@ -284,7 +284,7 @@ impl ChatId {
|
||||
for contact_id in get_chat_contacts(context, self).await? {
|
||||
if contact_id != DC_CONTACT_ID_SELF {
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat)
|
||||
.await?;
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,7 +497,7 @@ impl ChatId {
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
|
||||
job::kill_action(context, Action::Housekeeping).await?;
|
||||
job::kill_action(context, Action::Housekeeping).await;
|
||||
let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10);
|
||||
job::add(context, j).await;
|
||||
|
||||
@@ -1099,9 +1099,10 @@ impl Chat {
|
||||
if self.typ == Chattype::Group
|
||||
&& !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await
|
||||
{
|
||||
context.emit_event(EventType::ErrorSelfNotInGroup(
|
||||
"Cannot send message; self not in group.".into(),
|
||||
));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ErrorSelfNotInGroup("Cannot send message; self not in group.".into())
|
||||
);
|
||||
bail!("Cannot set message; self not in group.");
|
||||
}
|
||||
|
||||
@@ -1222,7 +1223,7 @@ impl Chat {
|
||||
};
|
||||
let ephemeral_timestamp = match ephemeral_timer {
|
||||
EphemeralTimer::Disabled => 0,
|
||||
EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
|
||||
EphemeralTimer::Enabled { duration } => time() + i64::from(duration),
|
||||
};
|
||||
|
||||
let new_mime_headers = if msg.has_html() {
|
||||
@@ -2272,9 +2273,12 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
|
||||
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
context.emit_event(EventType::ErrorSelfNotInGroup(
|
||||
"Cannot add contact to group; self not in group.".into(),
|
||||
));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ErrorSelfNotInGroup(
|
||||
"Cannot add contact to group; self not in group.".into()
|
||||
)
|
||||
);
|
||||
bail!("can not add contact because our account is not part of it");
|
||||
}
|
||||
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
|
||||
@@ -2446,15 +2450,19 @@ impl rusqlite::types::FromSql for MuteDuration {
|
||||
|
||||
pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
|
||||
ensure!(!chat_id.is_special(), "Invalid chat ID");
|
||||
context
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats SET muted_until=? WHERE id=?;",
|
||||
paramsv![duration, chat_id],
|
||||
)
|
||||
.await
|
||||
.context(format!("Failed to set mute duration for {}", chat_id))?;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
.is_ok()
|
||||
{
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
} else {
|
||||
bail!("Failed to set mute duration, chat might not exist -");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2481,9 +2489,12 @@ pub async fn remove_contact_from_chat(
|
||||
if let Ok(chat) = Chat::load_from_db(context, chat_id).await {
|
||||
if chat.typ == Chattype::Group {
|
||||
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
|
||||
context.emit_event(EventType::ErrorSelfNotInGroup(
|
||||
"Cannot remove contact from chat; self not in group.".into(),
|
||||
));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ErrorSelfNotInGroup(
|
||||
"Cannot remove contact from chat; self not in group.".into()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
|
||||
if chat.is_promoted() {
|
||||
@@ -2575,9 +2586,10 @@ pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -
|
||||
if chat.name == new_name {
|
||||
success = true;
|
||||
} else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
|
||||
context.emit_event(EventType::ErrorSelfNotInGroup(
|
||||
"Cannot set chat name; self not in group".into(),
|
||||
));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into())
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if context
|
||||
@@ -2641,9 +2653,12 @@ pub async fn set_chat_profile_image(
|
||||
);
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
|
||||
context.emit_event(EventType::ErrorSelfNotInGroup(
|
||||
"Cannot set chat profile image; self not in group.".into(),
|
||||
));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ErrorSelfNotInGroup(
|
||||
"Cannot set chat profile image; self not in group.".into()
|
||||
)
|
||||
);
|
||||
bail!("Failed to set profile image");
|
||||
}
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
@@ -2671,12 +2686,15 @@ pub async fn set_chat_profile_image(
|
||||
chat.update_param(context).await?;
|
||||
if chat.is_promoted() && !chat.is_mailing_list() {
|
||||
msg.id = send_msg(context, chat_id, &mut msg).await?;
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id
|
||||
}
|
||||
);
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
emit_event!(context, EventType::ChatModified(chat_id));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3234,9 +3252,7 @@ mod tests {
|
||||
assert!(chat.get_profile_image(&t).await.unwrap().is_some());
|
||||
|
||||
// delete device message, make sure it is not added again
|
||||
message::delete_msgs(&t, &[*msg1_id.as_ref().unwrap()])
|
||||
.await
|
||||
.unwrap();
|
||||
message::delete_msgs(&t, &[*msg1_id.as_ref().unwrap()]).await;
|
||||
let msg1 = message::Message::load_from_db(&t, *msg1_id.as_ref().unwrap()).await;
|
||||
assert!(msg1.is_err() || msg1.unwrap().chat_id.is_trash());
|
||||
let msg3_id = add_device_msg(&t, Some("any-label"), Some(&mut msg2)).await;
|
||||
|
||||
@@ -11,9 +11,9 @@ use crate::constants::{
|
||||
use crate::contact::Contact;
|
||||
use crate::context::Context;
|
||||
use crate::ephemeral::delete_expired_messages;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::{Message, MessageState, MsgId};
|
||||
use crate::stock_str;
|
||||
use crate::summary::Summary;
|
||||
|
||||
/// An object representing a single chatlist in memory.
|
||||
///
|
||||
@@ -288,13 +288,26 @@ impl Chatlist {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a summary for a given chatlist index.
|
||||
/// Get a summary for a chatlist index.
|
||||
///
|
||||
/// The summary is returned by a dc_lot_t object with the following fields:
|
||||
///
|
||||
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
|
||||
/// The string may be colored by having a look at text1_meaning.
|
||||
/// If there is no such name or it should not be displayed, the element is NULL.
|
||||
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
|
||||
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
|
||||
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
|
||||
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
|
||||
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
|
||||
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
|
||||
// 0 if not applicable.
|
||||
pub async fn get_summary(
|
||||
&self,
|
||||
context: &Context,
|
||||
index: usize,
|
||||
chat: Option<&Chat>,
|
||||
) -> Result<Summary> {
|
||||
) -> Result<Lot> {
|
||||
// The summary is created by the chat, not by the last message.
|
||||
// This is because we may want to display drafts here or stuff as
|
||||
// "is typing".
|
||||
@@ -307,13 +320,14 @@ impl Chatlist {
|
||||
Chatlist::get_summary2(context, *chat_id, *lastmsg_id, chat).await
|
||||
}
|
||||
|
||||
/// Returns a summary for a given chatlist item.
|
||||
pub async fn get_summary2(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
lastmsg_id: Option<MsgId>,
|
||||
chat: Option<&Chat>,
|
||||
) -> Result<Summary> {
|
||||
) -> Result<Lot> {
|
||||
let mut ret = Lot::new();
|
||||
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
@@ -330,8 +344,9 @@ impl Chatlist {
|
||||
} else {
|
||||
match chat.typ {
|
||||
Chattype::Group | Chattype::Mailinglist => {
|
||||
let lastcontact = Contact::load_from_db(context, lastmsg.from_id).await?;
|
||||
(Some(lastmsg), Some(lastcontact))
|
||||
let lastcontact =
|
||||
Contact::load_from_db(context, lastmsg.from_id).await.ok();
|
||||
(Some(lastmsg), lastcontact)
|
||||
}
|
||||
Chattype::Single | Chattype::Undefined => (Some(lastmsg), None),
|
||||
}
|
||||
@@ -341,15 +356,17 @@ impl Chatlist {
|
||||
};
|
||||
|
||||
if chat.id.is_archived_link() {
|
||||
Ok(Default::default())
|
||||
} else if let Some(lastmsg) = lastmsg.filter(|msg| msg.from_id != DC_CONTACT_ID_UNDEFINED) {
|
||||
Ok(Summary::new(context, &lastmsg, chat, lastcontact.as_ref()).await)
|
||||
ret.text2 = None;
|
||||
} else if let Some(mut lastmsg) =
|
||||
lastmsg.filter(|msg| msg.from_id != DC_CONTACT_ID_UNDEFINED)
|
||||
{
|
||||
ret.fill(&mut lastmsg, chat, lastcontact.as_ref(), context)
|
||||
.await;
|
||||
} else {
|
||||
Ok(Summary {
|
||||
text: stock_str::no_messages(context).await,
|
||||
..Default::default()
|
||||
})
|
||||
ret.text2 = Some(stock_str::no_messages(context).await);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn get_index_for_id(&self, id: ChatId) -> Option<usize> {
|
||||
@@ -620,6 +637,6 @@ mod tests {
|
||||
|
||||
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
|
||||
let summary = chats.get_summary(&t, 0, None).await.unwrap();
|
||||
assert_eq!(summary.text, "foo: bar test"); // the linebreak should be removed from summary
|
||||
assert_eq!(summary.get_text2().unwrap(), "foo: bar test"); // the linebreak should be removed from summary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,11 +156,6 @@ pub enum Config {
|
||||
#[strum(props(default = "0"))]
|
||||
NotifyAboutWrongPw,
|
||||
|
||||
/// If a warning about exceeding quota was shown recently,
|
||||
/// this is the percentage of quota at the time the warning was given.
|
||||
/// Unset, when quota falls below minimal warning threshold again.
|
||||
QuotaExceeding,
|
||||
|
||||
/// address to webrtc instance to use for videochats
|
||||
WebrtcInstance,
|
||||
|
||||
@@ -317,7 +312,7 @@ impl Context {
|
||||
.set_raw_config(key, value)
|
||||
.await
|
||||
.map_err(Into::into);
|
||||
job::schedule_resync(self).await?;
|
||||
job::schedule_resync(self).await;
|
||||
ret
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -319,9 +319,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
.filter(|params| params.protocol == Protocol::Smtp)
|
||||
.cloned()
|
||||
.collect();
|
||||
let provider_strict_tls = param
|
||||
.provider
|
||||
.map_or(socks5_config.is_some(), |provider| provider.strict_tls);
|
||||
let provider_strict_tls = param.provider.map_or(false, |provider| provider.strict_tls);
|
||||
|
||||
let smtp_config_task = task::spawn(async move {
|
||||
let mut smtp_configured = false;
|
||||
|
||||
@@ -1109,19 +1109,15 @@ impl Contact {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn scaleup_origin_by_id(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
origin: Origin,
|
||||
) -> Result<()> {
|
||||
pub async fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
|
||||
paramsv![origin, contact_id as i32, origin],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -423,12 +423,6 @@ impl Context {
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"quota_exceeding",
|
||||
self.get_config_int(Config::QuotaExceeding)
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let elapsed = self.creation_time.elapsed();
|
||||
res.insert("uptime", duration_to_str(elapsed.unwrap_or_default()));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use anyhow::{bail, ensure, format_err, Result};
|
||||
use itertools::join;
|
||||
use mailparse::SingleInfo;
|
||||
use num_traits::FromPrimitive;
|
||||
@@ -19,7 +19,7 @@ use crate::constants::{
|
||||
use crate::contact::{addr_cmp, normalize_name, Contact, Origin, VerifiedStatus};
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::{
|
||||
dc_create_smeared_timestamp, dc_extract_grpid_from_rfc724_mid, dc_smeared_time,
|
||||
dc_create_smeared_timestamp, dc_extract_grpid_from_rfc724_mid, dc_smeared_time, time,
|
||||
};
|
||||
use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
|
||||
use crate::events::EventType;
|
||||
@@ -210,38 +210,27 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
}
|
||||
|
||||
if let Some(avatar_action) = &mime_parser.user_avatar {
|
||||
if from_id != 0
|
||||
&& context
|
||||
.update_contacts_timestamp(from_id, Param::AvatarTimestamp, sent_timestamp)
|
||||
.await?
|
||||
match contact::set_profile_image(
|
||||
context,
|
||||
from_id,
|
||||
avatar_action,
|
||||
mime_parser.was_encrypted(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
match contact::set_profile_image(
|
||||
context,
|
||||
from_id,
|
||||
avatar_action,
|
||||
mime_parser.was_encrypted(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "receive_imf cannot update profile image: {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(()) => {
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "receive_imf cannot update profile image: {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Always update the status, even if there is no footer, to allow removing the status.
|
||||
//
|
||||
// Ignore MDNs though, as they never contain the signature even if user has set it.
|
||||
if mime_parser.mdn_reports.is_empty()
|
||||
&& from_id != 0
|
||||
&& context
|
||||
.update_contacts_timestamp(from_id, Param::StatusTimestamp, sent_timestamp)
|
||||
.await?
|
||||
{
|
||||
if mime_parser.mdn_reports.is_empty() {
|
||||
if let Err(err) = contact::set_status(
|
||||
context,
|
||||
from_id,
|
||||
@@ -421,9 +410,6 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
let rcvd_timestamp = dc_smeared_time(context).await;
|
||||
*sent_timestamp = std::cmp::min(*sent_timestamp, rcvd_timestamp);
|
||||
|
||||
// check if the message introduces a new chat:
|
||||
// - outgoing messages introduce a chat with the first to: address if they are sent by a messenger
|
||||
// - incoming messages introduce a chat only for known contacts if they are sent by a messenger
|
||||
@@ -497,7 +483,6 @@ async fn add_parts(
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
*sent_timestamp,
|
||||
if test_normal_chat.is_none() {
|
||||
allow_creation
|
||||
} else {
|
||||
@@ -618,7 +603,7 @@ async fn add_parts(
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
// the contact requests will pop up and this should be just fine.
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo)
|
||||
.await?;
|
||||
.await;
|
||||
info!(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
@@ -727,7 +712,6 @@ async fn add_parts(
|
||||
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
|
||||
context,
|
||||
&mut mime_parser,
|
||||
*sent_timestamp,
|
||||
allow_creation,
|
||||
Blocked::Not,
|
||||
from_id,
|
||||
@@ -821,8 +805,9 @@ async fn add_parts(
|
||||
let location_kml_is = mime_parser.location_kml.is_some();
|
||||
|
||||
// correct message_timestamp, it should not be used before,
|
||||
// however, we cannot do this earlier as we need chat_id to be set
|
||||
// however, we cannot do this earlier as we need from_id to be set
|
||||
let in_fresh = state == MessageState::InFresh;
|
||||
let rcvd_timestamp = time();
|
||||
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
|
||||
|
||||
// Apply ephemeral timer changes to the chat.
|
||||
@@ -838,9 +823,6 @@ async fn add_parts(
|
||||
|| parent.is_none()
|
||||
|| parent.unwrap().ephemeral_timer != ephemeral_timer)
|
||||
&& chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
|
||||
&& chat_id
|
||||
.update_timestamp(context, Param::EphemeralSettingsTimestamp, *sent_timestamp)
|
||||
.await?
|
||||
{
|
||||
if let Err(err) = chat_id
|
||||
.inner_set_ephemeral_timer(context, ephemeral_timer)
|
||||
@@ -886,10 +868,6 @@ async fn add_parts(
|
||||
};
|
||||
|
||||
if chat.is_protected() || new_status.is_some() {
|
||||
// TODO: on partial downloads, do not try to check verified properties,
|
||||
// eg. the Chat-Verified header is in the encrypted part and we would always get warnings.
|
||||
// however, this seems not to be a big deal as we even show "failed verifications" messages in-chat -
|
||||
// nothing else is done for "not yet downloaded content".
|
||||
if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await
|
||||
{
|
||||
warn!(context, "verification problem: {}", err);
|
||||
@@ -898,24 +876,15 @@ async fn add_parts(
|
||||
} else {
|
||||
// change chat protection only when verification check passes
|
||||
if let Some(new_status) = new_status {
|
||||
if chat_id
|
||||
.update_timestamp(
|
||||
if let Err(e) = chat_id.inner_set_protection(context, new_status).await {
|
||||
chat::add_info_msg(
|
||||
context,
|
||||
Param::ProtectionSettingsTimestamp,
|
||||
*sent_timestamp,
|
||||
chat_id,
|
||||
format!("Cannot set protection: {}", e),
|
||||
sort_timestamp,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
if let Err(e) = chat_id.inner_set_protection(context, new_status).await {
|
||||
chat::add_info_msg(
|
||||
context,
|
||||
chat_id,
|
||||
format!("Cannot set protection: {}", e),
|
||||
sort_timestamp,
|
||||
)
|
||||
.await;
|
||||
return Ok(chat_id); // do not return an error as this would result in retrying the message
|
||||
}
|
||||
.await;
|
||||
return Ok(chat_id); // do not return an error as this would result in retrying the message
|
||||
}
|
||||
set_better_msg(
|
||||
mime_parser,
|
||||
@@ -941,6 +910,8 @@ async fn add_parts(
|
||||
std::cmp::max(sort_timestamp, parent_timestamp)
|
||||
});
|
||||
|
||||
*sent_timestamp = std::cmp::min(*sent_timestamp, rcvd_timestamp);
|
||||
|
||||
// if the mime-headers should be saved, find out its size
|
||||
// (the mime-header ends with an empty line)
|
||||
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await?;
|
||||
@@ -1046,9 +1017,7 @@ INSERT INTO msgs
|
||||
} else {
|
||||
match ephemeral_timer {
|
||||
EphemeralTimer::Disabled => 0,
|
||||
EphemeralTimer::Enabled { duration } => {
|
||||
rcvd_timestamp.saturating_add(duration.into())
|
||||
}
|
||||
EphemeralTimer::Enabled { duration } => rcvd_timestamp + i64::from(duration),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1128,23 +1097,25 @@ INSERT INTO msgs
|
||||
}
|
||||
}
|
||||
|
||||
if !is_mdn {
|
||||
async fn update_last_subject(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
mime_parser: &MimeMessage,
|
||||
) -> Result<()> {
|
||||
let mut chat = Chat::load_from_db(context, chat_id).await?;
|
||||
|
||||
// In contrast to most other update-timestamps,
|
||||
// use `sort_timestamp` instead of `sent_timestamp` for the subject-timestamp comparison.
|
||||
// This way, `LastSubject` actually refers to the most recent message _shown_ in the chat.
|
||||
if chat
|
||||
.param
|
||||
.update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
|
||||
{
|
||||
// write the last subject even if empty -
|
||||
// otherwise a reply may get an outdated subject.
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
|
||||
chat.param.set(Param::LastSubject, subject);
|
||||
chat.update_param(context).await?;
|
||||
}
|
||||
chat.param.set(
|
||||
Param::LastSubject,
|
||||
mime_parser
|
||||
.get_subject()
|
||||
.ok_or_else(|| format_err!("No subject in email"))?,
|
||||
);
|
||||
chat.update_param(context).await?;
|
||||
Ok(())
|
||||
}
|
||||
if !is_mdn {
|
||||
update_last_subject(context, chat_id, mime_parser)
|
||||
.await
|
||||
.ok_or_log_msg(context, "Could not update LastSubject of chat");
|
||||
}
|
||||
|
||||
Ok(chat_id)
|
||||
@@ -1343,7 +1314,6 @@ async fn is_probably_private_reply(
|
||||
async fn create_or_lookup_group(
|
||||
context: &Context,
|
||||
mime_parser: &mut MimeMessage,
|
||||
sent_timestamp: i64,
|
||||
allow_creation: bool,
|
||||
create_blocked: Blocked,
|
||||
from_id: u32,
|
||||
@@ -1353,6 +1323,7 @@ async fn create_or_lookup_group(
|
||||
let mut recreate_member_list = false;
|
||||
let mut send_EVENT_CHAT_MODIFIED = false;
|
||||
let mut X_MrAddToGrp = None;
|
||||
let mut X_MrGrpNameChanged = false;
|
||||
let mut better_msg: String = From::from("");
|
||||
|
||||
if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled {
|
||||
@@ -1430,6 +1401,7 @@ async fn create_or_lookup_group(
|
||||
better_msg = stock_str::msg_add_member(context, &added_member, from_id).await;
|
||||
X_MrAddToGrp = Some(added_member);
|
||||
} else if let Some(old_name) = mime_parser.get_header(HeaderDef::ChatGroupNameChanged) {
|
||||
X_MrGrpNameChanged = true;
|
||||
better_msg = stock_str::msg_grp_name(
|
||||
context,
|
||||
old_name,
|
||||
@@ -1559,16 +1531,9 @@ async fn create_or_lookup_group(
|
||||
// execute group commands
|
||||
if X_MrAddToGrp.is_some() {
|
||||
recreate_member_list = true;
|
||||
} else if mime_parser
|
||||
.get_header(HeaderDef::ChatGroupNameChanged)
|
||||
.is_some()
|
||||
{
|
||||
} else if X_MrGrpNameChanged {
|
||||
if let Some(ref grpname) = grpname {
|
||||
if grpname.len() < 200
|
||||
&& chat_id
|
||||
.update_timestamp(context, Param::GroupNameTimestamp, sent_timestamp)
|
||||
.await?
|
||||
{
|
||||
if grpname.len() < 200 {
|
||||
info!(context, "updating grpname for chat {}", chat_id);
|
||||
if context
|
||||
.sql
|
||||
@@ -1590,69 +1555,54 @@ async fn create_or_lookup_group(
|
||||
if let Some(avatar_action) = &mime_parser.group_avatar {
|
||||
info!(context, "group-avatar change for {}", chat_id);
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await {
|
||||
if chat
|
||||
.param
|
||||
.update_timestamp(Param::AvatarTimestamp, sent_timestamp)?
|
||||
{
|
||||
match avatar_action {
|
||||
AvatarAction::Change(profile_image) => {
|
||||
chat.param.set(Param::ProfileImage, profile_image);
|
||||
}
|
||||
AvatarAction::Delete => {
|
||||
chat.param.remove(Param::ProfileImage);
|
||||
}
|
||||
};
|
||||
chat.update_param(context).await?;
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
}
|
||||
match avatar_action {
|
||||
AvatarAction::Change(profile_image) => {
|
||||
chat.param.set(Param::ProfileImage, profile_image);
|
||||
}
|
||||
AvatarAction::Delete => {
|
||||
chat.param.remove(Param::ProfileImage);
|
||||
}
|
||||
};
|
||||
chat.update_param(context).await?;
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add members to group/check members
|
||||
if recreate_member_list {
|
||||
if chat_id
|
||||
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
|
||||
.await?
|
||||
{
|
||||
if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
|
||||
// Members could have been removed while we were
|
||||
// absent. We can't use existing member list and need to
|
||||
// start from scratch.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM chats_contacts WHERE chat_id=?;",
|
||||
paramsv![chat_id],
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
|
||||
// Members could have been removed while we were
|
||||
// absent. We can't use existing member list and need to
|
||||
// start from scratch.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM chats_contacts WHERE chat_id=?;",
|
||||
paramsv![chat_id],
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
|
||||
}
|
||||
if from_id > DC_CONTACT_ID_LAST_SPECIAL
|
||||
&& !Contact::addr_equals_contact(context, &self_addr, from_id).await
|
||||
&& !chat::is_contact_in_chat(context, chat_id, from_id).await
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, from_id).await;
|
||||
}
|
||||
for &to_id in to_ids.iter() {
|
||||
info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
|
||||
if !Contact::addr_equals_contact(context, &self_addr, to_id).await
|
||||
&& !chat::is_contact_in_chat(context, chat_id, to_id).await
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, to_id).await;
|
||||
}
|
||||
}
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
|
||||
}
|
||||
} else if let Some(contact_id) = removed_id {
|
||||
if chat_id
|
||||
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
|
||||
.await?
|
||||
if from_id > DC_CONTACT_ID_LAST_SPECIAL
|
||||
&& !Contact::addr_equals_contact(context, &self_addr, from_id).await
|
||||
&& !chat::is_contact_in_chat(context, chat_id, from_id).await
|
||||
{
|
||||
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await;
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
chat::add_to_chat_contacts_table(context, chat_id, from_id).await;
|
||||
}
|
||||
for &to_id in to_ids.iter() {
|
||||
info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
|
||||
if !Contact::addr_equals_contact(context, &self_addr, to_id).await
|
||||
&& !chat::is_contact_in_chat(context, chat_id, to_id).await
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, to_id).await;
|
||||
}
|
||||
}
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
} else if let Some(contact_id) = removed_id {
|
||||
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await;
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
}
|
||||
|
||||
if send_EVENT_CHAT_MODIFIED {
|
||||
@@ -1715,17 +1665,14 @@ async fn create_or_lookup_mailinglist(
|
||||
}
|
||||
|
||||
// if we do not have a name yet and `From` indicates, that this is a notification list,
|
||||
// a usable name is often in the `From` header (seen for several parcel service notifications).
|
||||
// same, if we do not have a name yet and `List-Id` has a known suffix (`.xt.local`)
|
||||
//
|
||||
// this pattern is similar to mailchimp above, however,
|
||||
// with weaker conditions and does not overwrite existing names.
|
||||
// a usable name is often in the `From` header.
|
||||
// this pattern was seen for parcel service notifications
|
||||
// and is similar to mailchimp above, however, with weaker conditions.
|
||||
if name.is_empty() {
|
||||
if let Some(from) = mime_parser.from.first() {
|
||||
if from.addr.contains("noreply")
|
||||
|| from.addr.contains("no-reply")
|
||||
|| from.addr.starts_with("notifications@")
|
||||
|| listid.ends_with(".xt.local")
|
||||
{
|
||||
if let Some(display_name) = &from.display_name {
|
||||
name = display_name.clone();
|
||||
@@ -2792,18 +2739,6 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_testrun_2() {
|
||||
test_parse_ndn(
|
||||
"alice@example.org",
|
||||
"bob@example.org",
|
||||
"Mr.5xqflwt0YFv.IXDFfHauvWx@testrun.org",
|
||||
include_bytes!("../test-data/message/testrun_ndn_2.eml"),
|
||||
Some("Undelivered Mail Returned to Sender – This is the mail system at host hq5.merlinux.eu.\n\nI'm sorry to have to inform you that your message could not\nbe delivered to one or more recipients. It's attached below.\n\nFor further assistance, please send mail to postmaster.\n\nIf you do so, please include this problem report. You can\ndelete your own text from the attached returned message.\n\n The mail system\n\n<bob@example.org>: Host or domain name not found. Name service error for\n name=echedelyr.tk type=AAAA: Host not found"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// ndn = Non Delivery Notification
|
||||
async fn test_parse_ndn(
|
||||
self_addr: &str,
|
||||
@@ -3338,40 +3273,6 @@ mod tests {
|
||||
assert_eq!(chat.name, "DPD");
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_xt_local_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_xt_local_microsoft.eml"),
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let chat = Chat::load_from_db(&t, t.get_last_msg().await.chat_id).await?;
|
||||
assert_eq!(chat.typ, Chattype::Mailinglist);
|
||||
assert_eq!(chat.grpid, "96540.xt.local");
|
||||
assert_eq!(chat.name, "Microsoft Store");
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_xt_local_spiegel.eml"),
|
||||
"INBOX",
|
||||
2,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let chat = Chat::load_from_db(&t, t.get_last_msg().await.chat_id).await?;
|
||||
assert_eq!(chat.typ, Chattype::Mailinglist);
|
||||
assert_eq!(chat.grpid, "121231234.xt.local");
|
||||
assert_eq!(chat.name, "DER SPIEGEL Kundenservice");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_mailing_list_with_mimepart_footer() {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -279,7 +279,7 @@ impl MsgId {
|
||||
/// Starts ephemeral message timer for the message if it is not started yet.
|
||||
pub(crate) async fn start_ephemeral_timer(self, context: &Context) -> anyhow::Result<()> {
|
||||
if let Timer::Enabled { duration } = self.ephemeral_timer(context).await? {
|
||||
let ephemeral_timestamp = time().saturating_add(duration.into());
|
||||
let ephemeral_timestamp = time() + i64::from(duration);
|
||||
|
||||
context
|
||||
.sql
|
||||
@@ -416,18 +416,24 @@ pub async fn schedule_ephemeral_task(context: &Context) {
|
||||
let context1 = context.clone();
|
||||
let ephemeral_task = task::spawn(async move {
|
||||
async_std::task::sleep(duration).await;
|
||||
context1.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
emit_event!(
|
||||
context1,
|
||||
EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0)
|
||||
}
|
||||
);
|
||||
});
|
||||
*context.ephemeral_task.write().await = Some(ephemeral_task);
|
||||
} else {
|
||||
// Emit event immediately
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,6 @@ impl Events {
|
||||
pub struct EventEmitter(Receiver<Event>);
|
||||
|
||||
impl EventEmitter {
|
||||
pub(crate) fn into_inner(self) -> Receiver<Event> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Blocking recv of an event. Return `None` if the `Sender` has been droped.
|
||||
pub fn recv_sync(&self) -> Option<Event> {
|
||||
async_std::task::block_on(self.recv())
|
||||
|
||||
296
src/imap.rs
296
src/imap.rs
@@ -6,8 +6,9 @@
|
||||
use std::{cmp, cmp::max, collections::BTreeMap};
|
||||
|
||||
use anyhow::{anyhow, bail, format_err, Context as _, Result};
|
||||
use async_imap::types::{
|
||||
Fetch, Flag, Mailbox, Name, NameAttribute, Quota, QuotaRoot, UnsolicitedResponse,
|
||||
use async_imap::{
|
||||
error::Result as ImapResult,
|
||||
types::{Fetch, Flag, Mailbox, Name, NameAttribute, Quota, QuotaRoot, UnsolicitedResponse},
|
||||
};
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::prelude::*;
|
||||
@@ -88,6 +89,7 @@ pub struct Imap {
|
||||
idle_interrupt: Receiver<InterruptInfo>,
|
||||
config: ImapConfig,
|
||||
session: Option<Session>,
|
||||
connected: bool,
|
||||
interrupt: Option<stop_token::StopSource>,
|
||||
should_reconnect: bool,
|
||||
login_failed_once: bool,
|
||||
@@ -199,6 +201,7 @@ impl Imap {
|
||||
idle_interrupt,
|
||||
config,
|
||||
session: None,
|
||||
connected: false,
|
||||
interrupt: None,
|
||||
should_reconnect: false,
|
||||
login_failed_once: false,
|
||||
@@ -226,11 +229,7 @@ impl Imap {
|
||||
param.socks5_config.clone(),
|
||||
¶m.addr,
|
||||
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
param
|
||||
.provider
|
||||
.map_or(param.socks5_config.is_some(), |provider| {
|
||||
provider.strict_tls
|
||||
}),
|
||||
param.provider.map_or(false, |provider| provider.strict_tls),
|
||||
idle_interrupt,
|
||||
)
|
||||
.await?;
|
||||
@@ -252,7 +251,7 @@ impl Imap {
|
||||
if self.should_reconnect() {
|
||||
self.disconnect(context).await;
|
||||
self.should_reconnect = false;
|
||||
} else if self.session.is_some() {
|
||||
} else if self.is_connected() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -260,7 +259,7 @@ impl Imap {
|
||||
|
||||
let oauth2 = self.config.oauth2;
|
||||
|
||||
let connection_res: Result<Client> = if self.config.lp.security == Socket::Starttls
|
||||
let connection_res: ImapResult<Client> = if self.config.lp.security == Socket::Starttls
|
||||
|| self.config.lp.security == Socket::Plain
|
||||
{
|
||||
let config = &mut self.config;
|
||||
@@ -345,16 +344,17 @@ impl Imap {
|
||||
match login_res {
|
||||
Ok(session) => {
|
||||
// needs to be set here to ensure it is set on reconnects.
|
||||
self.connected = true;
|
||||
self.session = Some(session);
|
||||
self.login_failed_once = false;
|
||||
context.emit_event(EventType::ImapConnected(format!(
|
||||
"IMAP-LOGIN as {}",
|
||||
self.config.lp.user
|
||||
)));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ImapConnected(format!("IMAP-LOGIN as {}", self.config.lp.user))
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
Err((err, _)) => {
|
||||
let imap_user = self.config.lp.user.to_owned();
|
||||
let message = stock_str::cannot_login(context, &imap_user).await;
|
||||
|
||||
@@ -439,11 +439,16 @@ impl Imap {
|
||||
warn!(context, "failed to logout: {:?}", err);
|
||||
}
|
||||
}
|
||||
self.connected = false;
|
||||
self.capabilities_determined = false;
|
||||
self.config.selected_folder = None;
|
||||
self.config.selected_mailbox = None;
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
pub fn should_reconnect(&self) -> bool {
|
||||
self.should_reconnect
|
||||
}
|
||||
@@ -579,7 +584,7 @@ impl Imap {
|
||||
folder, old_uid_next, uid_next, new_uid_validity,
|
||||
);
|
||||
set_uid_next(context, folder, uid_next).await?;
|
||||
job::schedule_resync(context).await?;
|
||||
job::schedule_resync(context).await;
|
||||
}
|
||||
uid_next != old_uid_next // If uid_next changed, there are new emails
|
||||
} else {
|
||||
@@ -632,7 +637,7 @@ impl Imap {
|
||||
set_uid_next(context, folder, new_uid_next).await?;
|
||||
set_uidvalidity(context, folder, new_uid_validity).await?;
|
||||
if old_uid_validity != 0 || old_uid_next != 0 {
|
||||
job::schedule_resync(context).await?;
|
||||
job::schedule_resync(context).await;
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
@@ -874,13 +879,19 @@ impl Imap {
|
||||
return (None, 0);
|
||||
}
|
||||
|
||||
let session = match self.session.as_mut() {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
warn!(context, "Not connected");
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
};
|
||||
if !self.is_connected() {
|
||||
warn!(context, "Not connected");
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
|
||||
if self.session.is_none() {
|
||||
// we could not get a valid imap session, this should be retried
|
||||
self.trigger_reconnect(context).await;
|
||||
warn!(context, "Could not get IMAP session");
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
|
||||
let session = self.session.as_mut().unwrap();
|
||||
|
||||
let sets = build_sequence_sets(server_uids.clone());
|
||||
let mut read_errors = 0;
|
||||
@@ -1001,10 +1012,13 @@ impl Imap {
|
||||
if let Some(ref mut session) = &mut self.session {
|
||||
match session.uid_mv(&set, &dest_folder).await {
|
||||
Ok(_) => {
|
||||
context.emit_event(EventType::ImapMessageMoved(format!(
|
||||
"IMAP Message {} moved to {}",
|
||||
display_folder_id, dest_folder
|
||||
)));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ImapMessageMoved(format!(
|
||||
"IMAP Message {} moved to {}",
|
||||
display_folder_id, dest_folder
|
||||
))
|
||||
);
|
||||
return ImapActionResult::Success;
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1042,17 +1056,23 @@ impl Imap {
|
||||
|
||||
if !self.add_flag_finalized(context, uid, "\\Deleted").await {
|
||||
warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid);
|
||||
context.emit_event(EventType::ImapMessageMoved(format!(
|
||||
"IMAP Message {} copied to {} (delete FAILED)",
|
||||
display_folder_id, dest_folder
|
||||
)));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ImapMessageMoved(format!(
|
||||
"IMAP Message {} copied to {} (delete FAILED)",
|
||||
display_folder_id, dest_folder
|
||||
))
|
||||
);
|
||||
ImapActionResult::Failed
|
||||
} else {
|
||||
self.config.selected_folder_needs_expunge = true;
|
||||
context.emit_event(EventType::ImapMessageMoved(format!(
|
||||
"IMAP Message {} copied to {} (delete successfull)",
|
||||
display_folder_id, dest_folder
|
||||
)));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ImapMessageMoved(format!(
|
||||
"IMAP Message {} copied to {} (delete successfull)",
|
||||
display_folder_id, dest_folder
|
||||
))
|
||||
);
|
||||
ImapActionResult::Success
|
||||
}
|
||||
}
|
||||
@@ -1109,7 +1129,7 @@ impl Imap {
|
||||
if uid == 0 {
|
||||
return Some(ImapActionResult::RetryLater);
|
||||
}
|
||||
if self.session.is_none() {
|
||||
if !self.is_connected() {
|
||||
// currently jobs are only performed on the INBOX thread
|
||||
// TODO: make INBOX/SENT/MVBOX perform the jobs on their
|
||||
// respective folders to avoid select_folder network traffic
|
||||
@@ -1246,10 +1266,13 @@ impl Imap {
|
||||
);
|
||||
ImapActionResult::RetryLater
|
||||
} else {
|
||||
context.emit_event(EventType::ImapMessageDeleted(format!(
|
||||
"IMAP Message {} marked as deleted [{}]",
|
||||
display_imap_id, message_id
|
||||
)));
|
||||
emit_event!(
|
||||
context,
|
||||
EventType::ImapMessageDeleted(format!(
|
||||
"IMAP Message {} marked as deleted [{}]",
|
||||
display_imap_id, message_id
|
||||
))
|
||||
);
|
||||
self.config.selected_folder_needs_expunge = true;
|
||||
ImapActionResult::Success
|
||||
}
|
||||
@@ -1269,114 +1292,115 @@ impl Imap {
|
||||
}
|
||||
|
||||
pub async fn configure_folders(&mut self, context: &Context, create_mvbox: bool) -> Result<()> {
|
||||
let session = match self.session {
|
||||
Some(ref mut session) => session,
|
||||
None => bail!("no IMAP connection established"),
|
||||
};
|
||||
|
||||
let mut folders = match session.list(Some(""), Some("*")).await {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
bail!("list_folders failed: {}", err);
|
||||
}
|
||||
};
|
||||
|
||||
let mut delimiter = ".".to_string();
|
||||
let mut delimiter_is_default = true;
|
||||
let mut mvbox_folder = None;
|
||||
let mut folder_configs = BTreeMap::new();
|
||||
let mut fallback_folder = get_fallback_folder(&delimiter);
|
||||
|
||||
while let Some(folder) = folders.next().await {
|
||||
let folder = folder?;
|
||||
info!(context, "Scanning folder: {:?}", folder);
|
||||
|
||||
// Update the delimiter iff there is a different one, but only once.
|
||||
if let Some(d) = folder.delimiter() {
|
||||
if delimiter_is_default && !d.is_empty() && delimiter != d {
|
||||
delimiter = d.to_string();
|
||||
fallback_folder = get_fallback_folder(&delimiter);
|
||||
delimiter_is_default = false;
|
||||
}
|
||||
}
|
||||
|
||||
let folder_meaning = get_folder_meaning(&folder);
|
||||
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
|
||||
if folder.name() == "DeltaChat" {
|
||||
// Always takes precedence
|
||||
mvbox_folder = Some(folder.name().to_string());
|
||||
} else if folder.name() == fallback_folder {
|
||||
// only set if none has been already set
|
||||
if mvbox_folder.is_none() {
|
||||
mvbox_folder = Some(folder.name().to_string());
|
||||
}
|
||||
} else if let Some(config) = folder_meaning.to_config() {
|
||||
// Always takes precedence
|
||||
folder_configs.insert(config, folder.name().to_string());
|
||||
} else if let Some(config) = folder_name_meaning.to_config() {
|
||||
// only set if none has been already set
|
||||
folder_configs
|
||||
.entry(config)
|
||||
.or_insert_with(|| folder.name().to_string());
|
||||
}
|
||||
if !self.is_connected() {
|
||||
bail!("IMAP No Connection established");
|
||||
}
|
||||
drop(folders);
|
||||
|
||||
info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
|
||||
|
||||
if mvbox_folder.is_none() && create_mvbox {
|
||||
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
|
||||
|
||||
match session.create("DeltaChat").await {
|
||||
Ok(_) => {
|
||||
mvbox_folder = Some("DeltaChat".into());
|
||||
info!(context, "MVBOX-folder created.",);
|
||||
}
|
||||
if let Some(ref mut session) = &mut self.session {
|
||||
let mut folders = match session.list(Some(""), Some("*")).await {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot create MVBOX-folder, trying to create INBOX subfolder. ({})", err
|
||||
);
|
||||
bail!("list_folders failed: {}", err);
|
||||
}
|
||||
};
|
||||
|
||||
match session.create(&fallback_folder).await {
|
||||
Ok(_) => {
|
||||
mvbox_folder = Some(fallback_folder);
|
||||
info!(
|
||||
context,
|
||||
"MVBOX-folder created as INBOX subfolder. ({})", err
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Cannot create MVBOX-folder. ({})", err);
|
||||
let mut delimiter = ".".to_string();
|
||||
let mut delimiter_is_default = true;
|
||||
let mut mvbox_folder = None;
|
||||
let mut folder_configs = BTreeMap::new();
|
||||
let mut fallback_folder = get_fallback_folder(&delimiter);
|
||||
|
||||
while let Some(folder) = folders.next().await {
|
||||
let folder = folder?;
|
||||
info!(context, "Scanning folder: {:?}", folder);
|
||||
|
||||
// Update the delimiter iff there is a different one, but only once.
|
||||
if let Some(d) = folder.delimiter() {
|
||||
if delimiter_is_default && !d.is_empty() && delimiter != d {
|
||||
delimiter = d.to_string();
|
||||
fallback_folder = get_fallback_folder(&delimiter);
|
||||
delimiter_is_default = false;
|
||||
}
|
||||
}
|
||||
|
||||
let folder_meaning = get_folder_meaning(&folder);
|
||||
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
|
||||
if folder.name() == "DeltaChat" {
|
||||
// Always takes precedence
|
||||
mvbox_folder = Some(folder.name().to_string());
|
||||
} else if folder.name() == fallback_folder {
|
||||
// only set if none has been already set
|
||||
if mvbox_folder.is_none() {
|
||||
mvbox_folder = Some(folder.name().to_string());
|
||||
}
|
||||
} else if let Some(config) = folder_meaning.to_config() {
|
||||
// Always takes precedence
|
||||
folder_configs.insert(config, folder.name().to_string());
|
||||
} else if let Some(config) = folder_name_meaning.to_config() {
|
||||
// only set if none has been already set
|
||||
folder_configs
|
||||
.entry(config)
|
||||
.or_insert_with(|| folder.name().to_string());
|
||||
}
|
||||
}
|
||||
drop(folders);
|
||||
|
||||
info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
|
||||
|
||||
if mvbox_folder.is_none() && create_mvbox {
|
||||
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
|
||||
|
||||
match session.create("DeltaChat").await {
|
||||
Ok(_) => {
|
||||
mvbox_folder = Some("DeltaChat".into());
|
||||
info!(context, "MVBOX-folder created.",);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot create MVBOX-folder, trying to create INBOX subfolder. ({})",
|
||||
err
|
||||
);
|
||||
|
||||
match session.create(&fallback_folder).await {
|
||||
Ok(_) => {
|
||||
mvbox_folder = Some(fallback_folder);
|
||||
info!(
|
||||
context,
|
||||
"MVBOX-folder created as INBOX subfolder. ({})", err
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Cannot create MVBOX-folder. ({})", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// SUBSCRIBE is needed to make the folder visible to the LSUB command
|
||||
// that may be used by other MUAs to list folders.
|
||||
// for the LIST command, the folder is always visible.
|
||||
if let Some(ref mvbox) = mvbox_folder {
|
||||
if let Err(err) = session.subscribe(mvbox).await {
|
||||
warn!(context, "could not subscribe to {:?}: {:?}", mvbox, err);
|
||||
// SUBSCRIBE is needed to make the folder visible to the LSUB command
|
||||
// that may be used by other MUAs to list folders.
|
||||
// for the LIST command, the folder is always visible.
|
||||
if let Some(ref mvbox) = mvbox_folder {
|
||||
if let Err(err) = session.subscribe(mvbox).await {
|
||||
warn!(context, "could not subscribe to {:?}: {:?}", mvbox, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
context
|
||||
.set_config(Config::ConfiguredInboxFolder, Some("INBOX"))
|
||||
.await?;
|
||||
if let Some(ref mvbox_folder) = mvbox_folder {
|
||||
context
|
||||
.set_config(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
|
||||
.set_config(Config::ConfiguredInboxFolder, Some("INBOX"))
|
||||
.await?;
|
||||
if let Some(ref mvbox_folder) = mvbox_folder {
|
||||
context
|
||||
.set_config(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
|
||||
.await?;
|
||||
}
|
||||
for (config, name) in folder_configs {
|
||||
context.set_config(config, Some(&name)).await?;
|
||||
}
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int("folders_configured", DC_FOLDERS_CONFIGURED_VERSION)
|
||||
.await?;
|
||||
}
|
||||
for (config, name) in folder_configs {
|
||||
context.set_config(config, Some(&name)).await?;
|
||||
}
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int("folders_configured", DC_FOLDERS_CONFIGURED_VERSION)
|
||||
.await?;
|
||||
|
||||
info!(context, "FINISHED configuring IMAP-folders.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use async_imap::Client as ImapClient;
|
||||
use async_imap::{
|
||||
error::{Error as ImapError, Result as ImapResult},
|
||||
Client as ImapClient,
|
||||
};
|
||||
|
||||
use async_smtp::ServerAddress;
|
||||
use async_std::net::{self, TcpStream};
|
||||
@@ -39,12 +40,24 @@ impl DerefMut for Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub async fn login(self, username: &str, password: &str) -> Result<Session> {
|
||||
let Client { inner, .. } = self;
|
||||
pub async fn login(
|
||||
self,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> std::result::Result<Session, (ImapError, Self)> {
|
||||
let Client { inner, is_secure } = self;
|
||||
let session = inner
|
||||
.login(username, password)
|
||||
.await
|
||||
.map_err(|(err, _client)| err)?;
|
||||
.map_err(|(err, client)| {
|
||||
(
|
||||
err,
|
||||
Client {
|
||||
is_secure,
|
||||
inner: client,
|
||||
},
|
||||
)
|
||||
})?;
|
||||
Ok(Session { inner: session })
|
||||
}
|
||||
|
||||
@@ -52,12 +65,21 @@ impl Client {
|
||||
self,
|
||||
auth_type: &str,
|
||||
authenticator: impl async_imap::Authenticator,
|
||||
) -> Result<Session> {
|
||||
let Client { inner, .. } = self;
|
||||
let session = inner
|
||||
.authenticate(auth_type, authenticator)
|
||||
.await
|
||||
.map_err(|(err, _client)| err)?;
|
||||
) -> std::result::Result<Session, (ImapError, Self)> {
|
||||
let Client { inner, is_secure } = self;
|
||||
let session =
|
||||
inner
|
||||
.authenticate(auth_type, authenticator)
|
||||
.await
|
||||
.map_err(|(err, client)| {
|
||||
(
|
||||
err,
|
||||
Client {
|
||||
is_secure,
|
||||
inner: client,
|
||||
},
|
||||
)
|
||||
})?;
|
||||
Ok(Session { inner: session })
|
||||
}
|
||||
|
||||
@@ -65,7 +87,7 @@ impl Client {
|
||||
addr: impl net::ToSocketAddrs,
|
||||
domain: &str,
|
||||
strict_tls: bool,
|
||||
) -> Result<Self> {
|
||||
) -> ImapResult<Self> {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let tls = dc_build_tls(strict_tls);
|
||||
let tls_stream: Box<dyn SessionStream> = Box::new(tls.connect(domain, stream).await?);
|
||||
@@ -74,7 +96,7 @@ impl Client {
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.context("failed to read greeting")?;
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client {
|
||||
is_secure: true,
|
||||
@@ -82,14 +104,14 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn connect_insecure(addr: impl net::ToSocketAddrs) -> Result<Self> {
|
||||
pub async fn connect_insecure(addr: impl net::ToSocketAddrs) -> ImapResult<Self> {
|
||||
let stream: Box<dyn SessionStream> = Box::new(TcpStream::connect(addr).await?);
|
||||
|
||||
let mut client = ImapClient::new(stream);
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.context("failed to read greeting")?;
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client {
|
||||
is_secure: false,
|
||||
@@ -101,11 +123,15 @@ impl Client {
|
||||
target_addr: &ServerAddress,
|
||||
strict_tls: bool,
|
||||
socks5_config: Socks5Config,
|
||||
) -> Result<Self> {
|
||||
) -> ImapResult<Self> {
|
||||
let socks5_stream: Box<dyn SessionStream> = Box::new(
|
||||
socks5_config
|
||||
match socks5_config
|
||||
.connect(target_addr, Some(Duration::from_secs(IMAP_TIMEOUT)))
|
||||
.await?,
|
||||
.await
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(e) => return ImapResult::Err(async_imap::error::Error::Bad(e.to_string())),
|
||||
},
|
||||
);
|
||||
|
||||
let tls = dc_build_tls(strict_tls);
|
||||
@@ -116,7 +142,7 @@ impl Client {
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.context("failed to read greeting")?;
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client {
|
||||
is_secure: true,
|
||||
@@ -127,18 +153,22 @@ impl Client {
|
||||
pub async fn connect_insecure_socks5(
|
||||
target_addr: &ServerAddress,
|
||||
socks5_config: Socks5Config,
|
||||
) -> Result<Self> {
|
||||
) -> ImapResult<Self> {
|
||||
let socks5_stream: Box<dyn SessionStream> = Box::new(
|
||||
socks5_config
|
||||
match socks5_config
|
||||
.connect(target_addr, Some(Duration::from_secs(IMAP_TIMEOUT)))
|
||||
.await?,
|
||||
.await
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(e) => return ImapResult::Err(async_imap::error::Error::Bad(e.to_string())),
|
||||
},
|
||||
);
|
||||
|
||||
let mut client = ImapClient::new(socks5_stream);
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.context("failed to read greeting")?;
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
|
||||
Ok(Client {
|
||||
is_secure: false,
|
||||
@@ -146,7 +176,7 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn secure(self, domain: &str, strict_tls: bool) -> Result<Client> {
|
||||
pub async fn secure(self, domain: &str, strict_tls: bool) -> ImapResult<Client> {
|
||||
if self.is_secure {
|
||||
Ok(self)
|
||||
} else {
|
||||
|
||||
12
src/imex.rs
12
src/imex.rs
@@ -234,7 +234,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
msg = Message::default();
|
||||
msg.viewtype = Viewtype::File;
|
||||
msg.param.set(Param::File, setup_file_blob.as_name());
|
||||
msg.subject = stock_str::ac_setup_msg_subject(context).await;
|
||||
|
||||
msg.param
|
||||
.set(Param::MimeType, "application/autocrypt-setup");
|
||||
msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
|
||||
@@ -507,16 +507,14 @@ async fn import_backup(context: &Context, backup_to_import: &Path) -> Result<()>
|
||||
let archive = Archive::new(backup_file);
|
||||
|
||||
let mut entries = archive.entries()?;
|
||||
let mut last_progress = 0;
|
||||
while let Some(file) = entries.next().await {
|
||||
let f = &mut file?;
|
||||
|
||||
let current_pos = f.raw_file_position();
|
||||
let progress = 1000 * current_pos / file_size;
|
||||
if progress != last_progress && progress > 10 && progress < 1000 {
|
||||
if progress > 10 && progress < 1000 {
|
||||
// We already emitted ImexProgress(10) above
|
||||
context.emit_event(EventType::ImexProgress(progress as usize));
|
||||
last_progress = progress;
|
||||
}
|
||||
|
||||
if f.path()?.file_name() == Some(OsStr::new(DBFILE_BACKUP_NAME)) {
|
||||
@@ -739,7 +737,6 @@ async fn export_backup_inner(context: &Context, temp_path: &PathBuf) -> Result<(
|
||||
let count = read_dir.len();
|
||||
let mut written_files = 0;
|
||||
|
||||
let mut last_progress = 0;
|
||||
for entry in read_dir.into_iter() {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name();
|
||||
@@ -757,10 +754,9 @@ async fn export_backup_inner(context: &Context, temp_path: &PathBuf) -> Result<(
|
||||
|
||||
written_files += 1;
|
||||
let progress = 1000 * written_files / count;
|
||||
if progress != last_progress && progress > 10 && progress < 1000 {
|
||||
if progress > 10 && progress < 1000 {
|
||||
// We already emitted ImexProgress(10) above
|
||||
context.emit_event(EventType::ImexProgress(progress));
|
||||
last_progress = progress;
|
||||
emit_event!(context, EventType::ImexProgress(progress));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
101
src/job.rs
101
src/job.rs
@@ -2,11 +2,12 @@
|
||||
//!
|
||||
//! This module implements a job queue maintained in the SQLite database
|
||||
//! and job types.
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::{fmt, time::Duration};
|
||||
|
||||
use anyhow::{bail, ensure, format_err, Context as _, Error, Result};
|
||||
use async_smtp::smtp::response::{Category, Code, Detail};
|
||||
use async_std::task::sleep;
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use itertools::Itertools;
|
||||
use rand::{thread_rng, Rng};
|
||||
@@ -814,12 +815,12 @@ impl Job {
|
||||
}
|
||||
|
||||
/// Delete all pending jobs with the given action.
|
||||
pub async fn kill_action(context: &Context, action: Action) -> Result<()> {
|
||||
pub async fn kill_action(context: &Context, action: Action) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE action=?;", paramsv![action])
|
||||
.await?;
|
||||
Ok(())
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Remove jobs with specified IDs.
|
||||
@@ -1151,10 +1152,7 @@ async fn perform_job_action(
|
||||
sql::housekeeping(context).await.ok_or_log(context);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
Action::UpdateRecentQuota => match context.update_recent_quota(connection.inbox()).await {
|
||||
Ok(status) => status,
|
||||
Err(err) => Status::Finished(Err(err)),
|
||||
},
|
||||
Action::UpdateRecentQuota => context.update_recent_quota(connection.inbox()).await,
|
||||
};
|
||||
|
||||
info!(context, "Finished immediate try {} of job {}", tries, job);
|
||||
@@ -1182,14 +1180,13 @@ async fn send_mdn(context: &Context, msg: &Message) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn schedule_resync(context: &Context) -> Result<()> {
|
||||
kill_action(context, Action::ResyncFolders).await?;
|
||||
pub(crate) async fn schedule_resync(context: &Context) {
|
||||
kill_action(context, Action::ResyncFolders).await;
|
||||
add(
|
||||
context,
|
||||
Job::new(Action::ResyncFolders, 0, Params::new(), 0),
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a job.
|
||||
@@ -1238,15 +1235,21 @@ pub async fn add(context: &Context, job: Job) {
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_housekeeping_job(context: &Context) -> Result<Option<Job>> {
|
||||
let last_time = context.get_config_i64(Config::LastHousekeeping).await?;
|
||||
async fn load_housekeeping_job(context: &Context) -> Option<Job> {
|
||||
let last_time = match context.get_config_i64(Config::LastHousekeeping).await {
|
||||
Ok(last_time) => last_time,
|
||||
Err(err) => {
|
||||
warn!(context, "failed to load housekeeping config: {:?}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let next_time = last_time + (60 * 60 * 24);
|
||||
if next_time <= time() {
|
||||
kill_action(context, Action::Housekeeping).await?;
|
||||
Ok(Some(Job::new(Action::Housekeeping, 0, Params::new(), 0)))
|
||||
kill_action(context, Action::Housekeeping).await;
|
||||
Some(Job::new(Action::Housekeeping, 0, Params::new(), 0))
|
||||
} else {
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1260,9 +1263,20 @@ pub(crate) async fn load_next(
|
||||
context: &Context,
|
||||
thread: Thread,
|
||||
info: &InterruptInfo,
|
||||
) -> Result<Option<Job>> {
|
||||
) -> Option<Job> {
|
||||
info!(context, "loading job for {}-thread", thread);
|
||||
|
||||
while !context.sql.is_open().await {
|
||||
// The db is closed, which means that this thread should not be running.
|
||||
// Wait until the db is re-opened (if we returned None, this thread might do further damage)
|
||||
warn!(
|
||||
context,
|
||||
"{}: load_next() was called but the db was not opened, THIS SHOULD NOT HAPPEN. Waiting...",
|
||||
thread
|
||||
);
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
let query;
|
||||
let params;
|
||||
let t = time();
|
||||
@@ -1329,38 +1343,51 @@ LIMIT 1;
|
||||
info!(context, "cleaning up job, because of {}", err);
|
||||
|
||||
// TODO: improve by only doing a single query
|
||||
let id = context
|
||||
match context
|
||||
.sql
|
||||
.query_row(query, params.clone(), |row| row.get::<_, i32>(0))
|
||||
.await
|
||||
.context("Failed to retrieve invalid job ID from the database")?;
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE id=?;", paramsv![id])
|
||||
.await
|
||||
.with_context(|| format!("Failed to delete invalid job {}", id))?;
|
||||
{
|
||||
Ok(id) => {
|
||||
if let Err(err) = context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE id=?;", paramsv![id])
|
||||
.await
|
||||
{
|
||||
warn!(context, "failed to delete job {}: {:?}", id, err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "failed to retrieve invalid job from DB: {}", err);
|
||||
break None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match thread {
|
||||
Thread::Unknown => {
|
||||
bail!("unknown thread for job")
|
||||
error!(context, "unknown thread for job");
|
||||
None
|
||||
}
|
||||
Thread::Imap => {
|
||||
if let Some(job) = job {
|
||||
if job.action < Action::DeleteMsgOnImap {
|
||||
Ok(load_imap_deletion_job(context).await?.or(Some(job)))
|
||||
load_imap_deletion_job(context)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.or(Some(job))
|
||||
} else {
|
||||
Ok(Some(job))
|
||||
Some(job)
|
||||
}
|
||||
} else if let Some(job) = load_imap_deletion_job(context).await? {
|
||||
Ok(Some(job))
|
||||
} else if let Some(job) = load_imap_deletion_job(context).await.unwrap_or_default() {
|
||||
Some(job)
|
||||
} else {
|
||||
Ok(load_housekeeping_job(context).await?)
|
||||
load_housekeeping_job(context).await
|
||||
}
|
||||
}
|
||||
Thread::Smtp => Ok(job),
|
||||
Thread::Smtp => job,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1392,7 +1419,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_load_next_job_two() -> Result<()> {
|
||||
async fn test_load_next_job_two() {
|
||||
// We want to ensure that loading jobs skips over jobs which
|
||||
// fails to load from the database instead of failing to load
|
||||
// all jobs.
|
||||
@@ -1403,7 +1430,7 @@ mod tests {
|
||||
Thread::from(Action::MoveMsg),
|
||||
&InterruptInfo::new(false, None),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
// The housekeeping job should be loaded as we didn't run housekeeping in the last day:
|
||||
assert_eq!(jobs.unwrap().action, Action::Housekeeping);
|
||||
|
||||
@@ -1413,13 +1440,12 @@ mod tests {
|
||||
Thread::from(Action::MoveMsg),
|
||||
&InterruptInfo::new(false, None),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
assert!(jobs.is_some());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_load_next_job_one() -> Result<()> {
|
||||
async fn test_load_next_job_one() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
insert_job(&t, 1, true).await;
|
||||
@@ -1429,8 +1455,7 @@ mod tests {
|
||||
Thread::from(Action::MoveMsg),
|
||||
&InterruptInfo::new(false, None),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
assert!(jobs.is_some());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,13 +83,11 @@ mod simplify;
|
||||
mod smtp;
|
||||
pub mod stock_str;
|
||||
mod token;
|
||||
mod update_helper;
|
||||
#[macro_use]
|
||||
mod dehtml;
|
||||
mod color;
|
||||
pub mod html;
|
||||
pub mod plaintext;
|
||||
pub mod summary;
|
||||
|
||||
pub mod dc_receive_imf;
|
||||
pub mod dc_tools;
|
||||
|
||||
163
src/location.rs
163
src/location.rs
@@ -1,7 +1,7 @@
|
||||
//! Location handling.
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use anyhow::{ensure, Error};
|
||||
use bitflags::bitflags;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
@@ -63,7 +63,7 @@ impl Kml {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn parse(context: &Context, to_parse: &[u8]) -> Result<Self> {
|
||||
pub fn parse(context: &Context, to_parse: &[u8]) -> Result<Self, Error> {
|
||||
ensure!(to_parse.len() <= 1024 * 1024, "kml-file is too large");
|
||||
|
||||
let mut reader = quick_xml::Reader::from_reader(to_parse);
|
||||
@@ -191,55 +191,54 @@ impl Kml {
|
||||
}
|
||||
|
||||
/// Enables location streaming in chat identified by `chat_id` for `seconds` seconds.
|
||||
pub async fn send_locations_to_chat(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
seconds: i64,
|
||||
) -> Result<()> {
|
||||
ensure!(seconds >= 0);
|
||||
ensure!(!chat_id.is_special());
|
||||
pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) {
|
||||
let now = time();
|
||||
let is_sending_locations_before = is_sending_locations_to_chat(context, Some(chat_id)).await?;
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats \
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
paramsv![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(stock_str::msg_location_enabled(context).await);
|
||||
msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
|
||||
chat::send_msg(context, chat_id, &mut msg)
|
||||
if !(seconds < 0 || chat_id.is_special()) {
|
||||
let is_sending_locations_before =
|
||||
is_sending_locations_to_chat(context, Some(chat_id)).await;
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats \
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
paramsv![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id,
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str = stock_str::msg_location_disabled(context).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str, now).await;
|
||||
.is_ok()
|
||||
{
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(stock_str::msg_location_enabled(context).await);
|
||||
msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
|
||||
chat::send_msg(context, chat_id, &mut msg)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str = stock_str::msg_location_disabled(context).await;
|
||||
chat::add_info_msg(context, chat_id, stock_str, now).await;
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
if 0 != seconds {
|
||||
schedule_maybe_send_locations(context, false).await;
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(
|
||||
job::Action::MaybeSendLocationsEnded,
|
||||
chat_id.to_u32(),
|
||||
Params::new(),
|
||||
seconds + 1,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
if 0 != seconds {
|
||||
schedule_maybe_send_locations(context, false).await;
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(
|
||||
job::Action::MaybeSendLocationsEnded,
|
||||
chat_id.to_u32(),
|
||||
Params::new(),
|
||||
seconds + 1,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) {
|
||||
@@ -256,31 +255,25 @@ async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool)
|
||||
///
|
||||
/// If `chat_id` is `Some` only that chat is checked, otherwise returns `true` if any chat
|
||||
/// is sending locations.
|
||||
pub async fn is_sending_locations_to_chat(
|
||||
context: &Context,
|
||||
chat_id: Option<ChatId>,
|
||||
) -> Result<bool> {
|
||||
let exists = match chat_id {
|
||||
Some(chat_id) => {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(id) FROM chats WHERE id=? AND locations_send_until>?;",
|
||||
paramsv![chat_id, time()],
|
||||
)
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(id) FROM chats WHERE locations_send_until>?;",
|
||||
paramsv![time()],
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
Ok(exists)
|
||||
pub async fn is_sending_locations_to_chat(context: &Context, chat_id: Option<ChatId>) -> bool {
|
||||
match chat_id {
|
||||
Some(chat_id) => context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(id) FROM chats WHERE id=? AND locations_send_until>?;",
|
||||
paramsv![chat_id, time()],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
None => context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(id) FROM chats WHERE locations_send_until>?;",
|
||||
paramsv![time()],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
|
||||
@@ -295,11 +288,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
paramsv![time()],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|chats| {
|
||||
chats
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -336,7 +325,7 @@ pub async fn get_range(
|
||||
contact_id: Option<u32>,
|
||||
timestamp_from: i64,
|
||||
mut timestamp_to: i64,
|
||||
) -> Result<Vec<Location>> {
|
||||
) -> Result<Vec<Location>, Error> {
|
||||
if timestamp_to == 0 {
|
||||
timestamp_to = time() + 10;
|
||||
}
|
||||
@@ -411,7 +400,7 @@ fn is_marker(txt: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Deletes all locations from the database.
|
||||
pub async fn delete_all(context: &Context) -> Result<()> {
|
||||
pub async fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM locations;", paramsv![])
|
||||
@@ -420,7 +409,7 @@ pub async fn delete_all(context: &Context) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)> {
|
||||
pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> {
|
||||
let mut last_added_location_id = 0;
|
||||
|
||||
let self_addr = context
|
||||
@@ -528,7 +517,7 @@ pub async fn set_kml_sent_timestamp(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
) -> Result<(), Error> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
@@ -539,7 +528,11 @@ pub async fn set_kml_sent_timestamp(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_msg_location_id(context: &Context, msg_id: MsgId, location_id: u32) -> Result<()> {
|
||||
pub async fn set_msg_location_id(
|
||||
context: &Context,
|
||||
msg_id: MsgId,
|
||||
location_id: u32,
|
||||
) -> Result<(), Error> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
@@ -557,7 +550,7 @@ pub async fn save(
|
||||
contact_id: u32,
|
||||
locations: &[Location],
|
||||
independent: bool,
|
||||
) -> Result<u32> {
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(!chat_id.is_special(), "Invalid chat id");
|
||||
|
||||
let mut newest_timestamp = 0;
|
||||
@@ -637,7 +630,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
||||
},
|
||||
|rows| {
|
||||
rows.filter_map(|v| v.transpose())
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
|
||||
15
src/log.rs
15
src/log.rs
@@ -13,7 +13,7 @@ macro_rules! info {
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
$ctx.emit_event($crate::EventType::Info(full));
|
||||
emit_event!($ctx, $crate::EventType::Info(full));
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ macro_rules! warn {
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
$ctx.emit_event($crate::EventType::Warning(full));
|
||||
emit_event!($ctx, $crate::EventType::Warning(full));
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -39,10 +39,17 @@ macro_rules! error {
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
$ctx.emit_event($crate::EventType::Error(formatted));
|
||||
emit_event!($ctx, $crate::EventType::Error(formatted));
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! emit_event {
|
||||
($ctx:expr, $event:expr) => {
|
||||
$ctx.emit_event($event);
|
||||
};
|
||||
}
|
||||
|
||||
pub trait LogExt<T, E>
|
||||
where
|
||||
Self: std::marker::Sized,
|
||||
@@ -129,7 +136,7 @@ impl<T, E: std::fmt::Display> LogExt<T, E> for Result<T, E> {
|
||||
);
|
||||
// We can't use the warn!() macro here as the file!() and line!() macros
|
||||
// don't work with #[track_caller]
|
||||
context.emit_event(crate::EventType::Warning(full));
|
||||
emit_event!(context, crate::EventType::Warning(full));
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
39
src/lot.rs
39
src/lot.rs
@@ -3,8 +3,6 @@
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
|
||||
use crate::key::Fingerprint;
|
||||
use crate::message::MessageState;
|
||||
use crate::summary::{Summary, SummaryPrefix};
|
||||
|
||||
/// An object containing a set of values.
|
||||
/// The meaning of the values is defined by the function returning the object.
|
||||
@@ -141,40 +139,3 @@ impl Default for LotState {
|
||||
LotState::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageState> for LotState {
|
||||
fn from(s: MessageState) -> Self {
|
||||
use MessageState::*;
|
||||
match s {
|
||||
Undefined => LotState::Undefined,
|
||||
InFresh => LotState::MsgInFresh,
|
||||
InNoticed => LotState::MsgInNoticed,
|
||||
InSeen => LotState::MsgInSeen,
|
||||
OutPreparing => LotState::MsgOutPreparing,
|
||||
OutDraft => LotState::MsgOutDraft,
|
||||
OutPending => LotState::MsgOutPending,
|
||||
OutFailed => LotState::MsgOutFailed,
|
||||
OutDelivered => LotState::MsgOutDelivered,
|
||||
OutMdnRcvd => LotState::MsgOutMdnRcvd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Summary> for Lot {
|
||||
fn from(summary: Summary) -> Self {
|
||||
let (text1, text1_meaning) = match summary.prefix {
|
||||
None => (None, Meaning::None),
|
||||
Some(SummaryPrefix::Draft(text)) => (Some(text), Meaning::Text1Draft),
|
||||
Some(SummaryPrefix::Username(username)) => (Some(username), Meaning::Text1Username),
|
||||
Some(SummaryPrefix::Me(text)) => (Some(text), Meaning::Text1Self),
|
||||
};
|
||||
Self {
|
||||
text1_meaning,
|
||||
text1,
|
||||
text2: Some(summary.text),
|
||||
timestamp: summary.timestamp,
|
||||
state: summary.state.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
414
src/message.rs
414
src/message.rs
@@ -3,9 +3,10 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use anyhow::{ensure, format_err, Context as _, Result};
|
||||
use anyhow::{ensure, format_err, Result};
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use itertools::Itertools;
|
||||
use rusqlite::types::ValueRef;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -25,11 +26,15 @@ use crate::ephemeral::Timer as EphemeralTimer;
|
||||
use crate::events::EventType;
|
||||
use crate::job::{self, Action};
|
||||
use crate::log::LogExt;
|
||||
use crate::lot::{Lot, LotState, Meaning};
|
||||
use crate::mimeparser::{parse_message_id, FailureReport, SystemMessage};
|
||||
use crate::param::{Param, Params};
|
||||
use crate::pgp::split_armored_data;
|
||||
use crate::stock_str;
|
||||
use crate::summary::{get_summarytext_by_raw, Summary};
|
||||
|
||||
// In practice, the user additionally cuts the string themselves
|
||||
// pixel-accurate.
|
||||
const SUMMARY_CHARACTERS: usize = 160;
|
||||
|
||||
/// Message ID, including reserved IDs.
|
||||
///
|
||||
@@ -587,21 +592,23 @@ impl Message {
|
||||
self.ephemeral_timestamp
|
||||
}
|
||||
|
||||
/// Returns message summary for display in the search results.
|
||||
pub async fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
|
||||
pub async fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot {
|
||||
let mut ret = Lot::new();
|
||||
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
} else {
|
||||
let chat = Chat::load_from_db(context, self.chat_id).await?;
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.chat_id).await {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
return ret;
|
||||
};
|
||||
|
||||
let contact = if self.from_id != DC_CONTACT_ID_SELF {
|
||||
match chat.typ {
|
||||
Chattype::Group | Chattype::Mailinglist => {
|
||||
Some(Contact::get_by_id(context, self.from_id).await?)
|
||||
Contact::get_by_id(context, self.from_id).await.ok()
|
||||
}
|
||||
Chattype::Single | Chattype::Undefined => None,
|
||||
}
|
||||
@@ -609,7 +616,9 @@ impl Message {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Summary::new(context, self, chat, contact.as_ref()).await)
|
||||
ret.fill(self, chat, contact.as_ref(), context).await;
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub async fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String {
|
||||
@@ -1020,6 +1029,24 @@ impl std::fmt::Display for MessageState {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageState> for LotState {
|
||||
fn from(s: MessageState) -> Self {
|
||||
use MessageState::*;
|
||||
match s {
|
||||
Undefined => LotState::Undefined,
|
||||
InFresh => LotState::MsgInFresh,
|
||||
InNoticed => LotState::MsgInNoticed,
|
||||
InSeen => LotState::MsgInSeen,
|
||||
OutPreparing => LotState::MsgOutPreparing,
|
||||
OutDraft => LotState::MsgOutDraft,
|
||||
OutPending => LotState::MsgOutPending,
|
||||
OutFailed => LotState::MsgOutFailed,
|
||||
OutDelivered => LotState::MsgOutDelivered,
|
||||
OutMdnRcvd => LotState::MsgOutMdnRcvd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageState {
|
||||
pub fn can_fail(self) -> bool {
|
||||
use MessageState::*;
|
||||
@@ -1037,6 +1064,68 @@ impl MessageState {
|
||||
}
|
||||
}
|
||||
|
||||
impl Lot {
|
||||
/* library-internal */
|
||||
/* in practice, the user additionally cuts the string himself pixel-accurate */
|
||||
pub async fn fill(
|
||||
&mut self,
|
||||
msg: &mut Message,
|
||||
chat: &Chat,
|
||||
contact: Option<&Contact>,
|
||||
context: &Context,
|
||||
) {
|
||||
if msg.state == MessageState::OutDraft {
|
||||
self.text1 = Some(stock_str::draft(context).await);
|
||||
self.text1_meaning = Meaning::Text1Draft;
|
||||
} else if msg.from_id == DC_CONTACT_ID_SELF {
|
||||
if msg.is_info() || chat.is_self_talk() {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
} else {
|
||||
self.text1 = Some(stock_str::self_msg(context).await);
|
||||
self.text1_meaning = Meaning::Text1Self;
|
||||
}
|
||||
} else {
|
||||
match chat.typ {
|
||||
Chattype::Group | Chattype::Mailinglist => {
|
||||
if msg.is_info() || contact.is_none() {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
} else {
|
||||
self.text1 = msg
|
||||
.get_override_sender_name()
|
||||
.or_else(|| contact.map(|contact| msg.get_sender_name(contact)));
|
||||
self.text1_meaning = Meaning::Text1Username;
|
||||
}
|
||||
}
|
||||
Chattype::Single | Chattype::Undefined => {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut text2 = get_summarytext_by_raw(
|
||||
msg.viewtype,
|
||||
msg.text.as_ref(),
|
||||
msg.is_forwarded(),
|
||||
&msg.param,
|
||||
SUMMARY_CHARACTERS,
|
||||
context,
|
||||
)
|
||||
.await;
|
||||
|
||||
if text2.is_empty() && msg.quoted_text().is_some() {
|
||||
text2 = stock_str::reply_noun(context).await
|
||||
}
|
||||
|
||||
self.text2 = Some(text2);
|
||||
|
||||
self.timestamp = msg.get_timestamp();
|
||||
self.state = msg.state.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String> {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
let rawtxt: Option<String> = context
|
||||
@@ -1277,16 +1366,16 @@ pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
|
||||
for msg_id in msg_ids.iter() {
|
||||
let msg = Message::load_from_db(context, *msg_id).await?;
|
||||
if msg.location_id > 0 {
|
||||
delete_poi_location(context, msg.location_id).await;
|
||||
if let Ok(msg) = Message::load_from_db(context, *msg_id).await {
|
||||
if msg.location_id > 0 {
|
||||
delete_poi_location(context, msg.location_id).await;
|
||||
}
|
||||
}
|
||||
if let Err(err) = msg_id.trash(context).await {
|
||||
error!(context, "Unable to trash message {}: {}", msg_id, err);
|
||||
}
|
||||
msg_id
|
||||
.trash(context)
|
||||
.await
|
||||
.with_context(|| format!("Unable to trash message {}", msg_id))?;
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(Action::DeleteMsgOnImap, msg_id.to_u32(), Params::new(), 0),
|
||||
@@ -1299,14 +1388,13 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
job::kill_action(context, Action::Housekeeping).await?;
|
||||
job::kill_action(context, Action::Housekeeping).await;
|
||||
job::add(
|
||||
context,
|
||||
job::Job::new(Action::Housekeeping, 0, Params::new(), 10),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
|
||||
@@ -1402,6 +1490,88 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Returns a summary text.
|
||||
pub async fn get_summarytext_by_raw(
|
||||
viewtype: Viewtype,
|
||||
text: Option<impl AsRef<str>>,
|
||||
was_forwarded: bool,
|
||||
param: &Params,
|
||||
approx_characters: usize,
|
||||
context: &Context,
|
||||
) -> String {
|
||||
let mut append_text = true;
|
||||
let prefix = match viewtype {
|
||||
Viewtype::Image => stock_str::image(context).await,
|
||||
Viewtype::Gif => stock_str::gif(context).await,
|
||||
Viewtype::Sticker => stock_str::sticker(context).await,
|
||||
Viewtype::Video => stock_str::video(context).await,
|
||||
Viewtype::Voice => stock_str::voice_message(context).await,
|
||||
Viewtype::Audio | Viewtype::File => {
|
||||
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
append_text = false;
|
||||
stock_str::ac_setup_msg_subject(context).await
|
||||
} else {
|
||||
let file_name: String = param
|
||||
.get_path(Param::File, context)
|
||||
.unwrap_or(None)
|
||||
.and_then(|path| {
|
||||
path.file_name()
|
||||
.map(|fname| fname.to_string_lossy().into_owned())
|
||||
})
|
||||
.unwrap_or_else(|| String::from("ErrFileName"));
|
||||
let label = if viewtype == Viewtype::Audio {
|
||||
stock_str::audio(context).await
|
||||
} else {
|
||||
stock_str::file(context).await
|
||||
};
|
||||
format!("{} – {}", label, file_name)
|
||||
}
|
||||
}
|
||||
Viewtype::VideochatInvitation => {
|
||||
append_text = false;
|
||||
stock_str::videochat_invitation(context).await
|
||||
}
|
||||
_ => {
|
||||
if param.get_cmd() != SystemMessage::LocationOnly {
|
||||
"".to_string()
|
||||
} else {
|
||||
append_text = false;
|
||||
stock_str::location(context).await
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if !append_text {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
let summary_content = if let Some(text) = text {
|
||||
if text.as_ref().is_empty() {
|
||||
prefix
|
||||
} else if prefix.is_empty() {
|
||||
dc_truncate(text.as_ref(), approx_characters).to_string()
|
||||
} else {
|
||||
let tmp = format!("{} – {}", prefix, text.as_ref());
|
||||
dc_truncate(&tmp, approx_characters).to_string()
|
||||
}
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
|
||||
let summary = if was_forwarded {
|
||||
let tmp = format!(
|
||||
"{}: {}",
|
||||
stock_str::forwarded(context).await,
|
||||
summary_content
|
||||
);
|
||||
dc_truncate(&tmp, approx_characters).to_string()
|
||||
} else {
|
||||
summary_content
|
||||
};
|
||||
|
||||
summary.split_whitespace().join(" ")
|
||||
}
|
||||
|
||||
// as we do not cut inside words, this results in about 32-42 characters.
|
||||
// Do not use too long subjects - we add a tag after the subject which gets truncated by the clients otherwise.
|
||||
// It should also be very clear, the subject is _not_ the whole message.
|
||||
@@ -1465,17 +1635,6 @@ pub async fn handle_mdn(
|
||||
rfc724_mid: &str,
|
||||
timestamp_sent: i64,
|
||||
) -> Result<Option<(ChatId, MsgId)>> {
|
||||
if from_id == DC_CONTACT_ID_SELF {
|
||||
warn!(
|
||||
context,
|
||||
"ignoring MDN sent to self, this is a bug on the sender device"
|
||||
);
|
||||
|
||||
// This is not an error on our side,
|
||||
// we successfully ignored an invalid MDN and return `Ok`.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let res = context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
@@ -2079,6 +2238,203 @@ mod tests {
|
||||
assert!(chat::prepare_msg(ctx, chat.id, &mut msg).await.is_err());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_get_summarytext_by_raw() {
|
||||
let d = test::TestContext::new().await;
|
||||
let ctx = &d.ctx;
|
||||
|
||||
let some_text = Some(" bla \t\n\tbla\n\t".to_string());
|
||||
let empty_text = Some("".to_string());
|
||||
let no_text: Option<String> = None;
|
||||
|
||||
let mut some_file = Params::new();
|
||||
some_file.set(Param::File, "foo.bar");
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Text,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&Params::new(),
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Image,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Image" // file names are not added for images
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Video,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Video" // file names are not added for videos
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), false, &some_file, 50, ctx,)
|
||||
.await,
|
||||
"GIF" // file names are not added for GIFs
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Sticker,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx,
|
||||
)
|
||||
.await,
|
||||
"Sticker" // file names are not added for stickers
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
empty_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx,
|
||||
)
|
||||
.await,
|
||||
"Voice message" // file names are not added for voice messages, empty text is skipped
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Voice message" // file names are not added for voice messages
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar" // file name is added for audio
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
empty_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx,
|
||||
)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::File,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"File \u{2013} foo.bar \u{2013} bla bla" // file name is added for files
|
||||
);
|
||||
|
||||
// Forwarded
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Text,
|
||||
some_text.as_ref(),
|
||||
true,
|
||||
&Params::new(),
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Forwarded: bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::File,
|
||||
some_text.as_ref(),
|
||||
true,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Forwarded: File \u{2013} foo.bar \u{2013} bla bla"
|
||||
);
|
||||
|
||||
let mut asm_file = Params::new();
|
||||
asm_file.set(Param::File, "foo.bar");
|
||||
asm_file.set_cmd(SystemMessage::AutocryptSetupMessage);
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), false, &asm_file, 50, ctx)
|
||||
.await,
|
||||
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_parse_webrtc_instance() {
|
||||
let (webrtc_type, url) = Message::parse_webrtc_instance("basicwebrtc:https://foo/bar");
|
||||
|
||||
@@ -388,6 +388,10 @@ impl<'a> MimeFactory<'a> {
|
||||
|
||||
let subject = match self.loaded {
|
||||
Loaded::Message { ref chat } => {
|
||||
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
return Ok(stock_str::ac_setup_msg_subject(context).await);
|
||||
}
|
||||
|
||||
if !self.msg.subject.is_empty() {
|
||||
return Ok(self.msg.subject.clone());
|
||||
}
|
||||
@@ -1080,7 +1084,7 @@ impl<'a> MimeFactory<'a> {
|
||||
parts.push(msg_kml_part);
|
||||
}
|
||||
|
||||
if location::is_sending_locations_to_chat(context, Some(self.msg.chat_id)).await? {
|
||||
if location::is_sending_locations_to_chat(context, Some(self.msg.chat_id)).await {
|
||||
match self.get_location_kml_part(context).await {
|
||||
Ok(part) => parts.push(part),
|
||||
Err(err) => {
|
||||
|
||||
@@ -1141,11 +1141,11 @@ impl MimeMessage {
|
||||
report: &mailparse::ParsedMail<'_>,
|
||||
) -> Result<Option<FailureReport>> {
|
||||
// parse as mailheaders
|
||||
if let Some(original_msg) = report.subparts.iter().find(|p| {
|
||||
p.ctype.mimetype.contains("rfc822")
|
||||
|| p.ctype.mimetype == "message/global"
|
||||
|| p.ctype.mimetype == "message/global-headers"
|
||||
}) {
|
||||
if let Some(original_msg) = report
|
||||
.subparts
|
||||
.iter()
|
||||
.find(|p| p.ctype.mimetype.contains("rfc822") || p.ctype.mimetype == "message/global")
|
||||
{
|
||||
let report_body = original_msg.get_body_raw()?;
|
||||
let (report_fields, _) = mailparse::parse_headers(&report_body)?;
|
||||
|
||||
@@ -2997,74 +2997,4 @@ Message.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_ignore_read_receipt_to_self() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
// Alice receives BCC-self copy of a message sent to Bob.
|
||||
dc_receive_imf(
|
||||
&alice,
|
||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: alice@example.com\n\
|
||||
To: bob@example.net\n\
|
||||
Subject: foo\n\
|
||||
Message-ID: first@example.com\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Disposition-Notification-To: alice@example.com\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n"
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let msg = alice.get_last_msg().await;
|
||||
assert_eq!(msg.state, MessageState::OutDelivered);
|
||||
|
||||
// Due to a bug in the old version running on the other device, Alice receives a read
|
||||
// receipt from self.
|
||||
dc_receive_imf(
|
||||
&alice,
|
||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
From: alice@example.com\n\
|
||||
To: alice@example.com\n\
|
||||
Subject: message opened\n\
|
||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Message-ID: second@example.com\n\
|
||||
Content-Type: multipart/report; report-type=disposition-notification; boundary=\"SNIPP\"\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP\n\
|
||||
Content-Type: text/plain; charset=utf-8\n\
|
||||
\n\
|
||||
Read receipts do not guarantee sth. was read.\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP\n\
|
||||
Content-Type: message/disposition-notification\n\
|
||||
\n\
|
||||
Original-Recipient: rfc822;bob@example.com\n\
|
||||
Final-Recipient: rfc822;bob@example.com\n\
|
||||
Original-Message-ID: <first@example.com>\n\
|
||||
Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||
\n\
|
||||
\n\
|
||||
--SNIPP--"
|
||||
.as_bytes(),
|
||||
"INBOX",
|
||||
2,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Check that the state has not changed to `MessageState::OutMdnRcvd`.
|
||||
let msg = Message::load_from_db(&alice, msg.id).await?;
|
||||
assert_eq!(msg.state, MessageState::OutDelivered);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
32
src/param.rs
32
src/param.rs
@@ -139,27 +139,6 @@ pub enum Param {
|
||||
|
||||
/// For MDN-sending job
|
||||
MsgId = b'I',
|
||||
|
||||
/// For Contacts: timestamp of status (aka signature or footer) update.
|
||||
StatusTimestamp = b'j',
|
||||
|
||||
/// For Contacts and Chats: timestamp of avatar update.
|
||||
AvatarTimestamp = b'J',
|
||||
|
||||
/// For Chats: timestamp of status/signature/footer update.
|
||||
EphemeralSettingsTimestamp = b'B',
|
||||
|
||||
/// For Chats: timestamp of subject update.
|
||||
SubjectTimestamp = b'C',
|
||||
|
||||
/// For Chats: timestamp of group name update.
|
||||
GroupNameTimestamp = b'g',
|
||||
|
||||
/// For Chats: timestamp of group name update.
|
||||
MemberListTimestamp = b'k',
|
||||
|
||||
/// For Chats: timestamp of protection settings update.
|
||||
ProtectionSettingsTimestamp = b'L',
|
||||
}
|
||||
|
||||
/// An object for handling key=value parameter lists.
|
||||
@@ -266,11 +245,6 @@ impl Params {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
/// Get the given parameter and parse as `i64`.
|
||||
pub fn get_i64(&self, key: Param) -> Option<i64> {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
/// Get the given parameter and parse as `bool`.
|
||||
pub fn get_bool(&self, key: Param) -> Option<bool> {
|
||||
self.get_int(key).map(|v| v != 0)
|
||||
@@ -372,12 +346,6 @@ impl Params {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the given paramter to the passed in `i64`.
|
||||
pub fn set_i64(&mut self, key: Param, value: i64) -> &mut Self {
|
||||
self.set(key, value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the given parameter to the passed in `f64` .
|
||||
pub fn set_float(&mut self, key: Param, value: f64) -> &mut Self {
|
||||
self.set(key, format!("{}", value));
|
||||
|
||||
@@ -278,7 +278,7 @@ impl Peerstate {
|
||||
let msg = stock_str::contact_setup_changed(context, self.addr.clone()).await;
|
||||
|
||||
chat::add_info_msg(context, chat_id, msg, timestamp).await;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
emit_event!(context, EventType::ChatModified(chat_id));
|
||||
} else {
|
||||
bail!("contact with peerstate.addr {:?} not found", &self.addr);
|
||||
}
|
||||
|
||||
91
src/quota.rs
91
src/quota.rs
@@ -4,17 +4,13 @@ use anyhow::{anyhow, Result};
|
||||
use async_imap::types::{Quota, QuotaResource};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::chat::add_device_msg_with_importance;
|
||||
use crate::config::Config;
|
||||
use crate::constants::Viewtype;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::time;
|
||||
use crate::imap::scan_folders::get_watched_folders;
|
||||
use crate::imap::Imap;
|
||||
use crate::job::{Action, Status};
|
||||
use crate::message::Message;
|
||||
use crate::param::Params;
|
||||
use crate::{job, stock_str, EventType};
|
||||
use crate::{job, EventType};
|
||||
|
||||
/// warn about a nearly full mailbox after this usage percentage is reached.
|
||||
/// quota icon is "yellow".
|
||||
@@ -24,12 +20,6 @@ pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
|
||||
// this threshold only makes the quota icon "red".
|
||||
pub const QUOTA_ERROR_THRESHOLD_PERCENTAGE: u64 = 99;
|
||||
|
||||
/// if quota is below this value (again),
|
||||
/// QuotaExceeding is cleared.
|
||||
/// This value should be a bit below QUOTA_WARN_THRESHOLD_PERCENTAGE to
|
||||
/// avoid jittering and lots of warnings when quota is exactly at the warning threshold.
|
||||
pub const QUOTA_ALLCLEAR_PERCENTAGE: u64 = 75;
|
||||
|
||||
// if recent quota is older,
|
||||
// it is re-fetched on dc_get_connectivity_html()
|
||||
pub const QUOTA_MAX_AGE_SECONDS: i64 = 60;
|
||||
@@ -73,55 +63,25 @@ async fn get_unique_quota_roots_and_usage(
|
||||
Ok(unique_quota_roots)
|
||||
}
|
||||
|
||||
fn get_highest_usage<'t>(
|
||||
unique_quota_roots: &'t IndexMap<String, Vec<QuotaResource>>,
|
||||
) -> Result<(u64, &'t String, &QuotaResource)> {
|
||||
let mut highest: Option<(u64, &'t String, &QuotaResource)> = None;
|
||||
for (name, resources) in unique_quota_roots {
|
||||
for r in resources {
|
||||
let usage_percent = r.get_usage_percentage();
|
||||
match highest {
|
||||
None => {
|
||||
highest = Some((usage_percent, name, r));
|
||||
}
|
||||
Some((up, ..)) => {
|
||||
if up <= usage_percent {
|
||||
highest = Some((usage_percent, name, r));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
highest.ok_or_else(|| anyhow!("no quota_resource found, this is unexpected"))
|
||||
}
|
||||
|
||||
impl Context {
|
||||
// Adds a job to update `quota.recent`
|
||||
pub(crate) async fn schedule_quota_update(&self) -> Result<()> {
|
||||
job::kill_action(self, Action::UpdateRecentQuota).await?;
|
||||
pub(crate) async fn schedule_quota_update(&self) {
|
||||
job::kill_action(self, Action::UpdateRecentQuota).await;
|
||||
job::add(
|
||||
self,
|
||||
job::Job::new(Action::UpdateRecentQuota, 0, Params::new(), 0),
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates `quota.recent`, sets `quota.modified` to the current time
|
||||
/// and emits an event to let the UIs update connectivity view.
|
||||
///
|
||||
/// Moreover, once each time quota gets larger than `QUOTA_WARN_THRESHOLD_PERCENTAGE`,
|
||||
/// a device message is added.
|
||||
/// As the message is added only once, the user is not spammed
|
||||
/// in case for some providers the quota is always at ~100%
|
||||
/// and new space is allocated as needed.
|
||||
///
|
||||
/// Called in response to `Action::UpdateRecentQuota`.
|
||||
pub(crate) async fn update_recent_quota(&self, imap: &mut Imap) -> Result<Status> {
|
||||
pub(crate) async fn update_recent_quota(&self, imap: &mut Imap) -> Status {
|
||||
if let Err(err) = imap.prepare(self).await {
|
||||
warn!(self, "could not connect: {:?}", err);
|
||||
return Ok(Status::RetryNow);
|
||||
return Status::RetryNow;
|
||||
}
|
||||
|
||||
let quota = if imap.can_check_quota() {
|
||||
@@ -131,51 +91,12 @@ impl Context {
|
||||
Err(anyhow!("Quota not supported by your provider."))
|
||||
};
|
||||
|
||||
if let Ok(quota) = "a {
|
||||
match get_highest_usage(quota) {
|
||||
Ok((highest, _, _)) => {
|
||||
if highest >= QUOTA_WARN_THRESHOLD_PERCENTAGE {
|
||||
if self.get_config_int(Config::QuotaExceeding).await? == 0 {
|
||||
self.set_config(Config::QuotaExceeding, Some(&highest.to_string()))
|
||||
.await?;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(stock_str::quota_exceeding(self, highest).await);
|
||||
add_device_msg_with_importance(self, None, Some(&mut msg), true)
|
||||
.await?;
|
||||
}
|
||||
} else if highest <= QUOTA_ALLCLEAR_PERCENTAGE {
|
||||
self.set_config(Config::QuotaExceeding, None).await?;
|
||||
}
|
||||
}
|
||||
Err(err) => warn!(self, "cannot get highest quota usage: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
*self.quota.write().await = Some(QuotaInfo {
|
||||
recent: quota,
|
||||
modified: time(),
|
||||
});
|
||||
|
||||
self.emit_event(EventType::ConnectivityChanged);
|
||||
Ok(Status::Finished(Ok(())))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::quota::{
|
||||
QUOTA_ALLCLEAR_PERCENTAGE, QUOTA_ERROR_THRESHOLD_PERCENTAGE,
|
||||
QUOTA_WARN_THRESHOLD_PERCENTAGE,
|
||||
};
|
||||
|
||||
#[allow(clippy::assertions_on_constants)]
|
||||
#[async_std::test]
|
||||
async fn test_quota_thresholds() -> anyhow::Result<()> {
|
||||
assert!(QUOTA_ALLCLEAR_PERCENTAGE > 50);
|
||||
assert!(QUOTA_ALLCLEAR_PERCENTAGE < QUOTA_WARN_THRESHOLD_PERCENTAGE);
|
||||
assert!(QUOTA_WARN_THRESHOLD_PERCENTAGE < QUOTA_ERROR_THRESHOLD_PERCENTAGE);
|
||||
assert!(QUOTA_ERROR_THRESHOLD_PERCENTAGE < 100);
|
||||
Ok(())
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use async_std::{
|
||||
channel::{self, Receiver, Sender},
|
||||
task,
|
||||
};
|
||||
use governor;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
@@ -71,10 +70,6 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
|
||||
shutdown_sender,
|
||||
} = inbox_handlers;
|
||||
|
||||
let quota = governor::Quota::per_minute(std::num::NonZeroU32::new(1).unwrap())
|
||||
.allow_burst(std::num::NonZeroU32::new(5).unwrap());
|
||||
let rate_limiter = governor::RateLimiter::direct(quota);
|
||||
|
||||
let ctx1 = ctx.clone();
|
||||
let fut = async move {
|
||||
started
|
||||
@@ -87,12 +82,7 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
|
||||
let mut jobs_loaded = 0;
|
||||
let mut info = InterruptInfo::default();
|
||||
loop {
|
||||
rate_limiter.until_ready().await;
|
||||
match job::load_next(&ctx, Thread::Imap, &info)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
match job::load_next(&ctx, Thread::Imap, &info).await {
|
||||
Some(job) if jobs_loaded <= 20 => {
|
||||
jobs_loaded += 1;
|
||||
job::perform_job(&ctx, job::Connection::Inbox(&mut connection), job).await;
|
||||
@@ -253,10 +243,6 @@ async fn simple_imap_loop(
|
||||
|
||||
let ctx1 = ctx.clone();
|
||||
|
||||
let quota = governor::Quota::per_minute(std::num::NonZeroU32::new(2).unwrap())
|
||||
.allow_burst(std::num::NonZeroU32::new(5).unwrap());
|
||||
let rate_limiter = governor::RateLimiter::direct(quota);
|
||||
|
||||
let fut = async move {
|
||||
started
|
||||
.send(())
|
||||
@@ -265,7 +251,6 @@ async fn simple_imap_loop(
|
||||
let ctx = ctx1;
|
||||
|
||||
loop {
|
||||
rate_limiter.until_ready().await;
|
||||
fetch_idle(&ctx, &mut connection, folder).await;
|
||||
}
|
||||
};
|
||||
@@ -304,11 +289,7 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
|
||||
|
||||
let mut interrupt_info = Default::default();
|
||||
loop {
|
||||
match job::load_next(&ctx, Thread::Smtp, &interrupt_info)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
match job::load_next(&ctx, Thread::Smtp, &interrupt_info).await {
|
||||
Some(job) => {
|
||||
info!(ctx, "executing smtp job");
|
||||
job::perform_job(&ctx, job::Connection::Smtp(&mut connection), job).await;
|
||||
|
||||
@@ -504,11 +504,11 @@ impl Context {
|
||||
}
|
||||
|
||||
if quota.modified + QUOTA_MAX_AGE_SECONDS < time() {
|
||||
self.schedule_quota_update().await?;
|
||||
self.schedule_quota_update().await;
|
||||
}
|
||||
} else {
|
||||
ret += "<li>One moment...</li>";
|
||||
self.schedule_quota_update().await?;
|
||||
self.schedule_quota_update().await;
|
||||
}
|
||||
ret += "</ul>";
|
||||
|
||||
|
||||
@@ -635,10 +635,10 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await?;
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await;
|
||||
info!(context, "Auth verified.",);
|
||||
secure_connection_established(context, contact_chat_id).await?;
|
||||
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
||||
emit_event!(context, EventType::ContactsChanged(Some(contact_id)));
|
||||
inviter_progress!(context, contact_id, 600);
|
||||
if join_vg {
|
||||
// the vg-member-added message is special:
|
||||
@@ -861,7 +861,7 @@ async fn secure_connection_established(
|
||||
};
|
||||
let msg = stock_str::contact_verified(context, addr).await;
|
||||
chat::add_info_msg(context, contact_chat_id, msg, time()).await;
|
||||
context.emit_event(EventType::ChatModified(contact_chat_id));
|
||||
emit_event!(context, EventType::ChatModified(contact_chat_id));
|
||||
info!(context, "StockMessage::ContactVerified posted to 1:1 chat");
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -357,8 +357,8 @@ impl BobState {
|
||||
}
|
||||
mark_peer_as_verified(context, self.invite.fingerprint()).await?;
|
||||
Contact::scaleup_origin_by_id(context, self.invite.contact_id(), Origin::SecurejoinJoined)
|
||||
.await?;
|
||||
context.emit_event(EventType::ContactsChanged(None));
|
||||
.await;
|
||||
emit_event!(context, EventType::ContactsChanged(None));
|
||||
|
||||
if let QrInvite::Group { .. } = self.invite {
|
||||
let member_added = mime_message
|
||||
|
||||
@@ -109,8 +109,7 @@ impl Smtp {
|
||||
&lp.socks5_config,
|
||||
&lp.addr,
|
||||
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
lp.provider
|
||||
.map_or(lp.socks5_config.is_some(), |provider| provider.strict_tls),
|
||||
lp.provider.map_or(false, |provider| provider.strict_tls),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -598,8 +598,6 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
context.schedule_quota_update().await?;
|
||||
|
||||
if let Err(e) = context
|
||||
.set_config(Config::LastHousekeeping, Some(&time().to_string()))
|
||||
.await
|
||||
|
||||
@@ -258,15 +258,6 @@ pub enum StockMessage {
|
||||
|
||||
#[strum(props(fallback = "Forwarded"))]
|
||||
Forwarded = 97,
|
||||
|
||||
#[strum(props(
|
||||
fallback = "⚠️ Your provider's storage is about to exceed, already %1$s%% are used.\n\n\
|
||||
You may not be able to receive message when the storage is 100%% used.\n\n\
|
||||
👉 Please check if you can delete old data in the provider's webinterface \
|
||||
and consider to enable \"Settings / Delete Old Messages\". \
|
||||
You can check your current storage usage anytime at \"Settings / Connectivity\"."
|
||||
))]
|
||||
QuotaExceedingMsgBody = 98,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -849,14 +840,6 @@ pub(crate) async fn forwarded(context: &Context) -> String {
|
||||
translated(context, StockMessage::Forwarded).await
|
||||
}
|
||||
|
||||
/// Stock string: `⚠️ Your provider's storage is about to exceed...`.
|
||||
pub(crate) async fn quota_exceeding(context: &Context, highest_usage: u64) -> String {
|
||||
translated(context, StockMessage::QuotaExceedingMsgBody)
|
||||
.await
|
||||
.replace1(format!("{}", highest_usage))
|
||||
.replace("%%", "%")
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Set the stock string for the [StockMessage].
|
||||
///
|
||||
@@ -1040,16 +1023,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_quota_exceeding_stock_str() -> anyhow::Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let str = quota_exceeding(&t, 81).await;
|
||||
assert!(str.contains("81% "));
|
||||
assert!(str.contains("100% "));
|
||||
assert!(!str.contains("%%"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_update_device_chats() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
394
src/summary.rs
394
src/summary.rs
@@ -1,394 +0,0 @@
|
||||
//! # Message summary for chatlist.
|
||||
|
||||
use crate::chat::Chat;
|
||||
use crate::constants::{Chattype, Viewtype, DC_CONTACT_ID_SELF};
|
||||
use crate::contact::Contact;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::dc_truncate;
|
||||
use crate::message::{Message, MessageState};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::{Param, Params};
|
||||
use crate::stock_str;
|
||||
use itertools::Itertools;
|
||||
use std::fmt;
|
||||
|
||||
// In practice, the user additionally cuts the string themselves
|
||||
// pixel-accurate.
|
||||
const SUMMARY_CHARACTERS: usize = 160;
|
||||
|
||||
/// Prefix displayed before message and separated by ":" in the chatlist.
|
||||
#[derive(Debug)]
|
||||
pub enum SummaryPrefix {
|
||||
/// Username.
|
||||
Username(String),
|
||||
|
||||
/// Stock string saying "Draft".
|
||||
Draft(String),
|
||||
|
||||
/// Stock string saying "Me".
|
||||
Me(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for SummaryPrefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
SummaryPrefix::Username(username) => write!(f, "{}", username),
|
||||
SummaryPrefix::Draft(text) => write!(f, "{}", text),
|
||||
SummaryPrefix::Me(text) => write!(f, "{}", text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message summary.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Summary {
|
||||
/// Part displayed before ":", such as an username or a string "Draft".
|
||||
pub prefix: Option<SummaryPrefix>,
|
||||
|
||||
/// Summary text, always present.
|
||||
pub text: String,
|
||||
|
||||
/// Message timestamp.
|
||||
pub timestamp: i64,
|
||||
|
||||
/// Message state.
|
||||
pub state: MessageState,
|
||||
}
|
||||
|
||||
impl Summary {
|
||||
pub async fn new(
|
||||
context: &Context,
|
||||
msg: &Message,
|
||||
chat: &Chat,
|
||||
contact: Option<&Contact>,
|
||||
) -> Self {
|
||||
let prefix = if msg.state == MessageState::OutDraft {
|
||||
Some(SummaryPrefix::Draft(stock_str::draft(context).await))
|
||||
} else if msg.from_id == DC_CONTACT_ID_SELF {
|
||||
if msg.is_info() || chat.is_self_talk() {
|
||||
None
|
||||
} else {
|
||||
Some(SummaryPrefix::Me(stock_str::self_msg(context).await))
|
||||
}
|
||||
} else {
|
||||
match chat.typ {
|
||||
Chattype::Group | Chattype::Mailinglist => {
|
||||
if msg.is_info() || contact.is_none() {
|
||||
None
|
||||
} else {
|
||||
msg.get_override_sender_name()
|
||||
.or_else(|| contact.map(|contact| msg.get_sender_name(contact)))
|
||||
.map(SummaryPrefix::Username)
|
||||
}
|
||||
}
|
||||
Chattype::Single | Chattype::Undefined => None,
|
||||
}
|
||||
};
|
||||
|
||||
let mut text = get_summarytext_by_raw(
|
||||
msg.viewtype,
|
||||
msg.text.as_ref(),
|
||||
msg.is_forwarded(),
|
||||
&msg.param,
|
||||
SUMMARY_CHARACTERS,
|
||||
context,
|
||||
)
|
||||
.await;
|
||||
|
||||
if text.is_empty() && msg.quoted_text().is_some() {
|
||||
text = stock_str::reply_noun(context).await
|
||||
}
|
||||
|
||||
Self {
|
||||
prefix,
|
||||
text,
|
||||
timestamp: msg.get_timestamp(),
|
||||
state: msg.state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a summary text.
|
||||
pub async fn get_summarytext_by_raw(
|
||||
viewtype: Viewtype,
|
||||
text: Option<impl AsRef<str>>,
|
||||
was_forwarded: bool,
|
||||
param: &Params,
|
||||
approx_characters: usize,
|
||||
context: &Context,
|
||||
) -> String {
|
||||
let mut append_text = true;
|
||||
let prefix = match viewtype {
|
||||
Viewtype::Image => stock_str::image(context).await,
|
||||
Viewtype::Gif => stock_str::gif(context).await,
|
||||
Viewtype::Sticker => stock_str::sticker(context).await,
|
||||
Viewtype::Video => stock_str::video(context).await,
|
||||
Viewtype::Voice => stock_str::voice_message(context).await,
|
||||
Viewtype::Audio | Viewtype::File => {
|
||||
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
append_text = false;
|
||||
stock_str::ac_setup_msg_subject(context).await
|
||||
} else {
|
||||
let file_name: String = param
|
||||
.get_path(Param::File, context)
|
||||
.unwrap_or(None)
|
||||
.and_then(|path| {
|
||||
path.file_name()
|
||||
.map(|fname| fname.to_string_lossy().into_owned())
|
||||
})
|
||||
.unwrap_or_else(|| String::from("ErrFileName"));
|
||||
let label = if viewtype == Viewtype::Audio {
|
||||
stock_str::audio(context).await
|
||||
} else {
|
||||
stock_str::file(context).await
|
||||
};
|
||||
format!("{} – {}", label, file_name)
|
||||
}
|
||||
}
|
||||
Viewtype::VideochatInvitation => {
|
||||
append_text = false;
|
||||
stock_str::videochat_invitation(context).await
|
||||
}
|
||||
_ => {
|
||||
if param.get_cmd() != SystemMessage::LocationOnly {
|
||||
"".to_string()
|
||||
} else {
|
||||
append_text = false;
|
||||
stock_str::location(context).await
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if !append_text {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
let summary_content = if let Some(text) = text {
|
||||
if text.as_ref().is_empty() {
|
||||
prefix
|
||||
} else if prefix.is_empty() {
|
||||
dc_truncate(text.as_ref(), approx_characters).to_string()
|
||||
} else {
|
||||
let tmp = format!("{} – {}", prefix, text.as_ref());
|
||||
dc_truncate(&tmp, approx_characters).to_string()
|
||||
}
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
|
||||
let summary = if was_forwarded {
|
||||
let tmp = format!(
|
||||
"{}: {}",
|
||||
stock_str::forwarded(context).await,
|
||||
summary_content
|
||||
);
|
||||
dc_truncate(&tmp, approx_characters).to_string()
|
||||
} else {
|
||||
summary_content
|
||||
};
|
||||
|
||||
summary.split_whitespace().join(" ")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils as test;
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_get_summarytext_by_raw() {
|
||||
let d = test::TestContext::new().await;
|
||||
let ctx = &d.ctx;
|
||||
|
||||
let some_text = Some(" bla \t\n\tbla\n\t".to_string());
|
||||
let empty_text = Some("".to_string());
|
||||
let no_text: Option<String> = None;
|
||||
|
||||
let mut some_file = Params::new();
|
||||
some_file.set(Param::File, "foo.bar");
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Text,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&Params::new(),
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Image,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Image" // file names are not added for images
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Video,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Video" // file names are not added for videos
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), false, &some_file, 50, ctx,)
|
||||
.await,
|
||||
"GIF" // file names are not added for GIFs
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Sticker,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx,
|
||||
)
|
||||
.await,
|
||||
"Sticker" // file names are not added for stickers
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
empty_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx,
|
||||
)
|
||||
.await,
|
||||
"Voice message" // file names are not added for voice messages, empty text is skipped
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Voice message" // file names are not added for voice messages
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Voice,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
no_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar" // file name is added for audio
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
empty_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx,
|
||||
)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Audio,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::File,
|
||||
some_text.as_ref(),
|
||||
false,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"File \u{2013} foo.bar \u{2013} bla bla" // file name is added for files
|
||||
);
|
||||
|
||||
// Forwarded
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::Text,
|
||||
some_text.as_ref(),
|
||||
true,
|
||||
&Params::new(),
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Forwarded: bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(
|
||||
Viewtype::File,
|
||||
some_text.as_ref(),
|
||||
true,
|
||||
&some_file,
|
||||
50,
|
||||
ctx
|
||||
)
|
||||
.await,
|
||||
"Forwarded: File \u{2013} foo.bar \u{2013} bla bla"
|
||||
);
|
||||
|
||||
let mut asm_file = Params::new();
|
||||
asm_file.set(Param::File, "foo.bar");
|
||||
asm_file.set_cmd(SystemMessage::AutocryptSetupMessage);
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), false, &asm_file, 50, ctx)
|
||||
.await,
|
||||
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
//! # Functions to update timestamps.
|
||||
|
||||
use crate::chat::{Chat, ChatId};
|
||||
use crate::contact::Contact;
|
||||
use crate::context::Context;
|
||||
use crate::param::{Param, Params};
|
||||
use anyhow::Result;
|
||||
|
||||
impl Context {
|
||||
/// Updates a contact's timestamp, if reasonable.
|
||||
/// Returns true if the caller shall update the settings belonging to the scope.
|
||||
/// (if we have a ContactId type at some point, the function should go there)
|
||||
pub(crate) async fn update_contacts_timestamp(
|
||||
&self,
|
||||
contact_id: u32,
|
||||
scope: Param,
|
||||
new_timestamp: i64,
|
||||
) -> Result<bool> {
|
||||
let mut contact = Contact::load_from_db(self, contact_id).await?;
|
||||
if contact.param.update_timestamp(scope, new_timestamp)? {
|
||||
contact.update_param(self).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatId {
|
||||
/// Updates a chat id's timestamp on disk, if reasonable.
|
||||
/// Returns true if the caller shall update the settings belonging to the scope.
|
||||
pub(crate) async fn update_timestamp(
|
||||
&self,
|
||||
context: &Context,
|
||||
scope: Param,
|
||||
new_timestamp: i64,
|
||||
) -> Result<bool> {
|
||||
let mut chat = Chat::load_from_db(context, *self).await?;
|
||||
if chat.param.update_timestamp(scope, new_timestamp)? {
|
||||
chat.update_param(context).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Params {
|
||||
/// Updates a param's timestamp in memory, if reasonable.
|
||||
/// Returns true if the caller shall update the settings belonging to the scope.
|
||||
pub(crate) fn update_timestamp(&mut self, scope: Param, new_timestamp: i64) -> Result<bool> {
|
||||
let old_timestamp = self.get_i64(scope).unwrap_or_default();
|
||||
if new_timestamp >= old_timestamp {
|
||||
self.set_i64(scope, new_timestamp);
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::dc_tools::time;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_params_update_timestamp() -> Result<()> {
|
||||
let mut params = Params::new();
|
||||
let ts = time();
|
||||
|
||||
assert!(params.update_timestamp(Param::LastSubject, ts)?);
|
||||
assert!(params.update_timestamp(Param::LastSubject, ts)?); // same timestamp -> update
|
||||
assert!(params.update_timestamp(Param::LastSubject, ts + 10)?);
|
||||
assert!(!params.update_timestamp(Param::LastSubject, ts)?); // `ts` is now too old
|
||||
assert!(!params.update_timestamp(Param::LastSubject, 0)?);
|
||||
assert_eq!(params.get_i64(Param::LastSubject).unwrap(), ts + 10);
|
||||
|
||||
assert!(params.update_timestamp(Param::GroupNameTimestamp, 0)?); // stay unset -> update ...
|
||||
assert!(params.update_timestamp(Param::GroupNameTimestamp, 0)?); // ... also on multiple calls
|
||||
assert_eq!(params.get_i64(Param::GroupNameTimestamp).unwrap(), 0);
|
||||
|
||||
assert!(!params.update_timestamp(Param::AvatarTimestamp, -1)?);
|
||||
assert_eq!(params.get_i64(Param::AvatarTimestamp), None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_out_of_order_subject() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob Authname <bob@example.org>\n\
|
||||
To: alice@example.com\n\
|
||||
Subject: updated subject\n\
|
||||
Message-ID: <msg2@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Date: Sun, 22 Mar 2021 23:37:57 +0000\n\
|
||||
\n\
|
||||
second message\n",
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob Authname <bob@example.org>\n\
|
||||
To: alice@example.com\n\
|
||||
Subject: original subject\n\
|
||||
Message-ID: <msg1@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Date: Sun, 22 Mar 2021 22:37:57 +0000\n\
|
||||
\n\
|
||||
first message\n",
|
||||
"INBOX",
|
||||
2,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat = Chat::load_from_db(&t, msg.chat_id).await?;
|
||||
assert_eq!(
|
||||
chat.param.get(Param::LastSubject).unwrap(),
|
||||
"updated subject"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_out_of_order_group_name() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob Authname <bob@example.org>\n\
|
||||
To: alice@example.com\n\
|
||||
Message-ID: <msg1@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Group-ID: abcde\n\
|
||||
Chat-Group-Name: initial name\n\
|
||||
Date: Sun, 22 Mar 2021 01:00:00 +0000\n\
|
||||
\n\
|
||||
first message\n",
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat = Chat::load_from_db(&t, msg.chat_id).await?;
|
||||
assert_eq!(chat.name, "initial name");
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob Authname <bob@example.org>\n\
|
||||
To: alice@example.com\n\
|
||||
Message-ID: <msg3@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Group-ID: abcde\n\
|
||||
Chat-Group-Name: another name update\n\
|
||||
Chat-Group-Name-Changed: a name update\n\
|
||||
Date: Sun, 22 Mar 2021 03:00:00 +0000\n\
|
||||
\n\
|
||||
third message\n",
|
||||
"INBOX",
|
||||
2,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob Authname <bob@example.org>\n\
|
||||
To: alice@example.com\n\
|
||||
Message-ID: <msg2@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Group-ID: abcde\n\
|
||||
Chat-Group-Name: a name update\n\
|
||||
Chat-Group-Name-Changed: initial name\n\
|
||||
Date: Sun, 22 Mar 2021 02:00:00 +0000\n\
|
||||
\n\
|
||||
second message\n",
|
||||
"INBOX",
|
||||
3,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat = Chat::load_from_db(&t, msg.chat_id).await?;
|
||||
assert_eq!(chat.name, "another name update");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
Return-Path: <bounce-889884_HTML-1111111111-2222222-33333333-4444@bounce.microsoftstoreemail.com>
|
||||
X-Original-To: me@foobar.com
|
||||
Delivered-To: m111111f@dd22222.kasserver.com
|
||||
X-policyd-weight: NOT_IN_SPAMCOP=-1.5 NOT_IN_IX_MANITU=-1.5 CL_IP_EQ_HELO_MX=-3.1 (check from: .microsoftstoreemail. - helo: .merlinux. - helo-domain: .merlinux.) FROM/MX_MATCHES_NOT_HELO(DOMAIN)=1; rate: -5.1
|
||||
Authentication-Results: dd22222.kasserver.com;
|
||||
dkim=pass (1024-bit key; unprotected) header.d=microsoftstoreemail.com header.i=microsoftstore@microsoftstoreemail.com header.b="XV8jLprF";
|
||||
dkim-atps=neutral
|
||||
Received: from merlin.eu (hq6.merlin.eu [95.217.159.152])
|
||||
by dd22222.kasserver.com (Postfix) with ESMTPS id BAD2E53C0541
|
||||
for <me@foobar.com>; Mon, 30 Aug 2021 20:29:57 +0200 (CEST)
|
||||
Received: from [127.0.0.1] (localhost [127.0.0.1])
|
||||
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
|
||||
(No client certificate requested)
|
||||
by merlin.eu (Postfix) with ESMTPS id 40DA541C7E
|
||||
for <me@merlin.eu>; Mon, 30 Aug 2021 20:29:55 +0200 (CEST)
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=200608; d=microsoftstoreemail.com;
|
||||
h=From:To:Subject:Date:List-Help:MIME-Version:List-ID:X-CSA-Complaints:
|
||||
Message-ID:Content-Type; i=microsoftstore@microsoftstoreemail.com;
|
||||
bh=dvrsbCk+3USZNtvsQRvPSo2qpqsG0det56Snu0/Vz7I=;
|
||||
b=XV8jLprFcPv/OmruNBYNRrau26cDZYl4EchN88fJa3q49VpWwom5Pakcw2fkj1i63acBRpyVOBEr
|
||||
M3rZ/p/S3c+n5wkqcQJO/ruWPR16GacnfwYq3zGFIEs5HVjFLbMF+26YuCD7u6GEJC559yD7kWje
|
||||
1RCh9UZqYxBOsdYhkyk=
|
||||
Received: by mta16.microsoftstoreemail.com id h1111111111v for <me@merlin.eu>; Mon, 30 Aug 2021 18:14:50 +0000 (envelope-from <bounce-889884_HTML-1111111111-2222222-33333333-4444@bounce.microsoftstoreemail.com>)
|
||||
From: "Microsoft Store" <microsoftstore@microsoftstoreemail.com>
|
||||
To: <me@merlin.eu>
|
||||
Subject: Notice of Update to Microsoft Store Marketing Disclosure Document
|
||||
Date: Mon, 30 Aug 2021 12:14:50 -0600
|
||||
List-Help: <https://click.microsoftstoreemail.com/subscription_center.aspx?jwt=123.123.123>
|
||||
x-CSA-Compliance-Source: SFMC
|
||||
MIME-Version: 1.0
|
||||
List-ID: <96540.xt.local>
|
||||
X-CSA-Complaints: csa-complaints@eco.de
|
||||
X-SFMC-Stack: 1
|
||||
x-job: 10359607_7641603
|
||||
Message-ID: <9b806dd3-1234-1234-1234-49603f588b2c@ind1s01mta720.xt.local>
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="x28u2yBkExvV=_?:"
|
||||
X-Spamd-Bar: ++++
|
||||
X-Spam-Level: ****
|
||||
X-Rspamd-Server: hq6
|
||||
Authentication-Results: merlin.eu;
|
||||
dkim=pass header.d=microsoftstoreemail.com;
|
||||
dmarc=pass (policy=none) header.from=microsoftstoreemail.com;
|
||||
spf=pass smtp.mailfrom=bounce-889884_HTML-1111111111-2222222-33333333-4444@bounce.microsoftstoreemail.com
|
||||
X-Rspamd-Queue-Id: 40DA541C7E
|
||||
X-Spamd-Result: default: False [4.17 / 15.00];
|
||||
ARC_NA(0.00)[];
|
||||
R_DKIM_ALLOW(-0.20)[microsoftstoreemail.com];
|
||||
FROM_HAS_DN(0.00)[];
|
||||
R_SPF_ALLOW(-0.20)[+ip4:64.132.88.0/23];
|
||||
TO_MATCH_ENVRCPT_ALL(0.00)[];
|
||||
R_BAD_CTE_7BIT(1.05)[7bit,utf8];
|
||||
PREVIOUSLY_DELIVERED(0.00)[me@merlin.eu];
|
||||
TO_DN_NONE(0.00)[];
|
||||
MIME_GOOD(-0.10)[multipart/alternative,text/plain];
|
||||
URI_COUNT_ODD(1.00)[63];
|
||||
RCPT_COUNT_ONE(0.00)[1];
|
||||
MANY_INVISIBLE_PARTS(0.10)[2];
|
||||
IP_SCORE(-0.47)[asn: 22606(-2.23), country: US(-0.10)];
|
||||
DKIM_TRACE(0.00)[microsoftstoreemail.com:+];
|
||||
HTML_SHORT_LINK_IMG_2(1.00)[];
|
||||
DMARC_POLICY_ALLOW(-0.50)[microsoftstoreemail.com,none];
|
||||
MX_GOOD(-0.01)[cached: inbound.s1.exacttarget.com];
|
||||
FORGED_SENDER(0.30)[microsoftstore@microsoftstoreemail.com,bounce-889884_HTML-1111111111-2222222-33333333-4444@bounce.microsoftstoreemail.com];
|
||||
RWL_MAILSPIKE_POSSIBLE(0.00)[177.89.132.64.rep.mailspike.net : 127.0.0.17];
|
||||
RCVD_TLS_LAST(0.00)[];
|
||||
HFILTER_URL_ONLY(2.20)[1];
|
||||
ASN(0.00)[asn:22606, ipnet:64.132.89.0/24, country:US];
|
||||
FROM_NEQ_ENVFROM(0.00)[microsoftstore@microsoftstoreemail.com,bounce-889884_HTML-1111111111-2222222-33333333-4444@bounce.microsoftstoreemail.com];
|
||||
GREYLIST(0.00)[pass,body];
|
||||
RCVD_COUNT_TWO(0.00)[2]
|
||||
X-KasLoop: m111111f
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
--x28u2yBkExvV=_?:
|
||||
Content-Type: text/plain;
|
||||
charset="utf-8"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
en_us
|
||||
|
||||
@templateVersion = "Templates_v01"
|
||||
-->
|
||||
<span style="display: none"></span>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Notice of Update to Microsoft Store Marketing Disclosure Document</title>
|
||||
<style media="all" type="text/css">
|
||||
|
||||
div.preheader {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Wrapper -->
|
||||
<div style="display: none; max-height: 0px; overflow: hidden;">Please read these important updates ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
|
||||
... 100 or so more of these lines - and yes, this is really in a `text/plain` part ...
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--x28u2yBkExvV=_?:
|
||||
Content-Type: text/html;
|
||||
charset="utf-8"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
|
||||
|
||||
<!-- Template Top -->
|
||||
<!--
|
||||
en_us
|
||||
|
||||
@templateVersion = "Templates_v01"
|
||||
-->
|
||||
<span style="display: none"></span>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Notice of Update to Microsoft Store Marketing Disclosure Document</title>
|
||||
<style media="all" type="text/css">
|
||||
|
||||
div.preheader {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Wrapper -->
|
||||
<div style="display: none; max-height: 0px; overflow: hidden;">Please read these important updates ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||
|
||||
|
||||
... 100 or so more of these lines ...
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
--x28u2yBkExvV=_?:--
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
Return-Path: <bounce-155_HTML-111111111-222222-333333333-1234@bounce.angebote.spiegel.de>
|
||||
X-Original-To: me@foobar.com
|
||||
Delivered-To: m000002f@dd22222.kasserver.com
|
||||
X-policyd-weight: NOT_IN_SPAMCOP=-1.5 NOT_IN_IX_MANITU=-1.5 CL_IP_EQ_HELO_IP=-2 (check from: .spiegel. - helo: .mta.angebote.spiegel. - helo-domain: .spiegel.) FROM/MX_MATCHES_HELO(DOMAIN)=-2; rate: -7
|
||||
Authentication-Results: dd22222.kasserver.com;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=angebote.spiegel.de header.i=service@angebote.spiegel.de header.b="EO7/nr7w";
|
||||
dkim-atps=neutral
|
||||
Received: from mta.angebote.spiegel.de (mta.angebote.spiegel.de [13.111.60.76])
|
||||
by dd22222.kasserver.com (Postfix) with ESMTPS id 5667B53C0576
|
||||
for <me@foobar.com>; Mon, 6 Sep 2021 13:27:43 +0200 (CEST)
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=10dkim1; d=angebote.spiegel.de;
|
||||
h=From:To:Subject:Date:List-Unsubscribe:List-Unsubscribe-Post:MIME-Version:
|
||||
Reply-To:List-ID:X-CSA-Complaints:Message-ID:Content-Type;
|
||||
i=service@angebote.spiegel.de;
|
||||
bh=OgZoChKe0M5AaHNMVEyCAZ9jK0q01BajqQhtcqP22/Q=;
|
||||
b=EO7/nr7w54xd68XV1+qUteycqAN63r7HH4QAw60wImD4rE76J4+vWAcf8TNYayM/vPefcM7zcfFk
|
||||
ERwrlR/aT4BoRAWghyQHKgAZ0lwVKpqWGuYe9cF8wjLuYJOwPCoYIiry/GgOSXIVwazlXE2FRN9V
|
||||
Y8m8OStmH3KbVZC55j1Ta6OXMMHEyvwZ+/OHbJ2CLW3jinw84NevP2aMDoI60TidBS5HYVclUT9W
|
||||
IT4bs1o6a659LeVw/ViitQULL7c2P/UWPv0gm5w5IRci6jmdCOzfa+rvFmxSGIlfalTJ/VVG4V+V
|
||||
PlbJl49RrPBuXl62ub6f1EPjFGtbJD8Gy3BliQ==
|
||||
Received: by mta.angebote.spiegel.de id h6ntj42fmd4o for <me@foobar.com>; Mon, 6 Sep 2021 11:27:41 +0000 (envelope-from <bounce-155_HTML-111111111-222222-333333333-1234@bounce.angebote.spiegel.de>)
|
||||
From: "DER SPIEGEL Kundenservice" <service@angebote.spiegel.de>
|
||||
To: <me@foobar.com>
|
||||
Subject: subject here
|
||||
Date: Mon, 06 Sep 2021 05:27:41 -0600
|
||||
List-Unsubscribe: <https://click.angebote.spiegel.de/subscription_center.aspx?jwt=123.123.123-123-123>, <mailto:leave-123-123-123-123-123@leave.angebote.spiegel.de>
|
||||
List-Unsubscribe-Post: List-Unsubscribe=One-Click
|
||||
x-CSA-Compliance-Source: SFMC
|
||||
MIME-Version: 1.0
|
||||
Reply-To: "DER SPIEGEL Kundenservice" <reply-123-123-123-123-123@angebote.spiegel.de>
|
||||
List-ID: <121231234.xt.local>
|
||||
X-CSA-Complaints: csa-complaints@eco.de
|
||||
X-SFMC-Stack: 10
|
||||
x-job: 100006074_884883
|
||||
Message-ID: <123-123-123-123-123@dfw123123a96.xt.local>
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="NwnF3UJtimpn=_?:"
|
||||
X-KasLoop: m000002f
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
--NwnF3UJtimpn=_?:
|
||||
Content-Type: text/plain;
|
||||
charset="utf-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
plain text here
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--NwnF3UJtimpn=_?:
|
||||
Content-Type: text/html;
|
||||
charset="utf-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
<p>html text here</p>
|
||||
|
||||
--NwnF3UJtimpn=_?:--
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
Return-Path: <>
|
||||
Delivered-To: alice@example.org
|
||||
Received: from hq5.merlinux.eu
|
||||
by hq5.merlinux.eu with LMTP
|
||||
id IF/+LHrTFGEQBQAAPzvFDg
|
||||
(envelope-from <>)
|
||||
for <alice@example.org>; Thu, 12 Aug 2021 09:53:30 +0200
|
||||
Received: by hq5.merlinux.eu (Postfix)
|
||||
id 9C87727A0006; Thu, 12 Aug 2021 09:53:30 +0200 (CEST)
|
||||
Date: Thu, 12 Aug 2021 09:53:30 +0200 (CEST)
|
||||
From: MAILER-DAEMON@hq5.merlinux.eu (Mail Delivery System)
|
||||
Subject: Undelivered Mail Returned to Sender
|
||||
To: alice@example.org
|
||||
Auto-Submitted: auto-replied
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/report; report-type=delivery-status;
|
||||
boundary="A82D727A0003.1628754810/hq5.merlinux.eu"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Message-Id: <20210812075330.9C87727A0006@hq5.merlinux.eu>
|
||||
|
||||
This is a MIME-encapsulated message.
|
||||
|
||||
--A82D727A0003.1628754810/hq5.merlinux.eu
|
||||
Content-Description: Notification
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This is the mail system at host hq5.merlinux.eu.
|
||||
|
||||
I'm sorry to have to inform you that your message could not
|
||||
be delivered to one or more recipients. It's attached below.
|
||||
|
||||
For further assistance, please send mail to postmaster.
|
||||
|
||||
If you do so, please include this problem report. You can
|
||||
delete your own text from the attached returned message.
|
||||
|
||||
The mail system
|
||||
|
||||
<bob@example.org>: Host or domain name not found. Name service error for
|
||||
name=echedelyr.tk type=AAAA: Host not found
|
||||
|
||||
--A82D727A0003.1628754810/hq5.merlinux.eu
|
||||
Content-Description: Delivery report
|
||||
Content-Type: message/global-delivery-status
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Reporting-MTA: dns; hq5.merlinux.eu
|
||||
X-Postfix-Queue-ID: A82D727A0003
|
||||
X-Postfix-Sender: rfc822; alice@example.org
|
||||
Arrival-Date: Thu, 12 Aug 2021 09:53:29 +0200 (CEST)
|
||||
|
||||
Final-Recipient: rfc822; bob@example.org
|
||||
Original-Recipient: rfc822;bob@example.org
|
||||
Action: failed
|
||||
Status: 5.4.4
|
||||
Diagnostic-Code: X-Postfix; Host or domain name not found. Name service error
|
||||
for name=echedelyr.tk type=AAAA: Host not found
|
||||
|
||||
--A82D727A0003.1628754810/hq5.merlinux.eu
|
||||
Content-Description: Undelivered Message Headers
|
||||
Content-Type: message/global-headers
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Return-Path: <alice@example.org>
|
||||
Chat-Disposition-Notification-To: alice@example.org
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=testrun.org;
|
||||
s=testrun; t=1628754810;
|
||||
bh=exDToKrRerWMZ62UQVK/RgNiwDDKe+GqF6zGdG64jl8=;
|
||||
h=Subject:References:Date:To:From:From;
|
||||
b=SNTTDdhkppXqimCSPP+cqDvdzmryYwzurtZdN2XkTVQEeqMMdnGvEA9TOgeZpHqi0
|
||||
oHSjJ5oD+eUK1ECnfRuxDF9DmFnK7sbw1MaHxIcAVTLPgHrMxv+2Fjq1nmrerzmr1t
|
||||
z9jOYY8e6gfEBw1uDAfHMmIl4OGuoDSll8haqNF3C2JqdwTtcdtE/w6ERJwSeHhCsR
|
||||
bknan9Rh75Tr46Zh8WVi9YYRVDGFj7+OlL/67Va+Jxl3c4v4EJ5vF6ncxyJupP9eU2
|
||||
qndV+g1BbxsARo663codYZRiGh217AI8DG2HUr0rVOPdvWm1kw/NTkp3BxoHkv5q2a
|
||||
Uak5Jiieur6Hg==
|
||||
Subject: Message from Hocuri
|
||||
Chat-User-Avatar: 0
|
||||
MIME-Version: 1.0
|
||||
References: <Mr.5xqflwt0YFv.IXDFfHauvWx@testrun.org>
|
||||
Date: Thu, 12 Aug 2021 07:53:28 +0000
|
||||
Chat-Version: 1.0
|
||||
Autocrypt: addr=alice@example.org;
|
||||
keydata=xjMEX3tmZxYJKwYBBAHaRw8BAQdAl4LKVKPRqxG1ZXEO8e9s1DZWt6f38wSuJnY0mLSOuf
|
||||
7NFTxob2N1cmkxQHRlc3RydW4ub3JnPsKLBBAWCAAzAhkBBQJfe2ZnAhsDBAsJCAcGFQgJCgsCAxYC
|
||||
ARYhBBmltZLxgqC0SHJPEL7qvlxmQUTNAAoJEL7qvlxmQUTNIucBAJkRclHRG7cWpFbMYW+rspEFIQ
|
||||
j1GTKwriiBpk5ffnroAQC3h/scScpG/EeIPL0y80GRS5BoR1Ium3zrlR92EaijDc44BF97ZmcSCisG
|
||||
AQQBl1UBBQEBB0CmxhyX/NuXIlrl0/fdeEseAv6KCbZ4tV3tIvSvnH1KHgMBCAfCeAQYFggAIAUCX3
|
||||
tmZwIbDBYhBBmltZLxgqC0SHJPEL7qvlxmQUTNAAoJEL7qvlxmQUTNnkYA/3qY+e6PrtR1WT7PiVeZ
|
||||
RIQBkkJjWWSx+lBQ5fNb3e92AQCBEG3OnGy4RrxOqWW2ry7ETP33CJeiwAwCvv4LQlwCCw==
|
||||
Message-ID: <Mr.5xqflwt0YFv.IXDFfHauvWx@testrun.org>
|
||||
To: <bob@example.org>
|
||||
From: Hocuri <alice@example.org>
|
||||
Content-Type: multipart/mixed; boundary="qtFe0wPDNHWVlvqV0B8ymdWmE6ZmKD"
|
||||
|
||||
--A82D727A0003.1628754810/hq5.merlinux.eu--
|
||||
|
||||
Reference in New Issue
Block a user