mirror of
https://github.com/chatmail/core.git
synced 2026-06-25 00:56:39 +03:00
Compare commits
28 Commits
v1.137.0
...
link2xt/sq
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5820c4ce95 | ||
|
|
12ba33d9d4 | ||
|
|
60a7bbc9b5 | ||
|
|
e9f434b562 | ||
|
|
2423cb8175 | ||
|
|
65c9e72bf4 | ||
|
|
ea4d954c77 | ||
|
|
43523a96a2 | ||
|
|
2e2fa9e74f | ||
|
|
e43ffb20a1 | ||
|
|
2f0f247e70 | ||
|
|
5bda4f0c26 | ||
|
|
d39c8a3a19 | ||
|
|
e465415039 | ||
|
|
5cef77b8e6 | ||
|
|
60e733c30c | ||
|
|
8b98816eb9 | ||
|
|
50165b3e35 | ||
|
|
0be8b5a5c4 | ||
|
|
451bb6e9db | ||
|
|
83196d4cb5 | ||
|
|
0003e55ad5 | ||
|
|
02014eda6c | ||
|
|
f1c6cd69e9 | ||
|
|
ace281ff6c | ||
|
|
c9edd525e0 | ||
|
|
3f35b442c3 | ||
|
|
87e9365016 |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.77.0
|
||||
RUSTUP_TOOLCHAIN: 1.77.1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -83,15 +83,15 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust: 1.77.0
|
||||
rust: 1.77.1
|
||||
- os: windows-latest
|
||||
rust: 1.77.0
|
||||
rust: 1.77.1
|
||||
- os: macos-latest
|
||||
rust: 1.77.0
|
||||
rust: 1.77.1
|
||||
|
||||
# Minimum Supported Rust Version = 1.70.0
|
||||
# Minimum Supported Rust Version = 1.77.0
|
||||
- os: ubuntu-latest
|
||||
rust: 1.70.0
|
||||
rust: 1.77.0
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
16
.github/workflows/deltachat-rpc-server.yml
vendored
16
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -99,7 +99,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [arm64-v8a, armeabi-v7a, x86, x86_64]
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -201,18 +201,6 @@ jobs:
|
||||
name: deltachat-rpc-server-armeabi-v7a-android
|
||||
path: deltachat-rpc-server-armeabi-v7a-android.d
|
||||
|
||||
- name: Download Android binary for x86
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-x86-android
|
||||
path: deltachat-rpc-server-x86-android.d
|
||||
|
||||
- name: Download Android binary for x86_64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-android
|
||||
path: deltachat-rpc-server-x86_64-android.d
|
||||
|
||||
- name: Create bin/ directory
|
||||
run: |
|
||||
mkdir -p bin
|
||||
@@ -227,8 +215,6 @@ jobs:
|
||||
mv deltachat-rpc-server-aarch64-macos.d/deltachat-rpc-server bin/deltachat-rpc-server-aarch64-macos
|
||||
mv deltachat-rpc-server-arm64-v8a-android.d/deltachat-rpc-server bin/deltachat-rpc-server-arm64-v8a-android
|
||||
mv deltachat-rpc-server-armeabi-v7a-android.d/deltachat-rpc-server bin/deltachat-rpc-server-armeabi-v7a-android
|
||||
mv deltachat-rpc-server-x86-android.d/deltachat-rpc-server bin/deltachat-rpc-server-x86-android
|
||||
mv deltachat-rpc-server-x86_64-android.d/deltachat-rpc-server bin/deltachat-rpc-server-x86_64-android
|
||||
|
||||
- name: List binaries
|
||||
run: ls -l bin/
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,5 +1,39 @@
|
||||
# Changelog
|
||||
|
||||
## [1.137.2] - 2024-04-05
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] Increase Minimum Supported Rust Version to 1.77.0.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Show reactions in summaries ([#5387](https://github.com/deltachat/deltachat-core-rust/pull/5387)).
|
||||
|
||||
### Tests
|
||||
|
||||
- Test reactions for forwarded messages
|
||||
|
||||
### Refactor
|
||||
|
||||
- `is_probably_private_reply`: Remove reaction-specific code.
|
||||
- Use Rust 1.77.0 support for recursion in async functions.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: Bump rustyline from 13.0.0 to 14.0.0.
|
||||
- Update chrono from 0.4.34 to 0.4.37.
|
||||
- Update from brotli 3.4.0 to brotli 4.0.0.
|
||||
- Upgrade `h2` from 0.4.3 to 0.4.4.
|
||||
- Upgrade `image` from 0.24.9 to 0.25.1.
|
||||
- cargo: Bump fast-socks5 from 0.9.5 to 0.9.6.
|
||||
|
||||
## [1.137.1] - 2024-04-03
|
||||
|
||||
### CI
|
||||
|
||||
- Remove android builds for `x86` and `x86_64`.
|
||||
|
||||
## [1.137.0] - 2024-04-02
|
||||
|
||||
### API-Changes
|
||||
@@ -3836,3 +3870,5 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.136.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.136.4...v1.136.5
|
||||
[1.136.6]: https://github.com/deltachat/deltachat-core-rust/compare/v1.136.5...v1.136.6
|
||||
[1.137.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.136.6...v1.137.0
|
||||
[1.137.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.137.0...v1.137.1
|
||||
[1.137.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.137.1...v1.137.2
|
||||
|
||||
88
Cargo.lock
generated
88
Cargo.lock
generated
@@ -507,9 +507,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "3.4.0"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
|
||||
checksum = "125740193d7fee5cc63ab9e16c2fdc4e07c74ba755cc53b327d6ea029e9fc569"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -518,9 +518,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "2.5.1"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
|
||||
checksum = "65622a320492e09b5e0ac436b14c54ff68199bac392d0e89a6832c4518eea525"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -646,6 +646,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "charset"
|
||||
version = "0.1.3"
|
||||
@@ -658,9 +664,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.34"
|
||||
version = "0.4.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
|
||||
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -1085,7 +1091,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1096,7 +1102,9 @@ dependencies = [
|
||||
"async_zip",
|
||||
"backtrace",
|
||||
"base64 0.21.7",
|
||||
"bitflags 1.3.2",
|
||||
"brotli",
|
||||
"bstr",
|
||||
"chrono",
|
||||
"criterion",
|
||||
"deltachat-time",
|
||||
@@ -1166,7 +1174,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.2.0",
|
||||
@@ -1190,7 +1198,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1205,7 +1213,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1234,7 +1242,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1848,9 +1856,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fast-socks5"
|
||||
version = "0.9.5"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbcc731f3c17a5053e07e6a2290918da75cd8b9b1217b419721f715674ac520c"
|
||||
checksum = "f89f36d4ee12370d30d57b16c7e190950a1a916e7dbbb5fd5a412f5ef913fe84"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -2166,9 +2174,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4"
|
||||
checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -2505,17 +2513,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.9"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
||||
checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"image-webp",
|
||||
"num-traits",
|
||||
"png",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2640,12 +2660,6 @@ version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
@@ -2972,12 +2986,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -4225,9 +4240,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "13.0.0"
|
||||
version = "14.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
|
||||
checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
@@ -4242,7 +4257,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5900,3 +5915,18 @@ dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.77"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
|
||||
[profile.dev]
|
||||
@@ -45,7 +45,9 @@ async-smtp = { version = "0.9", default-features = false, features = ["runtime-t
|
||||
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
|
||||
backtrace = "0.3"
|
||||
base64 = "0.21"
|
||||
brotli = { version = "3.4", default-features=false, features = ["std"] }
|
||||
brotli = { version = "4", default-features=false, features = ["std"] }
|
||||
bitflags = "1.3"
|
||||
bstr = { version = "1.4.0", default-features=false, features = ["std", "alloc"] }
|
||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
||||
@@ -57,7 +59,7 @@ futures-lite = "2.3.0"
|
||||
hex = "0.4.0"
|
||||
hickory-resolver = "0.24"
|
||||
humansize = "2"
|
||||
image = { version = "0.24.9", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
image = { version = "0.25.1", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh = { version = "0.4.2", default-features = false }
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -7296,6 +7296,22 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced by the provider's domain.
|
||||
#define DC_STR_INVALID_UNENCRYPTED_MAIL 174
|
||||
|
||||
/// "You reacted %1$s to '%2$s'"
|
||||
///
|
||||
/// `%1$s` will be replaced by the reaction, usually an emoji
|
||||
/// `%2$s` will be replaced by the summary of the message the reaction refers to
|
||||
///
|
||||
/// Used in summaries.
|
||||
#define DC_STR_YOU_REACTED 176
|
||||
|
||||
/// "%1$s reacted %2$s to '%3$s'"
|
||||
///
|
||||
/// `%1$s` will be replaced by the name the contact who reacted
|
||||
/// `%2$s` will be replaced by the reaction, usually an emoji
|
||||
/// `%3$s` will be replaced by the summary of the message the reaction refers to
|
||||
///
|
||||
/// Used in summaries.
|
||||
#define DC_STR_REACTED_BY 177
|
||||
|
||||
/**
|
||||
* @}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
|
||||
@@ -53,5 +53,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.137.0"
|
||||
"version": "1.137.2"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
@@ -13,7 +13,7 @@ dirs = "5"
|
||||
log = "0.4.21"
|
||||
pretty_env_logger = "0.5"
|
||||
rusqlite = "0.31"
|
||||
rustyline = "13"
|
||||
rustyline = "14"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -339,6 +339,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
export-keys\n\
|
||||
import-keys\n\
|
||||
export-setup\n\
|
||||
dump <filename>\n\n
|
||||
read <filename>\n\n
|
||||
poke [<eml-file>|<folder>|<addr> <key-file>]\n\
|
||||
reset <flags>\n\
|
||||
stop\n\
|
||||
@@ -514,6 +516,14 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
&setup_code,
|
||||
);
|
||||
}
|
||||
"dump" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <filename> missing.");
|
||||
serialize_database(&context, arg1).await?;
|
||||
}
|
||||
"read" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <filename> missing.");
|
||||
deserialize_database(&context, arg1).await?;
|
||||
}
|
||||
"poke" => {
|
||||
ensure!(poke_spec(&context, Some(arg1)).await, "Poke failed");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -76,6 +76,12 @@ class Account:
|
||||
"""Get self avatar."""
|
||||
return self.get_config("selfavatar")
|
||||
|
||||
def check_qr(self, qr):
|
||||
return self._rpc.check_qr(self.id, qr)
|
||||
|
||||
def set_config_from_qr(self, qr: str):
|
||||
self._rpc.set_config_from_qr(self.id, qr)
|
||||
|
||||
@futuremethod
|
||||
def configure(self):
|
||||
"""Configure an account."""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from deltachat_rpc_client import Chat, SpecialContactId
|
||||
from deltachat_rpc_client import Chat, EventType, SpecialContactId
|
||||
|
||||
|
||||
def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
||||
@@ -579,3 +579,40 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
|
||||
# ac1 is still "not verified" for ac2 due to inconsistent state.
|
||||
assert not ac2_contact_ac1.get_snapshot().is_verified
|
||||
|
||||
|
||||
def test_withdraw_securejoin_qr(acfactory):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
logging.info("Alice creates a verified group")
|
||||
alice_chat = alice.create_group("Verified group", protect=True)
|
||||
assert alice_chat.get_basic_snapshot().is_protected
|
||||
logging.info("Bob joins verified group")
|
||||
|
||||
qr_code, _svg = alice_chat.get_qr_code()
|
||||
bob_chat = bob.secure_join(qr_code)
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
|
||||
assert snapshot.chat.get_basic_snapshot().is_protected
|
||||
bob_chat.leave()
|
||||
|
||||
snapshot = alice.get_message_by_id(alice.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Group left by {}.".format(bob.get_config("addr"))
|
||||
|
||||
logging.info("Alice withdraws QR code.")
|
||||
qr = alice.check_qr(qr_code)
|
||||
assert qr["kind"] == "withdrawVerifyGroup"
|
||||
alice.set_config_from_qr(qr_code)
|
||||
|
||||
logging.info("Bob scans withdrawn QR code.")
|
||||
bob_chat = bob.secure_join(qr_code)
|
||||
|
||||
logging.info("Bob scanned withdrawn QR code")
|
||||
while True:
|
||||
event = alice.wait_for_event()
|
||||
if event.kind == EventType.MSGS_CHANGED and event.chat_id != 0:
|
||||
break
|
||||
snapshot = alice.get_message_by_id(event.msg_id).get_snapshot()
|
||||
assert snapshot.text == "Cannot establish guaranteed end-to-end encryption with {}".format(bob.get_config("addr"))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -263,14 +263,6 @@
|
||||
cc = "aarch64-linux-android21-clang";
|
||||
rustTarget = "aarch64-linux-android";
|
||||
};
|
||||
x86 = {
|
||||
cc = "i686-linux-android19-clang";
|
||||
rustTarget = "i686-linux-android";
|
||||
};
|
||||
x86_64 = {
|
||||
cc = "x86_64-linux-android21-clang";
|
||||
rustTarget = "x86_64-linux-android";
|
||||
};
|
||||
};
|
||||
|
||||
mkAndroidRustPackage = arch: packageName:
|
||||
|
||||
201
fuzz/Cargo.lock
generated
201
fuzz/Cargo.lock
generated
@@ -339,6 +339,12 @@ version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.3"
|
||||
@@ -515,9 +521,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "3.4.0"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
|
||||
checksum = "125740193d7fee5cc63ab9e16c2fdc4e07c74ba755cc53b327d6ea029e9fc569"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -526,9 +532,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "2.5.1"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
|
||||
checksum = "65622a320492e09b5e0ac436b14c54ff68199bac392d0e89a6832c4518eea525"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -953,7 +959,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.136.0"
|
||||
version = "1.137.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.1.1",
|
||||
@@ -989,6 +995,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"openssl-src",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"pgp",
|
||||
@@ -1785,9 +1792,9 @@ checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
|
||||
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
|
||||
dependencies = [
|
||||
"fastrand 2.0.1",
|
||||
"futures-core",
|
||||
@@ -1912,9 +1919,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.24"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -2051,9 +2058,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -2062,12 +2069,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@@ -2077,12 +2096,6 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.2"
|
||||
@@ -2094,39 +2107,58 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.23"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
||||
checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.4.7",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.4",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2180,17 +2212,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.9"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
||||
checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"image-webp",
|
||||
"num-traits",
|
||||
"png",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2314,12 +2358,6 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
@@ -3480,11 +3518,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.24"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
|
||||
checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"base64 0.22.0",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
@@ -3492,8 +3530,10 @@ dependencies = [
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -3502,7 +3542,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 2.1.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@@ -3515,7 +3555,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.50.0",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3724,7 +3764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 1.0.2",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
@@ -3738,6 +3778,22 @@ dependencies = [
|
||||
"base64 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.7"
|
||||
@@ -4055,9 +4111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
@@ -4294,22 +4350,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.38"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.38"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4442,9 +4498,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@@ -4514,6 +4570,28 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
@@ -5169,9 +5247,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
@@ -5265,3 +5343,18 @@ dependencies = [
|
||||
"syn 1.0.107",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
@@ -257,6 +257,7 @@ module.exports = {
|
||||
DC_STR_PARTIAL_DOWNLOAD_MSG_BODY: 99,
|
||||
DC_STR_PART_OF_TOTAL_USED: 116,
|
||||
DC_STR_QUOTA_EXCEEDING_MSG_BODY: 98,
|
||||
DC_STR_REACTED_BY: 177,
|
||||
DC_STR_READRCPT: 31,
|
||||
DC_STR_READRCPT_MAILBODY: 32,
|
||||
DC_STR_REMOVE_MEMBER_BY_OTHER: 131,
|
||||
@@ -284,6 +285,7 @@ module.exports = {
|
||||
DC_STR_VIDEOCHAT_INVITE_MSG_BODY: 83,
|
||||
DC_STR_VOICEMESSAGE: 7,
|
||||
DC_STR_WELCOME_MESSAGE: 71,
|
||||
DC_STR_YOU_REACTED: 176,
|
||||
DC_TEXT1_DRAFT: 1,
|
||||
DC_TEXT1_SELF: 3,
|
||||
DC_TEXT1_USERNAME: 2,
|
||||
|
||||
@@ -257,6 +257,7 @@ export enum C {
|
||||
DC_STR_PARTIAL_DOWNLOAD_MSG_BODY = 99,
|
||||
DC_STR_PART_OF_TOTAL_USED = 116,
|
||||
DC_STR_QUOTA_EXCEEDING_MSG_BODY = 98,
|
||||
DC_STR_REACTED_BY = 177,
|
||||
DC_STR_READRCPT = 31,
|
||||
DC_STR_READRCPT_MAILBODY = 32,
|
||||
DC_STR_REMOVE_MEMBER_BY_OTHER = 131,
|
||||
@@ -284,6 +285,7 @@ export enum C {
|
||||
DC_STR_VIDEOCHAT_INVITE_MSG_BODY = 83,
|
||||
DC_STR_VOICEMESSAGE = 7,
|
||||
DC_STR_WELCOME_MESSAGE = 71,
|
||||
DC_STR_YOU_REACTED = 176,
|
||||
DC_TEXT1_DRAFT = 1,
|
||||
DC_TEXT1_SELF = 3,
|
||||
DC_TEXT1_USERNAME = 2,
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
|
||||
},
|
||||
"types": "node/dist/index.d.ts",
|
||||
"version": "1.137.0"
|
||||
"version": "1.137.2"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "1.137.0"
|
||||
version = "1.137.2"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.7"
|
||||
|
||||
@@ -1 +1 @@
|
||||
2024-04-02
|
||||
2024-04-05
|
||||
@@ -7,7 +7,7 @@ set -euo pipefail
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
RUST_VERSION=1.77.0
|
||||
RUST_VERSION=1.77.1
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
test -f "/lib/libc.musl-$ARCH.so.1" && LIBC=musl || LIBC=gnu
|
||||
|
||||
29
src/blob.rs
29
src/blob.rs
@@ -11,9 +11,8 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::{format_err, Context as _, Result};
|
||||
use base64::Engine as _;
|
||||
use futures::StreamExt;
|
||||
use image::{
|
||||
DynamicImage, GenericImage, GenericImageView, ImageFormat, ImageOutputFormat, Pixel, Rgba,
|
||||
};
|
||||
use image::codecs::jpeg::JpegEncoder;
|
||||
use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat, Pixel, Rgba};
|
||||
use num_traits::FromPrimitive;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::{fs, io};
|
||||
@@ -37,6 +36,12 @@ pub struct BlobObject<'a> {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ImageOutputFormat {
|
||||
Png,
|
||||
Jpeg { quality: u8 },
|
||||
}
|
||||
|
||||
impl<'a> BlobObject<'a> {
|
||||
/// Creates a new blob object with a unique name.
|
||||
///
|
||||
@@ -457,9 +462,13 @@ impl<'a> BlobObject<'a> {
|
||||
Ok(ImageFormat::Png) if !exceeds_max_bytes => ImageOutputFormat::Png,
|
||||
Ok(ImageFormat::Jpeg) => {
|
||||
add_white_bg = false;
|
||||
ImageOutputFormat::Jpeg(jpeg_quality)
|
||||
ImageOutputFormat::Jpeg {
|
||||
quality: jpeg_quality,
|
||||
}
|
||||
}
|
||||
_ => ImageOutputFormat::Jpeg(jpeg_quality),
|
||||
_ => ImageOutputFormat::Jpeg {
|
||||
quality: jpeg_quality,
|
||||
},
|
||||
};
|
||||
// We need to rewrite images with Exif to remove metadata such as location,
|
||||
// camera model, etc.
|
||||
@@ -530,7 +539,7 @@ impl<'a> BlobObject<'a> {
|
||||
if do_scale || exif.is_some() {
|
||||
// The file format is JPEG/PNG now, we may have to change the file extension
|
||||
if !matches!(fmt, Ok(ImageFormat::Jpeg))
|
||||
&& matches!(ofmt, ImageOutputFormat::Jpeg(_))
|
||||
&& matches!(ofmt, ImageOutputFormat::Jpeg { .. })
|
||||
{
|
||||
blob_abs = blob_abs.with_extension("jpg");
|
||||
let file_name = blob_abs.file_name().context("No image file name (???)")?;
|
||||
@@ -685,7 +694,13 @@ fn encode_img(
|
||||
) -> anyhow::Result<()> {
|
||||
encoded.clear();
|
||||
let mut buf = Cursor::new(encoded);
|
||||
img.write_to(&mut buf, fmt)?;
|
||||
match fmt {
|
||||
ImageOutputFormat::Png => img.write_to(&mut buf, ImageFormat::Png)?,
|
||||
ImageOutputFormat::Jpeg { quality } => {
|
||||
let encoder = JpegEncoder::new_with_quality(&mut buf, quality);
|
||||
img.write_with_encoder(encoder)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2698,7 +2698,9 @@ async fn send_msg_inner(context: &Context, chat_id: ChatId, msg: &mut Message) -
|
||||
}
|
||||
|
||||
if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
|
||||
context.emit_msgs_changed(msg.chat_id, msg.id);
|
||||
if !msg.hidden {
|
||||
context.emit_msgs_changed(msg.chat_id, msg.id);
|
||||
}
|
||||
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
context.emit_event(EventType::LocationChanged(Some(ContactId::SELF)));
|
||||
|
||||
@@ -416,7 +416,7 @@ impl Chatlist {
|
||||
if chat.id.is_archived_link() {
|
||||
Ok(Default::default())
|
||||
} else if let Some(lastmsg) = lastmsg.filter(|msg| msg.from_id != ContactId::UNDEFINED) {
|
||||
Ok(Summary::new(context, &lastmsg, chat, lastcontact.as_ref()).await)
|
||||
Summary::new(context, &lastmsg, chat, lastcontact.as_ref()).await
|
||||
} else {
|
||||
Ok(Summary {
|
||||
text: stock_str::no_messages(context).await,
|
||||
|
||||
166
src/html.rs
166
src/html.rs
@@ -7,12 +7,8 @@
|
||||
//! `MsgId.get_html()` will return HTML -
|
||||
//! this allows nice quoting, handling linebreaks properly etc.
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use base64::Engine as _;
|
||||
use futures::future::FutureExt;
|
||||
use lettre_email::mime::Mime;
|
||||
use lettre_email::PartBuilder;
|
||||
use mailparse::ParsedContentType;
|
||||
@@ -116,119 +112,109 @@ impl HtmlMsgParser {
|
||||
/// Usually, there is at most one plain-text and one HTML-text part,
|
||||
/// multiple plain-text parts might be used for mailinglist-footers,
|
||||
/// therefore we use the first one.
|
||||
fn collect_texts_recursive<'a>(
|
||||
async fn collect_texts_recursive<'a>(
|
||||
&'a mut self,
|
||||
mail: &'a mailparse::ParsedMail<'a>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
|
||||
// Boxed future to deal with recursion
|
||||
async move {
|
||||
match get_mime_multipart_type(&mail.ctype) {
|
||||
MimeMultipartType::Multiple => {
|
||||
for cur_data in &mail.subparts {
|
||||
self.collect_texts_recursive(cur_data).await?
|
||||
}
|
||||
Ok(())
|
||||
) -> Result<()> {
|
||||
match get_mime_multipart_type(&mail.ctype) {
|
||||
MimeMultipartType::Multiple => {
|
||||
for cur_data in &mail.subparts {
|
||||
Box::pin(self.collect_texts_recursive(cur_data)).await?
|
||||
}
|
||||
MimeMultipartType::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
|
||||
self.collect_texts_recursive(&mail).await
|
||||
Ok(())
|
||||
}
|
||||
MimeMultipartType::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
MimeMultipartType::Single => {
|
||||
let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
|
||||
if mimetype == mime::TEXT_HTML {
|
||||
if self.html.is_empty() {
|
||||
if let Ok(decoded_data) = mail.get_body() {
|
||||
self.html = decoded_data;
|
||||
}
|
||||
}
|
||||
} else if mimetype == mime::TEXT_PLAIN && self.plain.is_none() {
|
||||
let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
|
||||
Box::pin(self.collect_texts_recursive(&mail)).await
|
||||
}
|
||||
MimeMultipartType::Single => {
|
||||
let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
|
||||
if mimetype == mime::TEXT_HTML {
|
||||
if self.html.is_empty() {
|
||||
if let Ok(decoded_data) = mail.get_body() {
|
||||
self.plain = Some(PlainText {
|
||||
text: decoded_data,
|
||||
flowed: if let Some(format) = mail.ctype.params.get("format") {
|
||||
format.as_str().to_ascii_lowercase() == "flowed"
|
||||
} else {
|
||||
false
|
||||
},
|
||||
delsp: if let Some(delsp) = mail.ctype.params.get("delsp") {
|
||||
delsp.as_str().to_ascii_lowercase() == "yes"
|
||||
} else {
|
||||
false
|
||||
},
|
||||
});
|
||||
self.html = decoded_data;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else if mimetype == mime::TEXT_PLAIN && self.plain.is_none() {
|
||||
if let Ok(decoded_data) = mail.get_body() {
|
||||
self.plain = Some(PlainText {
|
||||
text: decoded_data,
|
||||
flowed: if let Some(format) = mail.ctype.params.get("format") {
|
||||
format.as_str().to_ascii_lowercase() == "flowed"
|
||||
} else {
|
||||
false
|
||||
},
|
||||
delsp: if let Some(delsp) = mail.ctype.params.get("delsp") {
|
||||
delsp.as_str().to_ascii_lowercase() == "yes"
|
||||
} else {
|
||||
false
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
/// Replace cid:-protocol by the data:-protocol where appropriate.
|
||||
/// This allows the final html-file to be self-contained.
|
||||
fn cid_to_data_recursive<'a>(
|
||||
async fn cid_to_data_recursive<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
mail: &'a mailparse::ParsedMail<'a>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
|
||||
// Boxed future to deal with recursion
|
||||
async move {
|
||||
match get_mime_multipart_type(&mail.ctype) {
|
||||
MimeMultipartType::Multiple => {
|
||||
for cur_data in &mail.subparts {
|
||||
self.cid_to_data_recursive(context, cur_data).await?;
|
||||
}
|
||||
Ok(())
|
||||
) -> Result<()> {
|
||||
match get_mime_multipart_type(&mail.ctype) {
|
||||
MimeMultipartType::Multiple => {
|
||||
for cur_data in &mail.subparts {
|
||||
Box::pin(self.cid_to_data_recursive(context, cur_data)).await?;
|
||||
}
|
||||
MimeMultipartType::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
|
||||
self.cid_to_data_recursive(context, &mail).await
|
||||
Ok(())
|
||||
}
|
||||
MimeMultipartType::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
MimeMultipartType::Single => {
|
||||
let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
|
||||
if mimetype.type_() == mime::IMAGE {
|
||||
if let Some(cid) = mail.headers.get_header_value(HeaderDef::ContentId) {
|
||||
if let Ok(cid) = parse_message_id(&cid) {
|
||||
if let Ok(replacement) = mimepart_to_data_url(mail) {
|
||||
let re_string = format!(
|
||||
"(<img[^>]*src[^>]*=[^>]*)(cid:{})([^>]*>)",
|
||||
regex::escape(&cid)
|
||||
);
|
||||
match regex::Regex::new(&re_string) {
|
||||
Ok(re) => {
|
||||
self.html = re
|
||||
.replace_all(
|
||||
&self.html,
|
||||
format!("${{1}}{replacement}${{3}}").as_str(),
|
||||
)
|
||||
.as_ref()
|
||||
.to_string()
|
||||
}
|
||||
Err(e) => warn!(
|
||||
context,
|
||||
"Cannot create regex for cid: {} throws {}",
|
||||
re_string,
|
||||
e
|
||||
),
|
||||
let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
|
||||
Box::pin(self.cid_to_data_recursive(context, &mail)).await
|
||||
}
|
||||
MimeMultipartType::Single => {
|
||||
let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
|
||||
if mimetype.type_() == mime::IMAGE {
|
||||
if let Some(cid) = mail.headers.get_header_value(HeaderDef::ContentId) {
|
||||
if let Ok(cid) = parse_message_id(&cid) {
|
||||
if let Ok(replacement) = mimepart_to_data_url(mail) {
|
||||
let re_string = format!(
|
||||
"(<img[^>]*src[^>]*=[^>]*)(cid:{})([^>]*>)",
|
||||
regex::escape(&cid)
|
||||
);
|
||||
match regex::Regex::new(&re_string) {
|
||||
Ok(re) => {
|
||||
self.html = re
|
||||
.replace_all(
|
||||
&self.html,
|
||||
format!("${{1}}{replacement}${{3}}").as_str(),
|
||||
)
|
||||
.as_ref()
|
||||
.to_string()
|
||||
}
|
||||
Err(e) => warn!(
|
||||
context,
|
||||
"Cannot create regex for cid: {} throws {}", re_string, e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
src/imex.rs
17
src/imex.rs
@@ -10,6 +10,7 @@ use futures::StreamExt;
|
||||
use futures_lite::FutureExt;
|
||||
use rand::{thread_rng, Rng};
|
||||
use tokio::fs::{self, File};
|
||||
use tokio::io::BufWriter;
|
||||
use tokio_tar::Archive;
|
||||
|
||||
use crate::blob::{BlobDirContents, BlobObject};
|
||||
@@ -499,7 +500,7 @@ fn get_next_backup_path(
|
||||
backup_time: i64,
|
||||
) -> Result<(PathBuf, PathBuf, PathBuf)> {
|
||||
let folder = PathBuf::from(folder);
|
||||
let stem = chrono::NaiveDateTime::from_timestamp_opt(backup_time, 0)
|
||||
let stem = chrono::DateTime::<chrono::Utc>::from_timestamp(backup_time, 0)
|
||||
.context("can't get next backup path")?
|
||||
// Don't change this file name format, in `dc_imex_has_backup` we use string comparison to determine which backup is newer:
|
||||
.format("delta-chat-backup-%Y-%m-%d")
|
||||
@@ -816,6 +817,20 @@ async fn export_database(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Serializes the database to a file.
|
||||
pub async fn serialize_database(context: &Context, filename: &str) -> Result<()> {
|
||||
let file = File::create(filename).await?;
|
||||
context.sql.serialize(BufWriter::new(file)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deserializes the database from a file.
|
||||
pub async fn deserialize_database(context: &Context, filename: &str) -> Result<()> {
|
||||
let file = File::open(filename).await?;
|
||||
context.sql.deserialize(file).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -137,7 +137,7 @@ impl Kml {
|
||||
// 0 4 7 10 13 16 19
|
||||
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
|
||||
Ok(res) => {
|
||||
self.curr.timestamp = res.timestamp();
|
||||
self.curr.timestamp = res.and_utc().timestamp();
|
||||
let now = time();
|
||||
if self.curr.timestamp > now {
|
||||
self.curr.timestamp = now;
|
||||
@@ -540,7 +540,7 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<Option<(Strin
|
||||
|
||||
fn get_kml_timestamp(utc: i64) -> String {
|
||||
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
||||
chrono::NaiveDateTime::from_timestamp_opt(utc, 0)
|
||||
chrono::DateTime::<chrono::Utc>::from_timestamp(utc, 0)
|
||||
.unwrap()
|
||||
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||
.to_string()
|
||||
|
||||
@@ -459,7 +459,19 @@ impl Message {
|
||||
}
|
||||
|
||||
/// Loads message with given ID from the database.
|
||||
///
|
||||
/// Returns an error if the message does not exist.
|
||||
pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
|
||||
let message = Self::load_from_db_optional(context, id)
|
||||
.await?
|
||||
.context("Message {id} does not exist")?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Loads message with given ID from the database.
|
||||
///
|
||||
/// Returns `None` if the message does not exist.
|
||||
pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
|
||||
ensure!(
|
||||
!id.is_special(),
|
||||
"Can not load special message ID {} from DB",
|
||||
@@ -467,7 +479,7 @@ impl Message {
|
||||
);
|
||||
let msg = context
|
||||
.sql
|
||||
.query_row(
|
||||
.query_row_optional(
|
||||
concat!(
|
||||
"SELECT",
|
||||
" m.id AS id,",
|
||||
@@ -796,7 +808,7 @@ impl Message {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Summary::new(context, self, chat, contact.as_ref()).await)
|
||||
Summary::new(context, self, chat, contact.as_ref()).await
|
||||
}
|
||||
|
||||
// It's a little unfortunate that the UI has to first call `dc_msg_get_override_sender_name` and then if it was `NULL`, call
|
||||
|
||||
@@ -575,11 +575,7 @@ impl<'a> MimeFactory<'a> {
|
||||
.protected
|
||||
.push(Header::new("Subject".into(), encoded_subject));
|
||||
|
||||
let date = chrono::Utc
|
||||
.from_local_datetime(
|
||||
&chrono::NaiveDateTime::from_timestamp_opt(self.timestamp, 0)
|
||||
.context("can't convert timestamp to NativeDateTime")?,
|
||||
)
|
||||
let date = chrono::DateTime::<chrono::Utc>::from_timestamp(self.timestamp, 0)
|
||||
.unwrap()
|
||||
.to_rfc2822();
|
||||
headers.unprotected.push(Header::new("Date".into(), date));
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
use std::cmp::min;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::str;
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
@@ -818,59 +816,53 @@ impl MimeMessage {
|
||||
self.headers.get(headerdef.get_headername())
|
||||
}
|
||||
|
||||
fn parse_mime_recursive<'a>(
|
||||
async fn parse_mime_recursive<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
mail: &'a mailparse::ParsedMail<'a>,
|
||||
is_related: bool,
|
||||
) -> Pin<Box<dyn Future<Output = Result<bool>> + 'a + Send>> {
|
||||
use futures::future::FutureExt;
|
||||
) -> Result<bool> {
|
||||
enum MimeS {
|
||||
Multiple,
|
||||
Single,
|
||||
Message,
|
||||
}
|
||||
|
||||
// Boxed future to deal with recursion
|
||||
async move {
|
||||
enum MimeS {
|
||||
Multiple,
|
||||
Single,
|
||||
Message,
|
||||
}
|
||||
let mimetype = mail.ctype.mimetype.to_lowercase();
|
||||
|
||||
let mimetype = mail.ctype.mimetype.to_lowercase();
|
||||
|
||||
let m = if mimetype.starts_with("multipart") {
|
||||
if mail.ctype.params.contains_key("boundary") {
|
||||
MimeS::Multiple
|
||||
} else {
|
||||
MimeS::Single
|
||||
}
|
||||
} else if mimetype.starts_with("message") {
|
||||
if mimetype == "message/rfc822" && !is_attachment_disposition(mail) {
|
||||
MimeS::Message
|
||||
} else {
|
||||
MimeS::Single
|
||||
}
|
||||
let m = if mimetype.starts_with("multipart") {
|
||||
if mail.ctype.params.contains_key("boundary") {
|
||||
MimeS::Multiple
|
||||
} else {
|
||||
MimeS::Single
|
||||
};
|
||||
}
|
||||
} else if mimetype.starts_with("message") {
|
||||
if mimetype == "message/rfc822" && !is_attachment_disposition(mail) {
|
||||
MimeS::Message
|
||||
} else {
|
||||
MimeS::Single
|
||||
}
|
||||
} else {
|
||||
MimeS::Single
|
||||
};
|
||||
|
||||
let is_related = is_related || mimetype == "multipart/related";
|
||||
match m {
|
||||
MimeS::Multiple => self.handle_multiple(context, mail, is_related).await,
|
||||
MimeS::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
|
||||
let is_related = is_related || mimetype == "multipart/related";
|
||||
match m {
|
||||
MimeS::Multiple => Box::pin(self.handle_multiple(context, mail, is_related)).await,
|
||||
MimeS::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
|
||||
|
||||
self.parse_mime_recursive(context, &mail, is_related).await
|
||||
}
|
||||
MimeS::Single => {
|
||||
self.add_single_part_if_known(context, mail, is_related)
|
||||
.await
|
||||
}
|
||||
Box::pin(self.parse_mime_recursive(context, &mail, is_related)).await
|
||||
}
|
||||
MimeS::Single => {
|
||||
self.add_single_part_if_known(context, mail, is_related)
|
||||
.await
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn handle_multiple(
|
||||
|
||||
@@ -64,6 +64,15 @@ pub enum Param {
|
||||
/// For Messages: the message is a reaction.
|
||||
Reaction = b'x',
|
||||
|
||||
/// For Chats: the timestamp of the last reaction.
|
||||
LastReactionTimestamp = b'y',
|
||||
|
||||
/// For Chats: Message ID of the last reaction.
|
||||
LastReactionMsgId = b'Y',
|
||||
|
||||
/// For Chats: Contact ID of the last reaction.
|
||||
LastReactionContactId = b'1',
|
||||
|
||||
/// For Messages: a message with "Auto-Submitted: auto-generated" header ("bot").
|
||||
Bot = b'b',
|
||||
|
||||
|
||||
229
src/reaction.rs
229
src/reaction.rs
@@ -20,11 +20,12 @@ use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::chat::{send_msg, ChatId};
|
||||
use crate::chat::{send_msg, Chat, ChatId};
|
||||
use crate::contact::ContactId;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::message::{rfc724_mid_exists, Message, MsgId, Viewtype};
|
||||
use crate::param::Param;
|
||||
|
||||
/// A single reaction consisting of multiple emoji sequences.
|
||||
///
|
||||
@@ -170,6 +171,7 @@ async fn set_msg_id_reaction(
|
||||
msg_id: MsgId,
|
||||
chat_id: ChatId,
|
||||
contact_id: ContactId,
|
||||
timestamp: i64,
|
||||
reaction: Reaction,
|
||||
) -> Result<()> {
|
||||
if reaction.is_empty() {
|
||||
@@ -194,6 +196,17 @@ async fn set_msg_id_reaction(
|
||||
(msg_id, contact_id, reaction.as_str()),
|
||||
)
|
||||
.await?;
|
||||
let mut chat = Chat::load_from_db(context, chat_id).await?;
|
||||
if chat
|
||||
.param
|
||||
.update_timestamp(Param::LastReactionTimestamp, timestamp)?
|
||||
{
|
||||
chat.param
|
||||
.set_i64(Param::LastReactionMsgId, i64::from(msg_id.to_u32()));
|
||||
chat.param
|
||||
.set_i64(Param::LastReactionContactId, i64::from(contact_id.to_u32()));
|
||||
chat.update_param(context).await?;
|
||||
}
|
||||
}
|
||||
|
||||
context.emit_event(EventType::ReactionsChanged {
|
||||
@@ -223,7 +236,15 @@ pub async fn send_reaction(context: &Context, msg_id: MsgId, reaction: &str) ->
|
||||
let reaction_msg_id = send_msg(context, chat_id, &mut reaction_msg).await?;
|
||||
|
||||
// Only set reaction if we successfully sent the message.
|
||||
set_msg_id_reaction(context, msg_id, msg.chat_id, ContactId::SELF, reaction).await?;
|
||||
set_msg_id_reaction(
|
||||
context,
|
||||
msg_id,
|
||||
msg.chat_id,
|
||||
ContactId::SELF,
|
||||
reaction_msg.timestamp_sort,
|
||||
reaction,
|
||||
)
|
||||
.await?;
|
||||
Ok(reaction_msg_id)
|
||||
}
|
||||
|
||||
@@ -250,10 +271,11 @@ pub(crate) async fn set_msg_reaction(
|
||||
in_reply_to: &str,
|
||||
chat_id: ChatId,
|
||||
contact_id: ContactId,
|
||||
timestamp: i64,
|
||||
reaction: Reaction,
|
||||
) -> Result<()> {
|
||||
if let Some((msg_id, _)) = rfc724_mid_exists(context, in_reply_to).await? {
|
||||
set_msg_id_reaction(context, msg_id, chat_id, contact_id, reaction).await
|
||||
set_msg_id_reaction(context, msg_id, chat_id, contact_id, timestamp, reaction).await
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
@@ -307,18 +329,72 @@ pub async fn get_msg_reactions(context: &Context, msg_id: MsgId) -> Result<React
|
||||
Ok(Reactions { reactions })
|
||||
}
|
||||
|
||||
impl Chat {
|
||||
/// Check if there is a reaction newer than the given timestamp.
|
||||
///
|
||||
/// If so, reaction details are returned and can be used to create a summary string.
|
||||
pub async fn get_last_reaction_if_newer_than(
|
||||
&self,
|
||||
context: &Context,
|
||||
timestamp: i64,
|
||||
) -> Result<Option<(Message, ContactId, String)>> {
|
||||
if let Some(reaction_timestamp) = self.param.get_i64(Param::LastReactionTimestamp) {
|
||||
if reaction_timestamp > timestamp {
|
||||
let reaction_msg_id = MsgId::new(
|
||||
self.param
|
||||
.get_int(Param::LastReactionMsgId)
|
||||
.unwrap_or_default() as u32,
|
||||
);
|
||||
// The message reacted to may be deleted physically (`load_from_db()` fails) or marked as a tombstone (`is_trash()`).
|
||||
// These are no errors as `Param::LastReaction*` are just weak pointers.
|
||||
// Instead, just return `Ok(None)` and let the caller create another summary.
|
||||
if let Some(reaction_msg) =
|
||||
Message::load_from_db_optional(context, reaction_msg_id).await?
|
||||
{
|
||||
if !reaction_msg.chat_id.is_trash() {
|
||||
let reaction_contact_id = ContactId::new(
|
||||
self.param
|
||||
.get_int(Param::LastReactionContactId)
|
||||
.unwrap_or_default() as u32,
|
||||
);
|
||||
if let Some(reaction) = context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
r#"SELECT reaction FROM reactions WHERE msg_id=? AND contact_id=?"#,
|
||||
(reaction_msg.id, reaction_contact_id),
|
||||
|row| {
|
||||
let reaction: String = row.get(0)?;
|
||||
Ok(reaction)
|
||||
},
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some((reaction_msg, reaction_contact_id, reaction)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chat::{get_chat_msgs, send_text_msg};
|
||||
use crate::chat::{forward_msgs, get_chat_msgs, send_text_msg};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
use crate::constants::DC_CHAT_ID_TRASH;
|
||||
use crate::contact::{Contact, ContactAddress, Origin};
|
||||
use crate::download::DownloadState;
|
||||
use crate::message::MessageState;
|
||||
use crate::message::{delete_msgs, MessageState};
|
||||
use crate::receive_imf::{receive_imf, receive_imf_from_inbox};
|
||||
use crate::sql::housekeeping;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::test_utils::TestContextManager;
|
||||
use crate::tools::SystemTime;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_parse_reaction() {
|
||||
@@ -549,6 +625,146 @@ Here's my footer -- bob@example.net"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_summary(t: &TestContext, expected: &str) {
|
||||
let chatlist = Chatlist::try_load(t, 0, None, None).await.unwrap();
|
||||
let summary = chatlist.get_summary(t, 0, None).await.unwrap();
|
||||
assert_eq!(summary.text, expected);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_reaction_summary() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
alice.set_config(Config::Displayname, Some("ALICE")).await?;
|
||||
bob.set_config(Config::Displayname, Some("BOB")).await?;
|
||||
|
||||
// Alice sends message to Bob
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
let alice_msg1 = alice.send_text(alice_chat.id, "Party?").await;
|
||||
let bob_msg1 = bob.recv_msg(&alice_msg1).await;
|
||||
|
||||
// Bob reacts to Alice's message, this is shown in the summaries
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
bob_msg1.chat_id.accept(&bob).await?;
|
||||
send_reaction(&bob, bob_msg1.id, "👍").await?;
|
||||
let bob_send_reaction = bob.pop_sent_msg().await;
|
||||
let alice_rcvd_reaction = alice.recv_msg(&bob_send_reaction).await;
|
||||
assert!(alice_rcvd_reaction.get_timestamp() > bob_msg1.get_timestamp());
|
||||
|
||||
let chatlist = Chatlist::try_load(&bob, 0, None, None).await?;
|
||||
let summary = chatlist.get_summary(&bob, 0, None).await?;
|
||||
assert_eq!(summary.text, "You reacted 👍 to \"Party?\"");
|
||||
assert_eq!(summary.timestamp, bob_msg1.get_timestamp()); // time refers to message, not to reaction
|
||||
assert_eq!(summary.state, MessageState::InFresh); // state refers to message, not to reaction
|
||||
assert!(summary.prefix.is_none());
|
||||
assert!(summary.thumbnail_path.is_none());
|
||||
assert_summary(&alice, "BOB reacted 👍 to \"Party?\"").await;
|
||||
|
||||
// Alice reacts to own message as well
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
send_reaction(&alice, alice_msg1.sender_msg_id, "🍿").await?;
|
||||
let alice_send_reaction = alice.pop_sent_msg().await;
|
||||
bob.recv_msg(&alice_send_reaction).await;
|
||||
|
||||
assert_summary(&alice, "You reacted 🍿 to \"Party?\"").await;
|
||||
assert_summary(&bob, "ALICE reacted 🍿 to \"Party?\"").await;
|
||||
|
||||
// Alice sends a newer message, this overwrites reaction summaries
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
let alice_msg2 = alice.send_text(alice_chat.id, "kewl").await;
|
||||
bob.recv_msg(&alice_msg2).await;
|
||||
|
||||
assert_summary(&alice, "kewl").await;
|
||||
assert_summary(&bob, "kewl").await;
|
||||
|
||||
// Reactions to older messages still overwrite newer messages
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
send_reaction(&alice, alice_msg1.sender_msg_id, "🤘").await?;
|
||||
let alice_send_reaction = alice.pop_sent_msg().await;
|
||||
bob.recv_msg(&alice_send_reaction).await;
|
||||
|
||||
assert_summary(&alice, "You reacted 🤘 to \"Party?\"").await;
|
||||
assert_summary(&bob, "ALICE reacted 🤘 to \"Party?\"").await;
|
||||
|
||||
// Retracted reactions remove all summary reactions
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
send_reaction(&alice, alice_msg1.sender_msg_id, "").await?;
|
||||
let alice_remove_reaction = alice.pop_sent_msg().await;
|
||||
bob.recv_msg(&alice_remove_reaction).await;
|
||||
|
||||
assert_summary(&alice, "kewl").await;
|
||||
assert_summary(&bob, "kewl").await;
|
||||
|
||||
// Alice adds another reaction and then deletes the message reacted to; this will also delete reaction summary
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
send_reaction(&alice, alice_msg1.sender_msg_id, "🧹").await?;
|
||||
assert_summary(&alice, "You reacted 🧹 to \"Party?\"").await;
|
||||
|
||||
delete_msgs(&alice, &[alice_msg1.sender_msg_id]).await?; // this will leave a tombstone
|
||||
assert_summary(&alice, "kewl").await;
|
||||
housekeeping(&alice).await?; // this will delete the tombstone
|
||||
assert_summary(&alice, "kewl").await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_reaction_forwarded_summary() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
// Alice adds a message to "Saved Messages"
|
||||
let self_chat = alice.get_self_chat().await;
|
||||
let msg_id = send_text_msg(&alice, self_chat.id, "foo".to_string()).await?;
|
||||
assert_summary(&alice, "foo").await;
|
||||
|
||||
// Alice reacts to that message
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
send_reaction(&alice, msg_id, "🐫").await?;
|
||||
assert_summary(&alice, "You reacted 🐫 to \"foo\"").await;
|
||||
let reactions = get_msg_reactions(&alice, msg_id).await?;
|
||||
assert_eq!(reactions.reactions.len(), 1);
|
||||
|
||||
// Alice forwards that message to Bob: Reactions are not forwarded, the message is prefixed by "Forwarded".
|
||||
let bob_id = Contact::create(&alice, "", "bob@example.net").await?;
|
||||
let bob_chat_id = ChatId::create_for_contact(&alice, bob_id).await?;
|
||||
forward_msgs(&alice, &[msg_id], bob_chat_id).await?;
|
||||
assert_summary(&alice, "Forwarded: foo").await; // forwarded messages are prefixed
|
||||
let chatlist = Chatlist::try_load(&alice, 0, None, None).await.unwrap();
|
||||
let forwarded_msg_id = chatlist.get_msg_id(0)?.unwrap();
|
||||
let reactions = get_msg_reactions(&alice, forwarded_msg_id).await?;
|
||||
assert!(reactions.reactions.is_empty()); // reactions are not forwarded
|
||||
|
||||
// Alice reacts to forwarded message:
|
||||
// For reaction summary neither original message author nor "Forwarded" prefix is shown
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
send_reaction(&alice, forwarded_msg_id, "🐳").await?;
|
||||
assert_summary(&alice, "You reacted 🐳 to \"foo\"").await;
|
||||
let reactions = get_msg_reactions(&alice, msg_id).await?;
|
||||
assert_eq!(reactions.reactions.len(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_reaction_self_chat_multidevice_summary() -> Result<()> {
|
||||
let alice0 = TestContext::new_alice().await;
|
||||
let alice1 = TestContext::new_alice().await;
|
||||
let chat = alice0.get_self_chat().await;
|
||||
|
||||
let msg_id = send_text_msg(&alice0, chat.id, "mom's birthday!".to_string()).await?;
|
||||
alice1.recv_msg(&alice0.pop_sent_msg().await).await;
|
||||
|
||||
SystemTime::shift(Duration::from_secs(10));
|
||||
send_reaction(&alice0, msg_id, "👆").await?;
|
||||
let sync = alice0.pop_sent_msg().await;
|
||||
receive_imf(&alice1, sync.payload().as_bytes(), false).await?;
|
||||
|
||||
assert_summary(&alice0, "You reacted 👆 to \"mom's birthday!\"").await;
|
||||
assert_summary(&alice1, "You reacted 👆 to \"mom's birthday!\"").await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_partial_download_and_reaction() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -665,8 +881,7 @@ Here's my footer -- bob@example.net"
|
||||
let alice1_msg = alice1.recv_msg(&alice0.pop_sent_msg().await).await;
|
||||
|
||||
send_reaction(&alice0, alice0_msg_id, "👀").await?;
|
||||
let sync = alice0.pop_sent_msg().await;
|
||||
receive_imf(&alice1, sync.payload().as_bytes(), false).await?;
|
||||
alice1.recv_msg(&alice0.pop_sent_msg().await).await;
|
||||
|
||||
expect_reactions_changed_event(&alice0, chat_id, alice0_msg_id, ContactId::SELF).await?;
|
||||
expect_reactions_changed_event(&alice1, alice1_msg.chat_id, alice1_msg.id, ContactId::SELF)
|
||||
|
||||
@@ -1077,6 +1077,12 @@ async fn add_parts(
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
}
|
||||
if chat_id.is_none() && is_dc_message == MessengerMessage::Yes {
|
||||
if let Some(chat) = ChatIdBlocked::lookup_by_contact(context, to_id).await? {
|
||||
chat_id = Some(chat.id);
|
||||
chat_id_blocked = chat.blocked;
|
||||
}
|
||||
}
|
||||
|
||||
// automatically unblock chat when the user sends a message
|
||||
if chat_id_blocked != Blocked::Not {
|
||||
@@ -1374,6 +1380,7 @@ async fn add_parts(
|
||||
&mime_in_reply_to,
|
||||
orig_chat_id.unwrap_or_default(),
|
||||
from_id,
|
||||
sort_timestamp,
|
||||
Reaction::from(reaction_str.as_str()),
|
||||
)
|
||||
.await?;
|
||||
@@ -1721,11 +1728,6 @@ async fn is_probably_private_reply(
|
||||
}
|
||||
}
|
||||
|
||||
let is_reaction = mime_parser.parts.iter().any(|part| part.is_reaction);
|
||||
if is_reaction {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::chat::{
|
||||
ChatVisibility,
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::constants::{DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS};
|
||||
use crate::constants::{ShowEmails, DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS};
|
||||
use crate::download::MIN_DOWNLOAD_LIMIT;
|
||||
use crate::imap::prefetch_should_download;
|
||||
use crate::imex::{imex, ImexMode};
|
||||
@@ -142,6 +142,35 @@ async fn test_adhoc_group_show_accepted_contact_unknown() {
|
||||
assert_eq!(chats.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group_outgoing_show_accepted_contact_unaccepted() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
bob.set_config(
|
||||
Config::ShowEmails,
|
||||
Some(&ShowEmails::AcceptedContacts.to_string()),
|
||||
)
|
||||
.await?;
|
||||
tcm.send_recv(alice, bob, "hi").await;
|
||||
receive_imf(
|
||||
bob,
|
||||
b"From: bob@example.net\n\
|
||||
To: alice@example.org, claire@example.com\n\
|
||||
Message-ID: <3333@example.net>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let chats = Chatlist::try_load(bob, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0)?;
|
||||
assert_eq!(chat_id.get_msg_cnt(bob).await?, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group_show_accepted_contact_known() {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
14
src/sql.rs
14
src/sql.rs
@@ -46,10 +46,12 @@ pub(crate) fn params_iter(
|
||||
iter.iter().map(|item| item as &dyn crate::sql::ToSql)
|
||||
}
|
||||
|
||||
mod deserialize;
|
||||
mod migrations;
|
||||
mod pool;
|
||||
mod serialize;
|
||||
|
||||
use pool::Pool;
|
||||
use pool::{Pool, PooledConnection};
|
||||
|
||||
/// A wrapper around the underlying Sqlite3 object.
|
||||
#[derive(Debug)]
|
||||
@@ -363,6 +365,12 @@ impl Sql {
|
||||
self.write_mtx.lock().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_connection(&self) -> Result<PooledConnection> {
|
||||
let lock = self.pool.read().await;
|
||||
let pool = lock.as_ref().context("no SQL connection")?;
|
||||
pool.get().await
|
||||
}
|
||||
|
||||
/// Allocates a connection and calls `function` with the connection. If `function` does write
|
||||
/// queries,
|
||||
/// - either first take a lock using `write_lock()`
|
||||
@@ -374,9 +382,7 @@ impl Sql {
|
||||
F: 'a + FnOnce(&mut Connection) -> Result<R> + Send,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let lock = self.pool.read().await;
|
||||
let pool = lock.as_ref().context("no SQL connection")?;
|
||||
let mut conn = pool.get().await?;
|
||||
let mut conn = self.get_connection().await?;
|
||||
let res = tokio::task::block_in_place(move || function(&mut conn))?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
1349
src/sql/deserialize.rs
Normal file
1349
src/sql/deserialize.rs
Normal file
File diff suppressed because it is too large
Load Diff
963
src/sql/serialize.rs
Normal file
963
src/sql/serialize.rs
Normal file
@@ -0,0 +1,963 @@
|
||||
//! Database serialization module.
|
||||
//!
|
||||
//! The module contains functions to serialize database into a stream.
|
||||
//!
|
||||
//! Output format is based on [bencoding](http://bittorrent.org/beps/bep_0003.html).
|
||||
|
||||
/// Database version supported by the current serialization code.
|
||||
///
|
||||
/// Serialization code MUST be updated before increasing this number.
|
||||
///
|
||||
/// If this version is below the actual database version,
|
||||
/// serialization code is outdated.
|
||||
/// If this version is above the actual database version,
|
||||
/// migrations have to be run first to update the database.
|
||||
const SERIALIZE_DBVERSION: &str = "99";
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use rusqlite::types::ValueRef;
|
||||
use rusqlite::Transaction;
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
|
||||
use super::Sql;
|
||||
|
||||
struct Encoder<'a, W: AsyncWrite + Unpin> {
|
||||
tx: Transaction<'a>,
|
||||
|
||||
w: W,
|
||||
}
|
||||
|
||||
async fn write_bytes(w: &mut (impl AsyncWrite + Unpin), b: &[u8]) -> Result<()> {
|
||||
let bytes_len = format!("{}:", b.len());
|
||||
w.write_all(bytes_len.as_bytes()).await?;
|
||||
w.write_all(b).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_str(w: &mut (impl AsyncWrite + Unpin), s: &str) -> Result<()> {
|
||||
write_bytes(w, s.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_i64(w: &mut (impl AsyncWrite + Unpin), i: i64) -> Result<()> {
|
||||
let s = format!("{i}");
|
||||
w.write_all(b"i").await?;
|
||||
w.write_all(s.as_bytes()).await?;
|
||||
w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_u32(w: &mut (impl AsyncWrite + Unpin), i: u32) -> Result<()> {
|
||||
let s = format!("{i}");
|
||||
w.write_all(b"i").await?;
|
||||
w.write_all(s.as_bytes()).await?;
|
||||
w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_f64(w: &mut (impl AsyncWrite + Unpin), f: f64) -> Result<()> {
|
||||
write_bytes(w, &f.to_be_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_bool(w: &mut (impl AsyncWrite + Unpin), b: bool) -> Result<()> {
|
||||
if b {
|
||||
w.write_all(b"i1e").await?;
|
||||
} else {
|
||||
w.write_all(b"i0e").await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a, W: AsyncWrite + Unpin> Encoder<'a, W> {
|
||||
fn new(tx: Transaction<'a>, w: W) -> Self {
|
||||
Self { tx, w }
|
||||
}
|
||||
|
||||
/// Serializes `config` table.
|
||||
async fn serialize_config(&mut self) -> Result<()> {
|
||||
// FIXME: sort the dictionary in lexicographical order
|
||||
// dbversion should be the first, so store it as "_config._dbversion"
|
||||
|
||||
let mut stmt = self.tx.prepare("SELECT keyname,value FROM config")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
self.w.write_all(b"d").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let keyname: String = row.get(0)?;
|
||||
let value: String = row.get(1)?;
|
||||
write_str(&mut self.w, &keyname).await?;
|
||||
write_str(&mut self.w, &value).await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_acpeerstates(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare("SELECT addr, backward_verified_key_id, last_seen, last_seen_autocrypt, public_key, prefer_encrypted, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let addr: String = row.get("addr")?;
|
||||
let backward_verified_key_id: Option<i64> = row.get("backward_verified_key_id")?;
|
||||
let prefer_encrypted: i64 = row.get("prefer_encrypted")?;
|
||||
|
||||
let last_seen: i64 = row.get("last_seen")?;
|
||||
|
||||
let last_seen_autocrypt: i64 = row.get("last_seen_autocrypt")?;
|
||||
let public_key: Option<Vec<u8>> = row.get("public_key")?;
|
||||
let public_key_fingerprint: Option<String> = row.get("public_key_fingerprint")?;
|
||||
|
||||
let gossip_timestamp: i64 = row.get("gossip_timestamp")?;
|
||||
let gossip_key: Option<Vec<u8>> = row.get("gossip_key")?;
|
||||
let gossip_key_fingerprint: Option<String> = row.get("gossip_key_fingerprint")?;
|
||||
|
||||
let verified_key: Option<Vec<u8>> = row.get("verified_key")?;
|
||||
let verified_key_fingerprint: Option<String> = row.get("verified_key_fingerprint")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "addr").await?;
|
||||
write_str(&mut self.w, &addr).await?;
|
||||
|
||||
if let Some(backward_verified_key_id) = backward_verified_key_id {
|
||||
write_str(&mut self.w, "backward_verified_key_id").await?;
|
||||
write_i64(&mut self.w, backward_verified_key_id).await?;
|
||||
}
|
||||
|
||||
if let Some(gossip_key) = gossip_key {
|
||||
write_str(&mut self.w, "gossip_key").await?;
|
||||
write_bytes(&mut self.w, &gossip_key).await?;
|
||||
}
|
||||
|
||||
if let Some(gossip_key_fingerprint) = gossip_key_fingerprint {
|
||||
write_str(&mut self.w, "gossip_key_fingerprint").await?;
|
||||
write_str(&mut self.w, &gossip_key_fingerprint).await?;
|
||||
}
|
||||
|
||||
write_str(&mut self.w, "gossip_timestamp").await?;
|
||||
write_i64(&mut self.w, gossip_timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "last_seen").await?;
|
||||
write_i64(&mut self.w, last_seen).await?;
|
||||
|
||||
write_str(&mut self.w, "last_seen_autocrypt").await?;
|
||||
write_i64(&mut self.w, last_seen_autocrypt).await?;
|
||||
|
||||
write_str(&mut self.w, "prefer_encrypted").await?;
|
||||
write_i64(&mut self.w, prefer_encrypted).await?;
|
||||
|
||||
if let Some(public_key) = public_key {
|
||||
write_str(&mut self.w, "public_key").await?;
|
||||
write_bytes(&mut self.w, &public_key).await?;
|
||||
}
|
||||
|
||||
if let Some(public_key_fingerprint) = public_key_fingerprint {
|
||||
write_str(&mut self.w, "public_key_fingerprint").await?;
|
||||
write_str(&mut self.w, &public_key_fingerprint).await?;
|
||||
}
|
||||
|
||||
if let Some(verified_key) = verified_key {
|
||||
write_str(&mut self.w, "verified_key").await?;
|
||||
write_bytes(&mut self.w, &verified_key).await?;
|
||||
}
|
||||
|
||||
if let Some(verified_key_fingerprint) = verified_key_fingerprint {
|
||||
write_str(&mut self.w, "verified_key_fingerprint").await?;
|
||||
write_str(&mut self.w, &verified_key_fingerprint).await?;
|
||||
}
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes chats.
|
||||
async fn serialize_chats(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare(
|
||||
"SELECT \
|
||||
id,\
|
||||
type,\
|
||||
name,\
|
||||
blocked,\
|
||||
grpid,\
|
||||
param,\
|
||||
archived,\
|
||||
gossiped_timestamp,\
|
||||
locations_send_begin,\
|
||||
locations_send_until,\
|
||||
locations_last_sent,\
|
||||
created_timestamp,\
|
||||
muted_until,\
|
||||
ephemeral_timer,\
|
||||
protected FROM chats",
|
||||
)?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: u32 = row.get("id")?;
|
||||
let typ: u32 = row.get("type")?;
|
||||
let name: String = row.get("name")?;
|
||||
let blocked: u32 = row.get("blocked")?;
|
||||
let grpid: String = row.get("grpid")?;
|
||||
let param: String = row.get("param")?;
|
||||
let archived: bool = row.get("archived")?;
|
||||
let gossiped_timestamp: i64 = row.get("gossiped_timestamp")?;
|
||||
let locations_send_begin: i64 = row.get("locations_send_begin")?;
|
||||
let locations_send_until: i64 = row.get("locations_send_until")?;
|
||||
let locations_last_sent: i64 = row.get("locations_last_sent")?;
|
||||
let created_timestamp: i64 = row.get("created_timestamp")?;
|
||||
let muted_until: i64 = row.get("muted_until")?;
|
||||
let ephemeral_timer: i64 = row.get("ephemeral_timer")?;
|
||||
let protected: u32 = row.get("protected")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "archived").await?;
|
||||
write_bool(&mut self.w, archived).await?;
|
||||
|
||||
write_str(&mut self.w, "blocked").await?;
|
||||
write_u32(&mut self.w, blocked).await?;
|
||||
|
||||
write_str(&mut self.w, "created_timestamp").await?;
|
||||
write_i64(&mut self.w, created_timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "ephemeral_timer").await?;
|
||||
write_i64(&mut self.w, ephemeral_timer).await?;
|
||||
|
||||
write_str(&mut self.w, "gossiped_timestamp").await?;
|
||||
write_i64(&mut self.w, gossiped_timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "grpid").await?;
|
||||
write_str(&mut self.w, &grpid).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_u32(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "locations_last_sent").await?;
|
||||
write_i64(&mut self.w, locations_last_sent).await?;
|
||||
|
||||
write_str(&mut self.w, "locations_send_begin").await?;
|
||||
write_i64(&mut self.w, locations_send_begin).await?;
|
||||
|
||||
write_str(&mut self.w, "locations_send_until").await?;
|
||||
write_i64(&mut self.w, locations_send_until).await?;
|
||||
|
||||
write_str(&mut self.w, "muted_until").await?;
|
||||
write_i64(&mut self.w, muted_until).await?;
|
||||
|
||||
write_str(&mut self.w, "name").await?;
|
||||
write_str(&mut self.w, &name).await?;
|
||||
|
||||
write_str(&mut self.w, "param").await?;
|
||||
write_str(&mut self.w, ¶m).await?;
|
||||
|
||||
write_str(&mut self.w, "protected").await?;
|
||||
write_u32(&mut self.w, protected).await?;
|
||||
|
||||
write_str(&mut self.w, "type").await?;
|
||||
write_u32(&mut self.w, typ).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_chats_contacts(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT chat_id, contact_id FROM chats_contacts")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let chat_id: u32 = row.get("chat_id")?;
|
||||
let contact_id: u32 = row.get("contact_id")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "chat_id").await?;
|
||||
write_u32(&mut self.w, chat_id).await?;
|
||||
|
||||
write_str(&mut self.w, "contact_id").await?;
|
||||
write_u32(&mut self.w, contact_id).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes contacts.
|
||||
async fn serialize_contacts(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare(
|
||||
"SELECT \
|
||||
id,\
|
||||
name,\
|
||||
addr,\
|
||||
origin,\
|
||||
blocked,\
|
||||
last_seen,\
|
||||
param,\
|
||||
authname,\
|
||||
selfavatar_sent,\
|
||||
status FROM contacts",
|
||||
)?;
|
||||
let mut rows = stmt.query(())?;
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: u32 = row.get("id")?;
|
||||
let name: String = row.get("name")?;
|
||||
let authname: String = row.get("authname")?;
|
||||
let addr: String = row.get("addr")?;
|
||||
let origin: u32 = row.get("origin")?;
|
||||
let blocked: Option<bool> = row.get("blocked")?;
|
||||
let blocked = blocked.unwrap_or_default();
|
||||
let last_seen: i64 = row.get("last_seen")?;
|
||||
let selfavatar_sent: i64 = row.get("selfavatar_sent")?;
|
||||
let param: String = row.get("param")?;
|
||||
let status: Option<String> = row.get("status")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "addr").await?;
|
||||
write_str(&mut self.w, &addr).await?;
|
||||
|
||||
write_str(&mut self.w, "authname").await?;
|
||||
write_str(&mut self.w, &authname).await?;
|
||||
|
||||
write_str(&mut self.w, "blocked").await?;
|
||||
write_bool(&mut self.w, blocked).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_u32(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "last_seen").await?;
|
||||
write_i64(&mut self.w, last_seen).await?;
|
||||
|
||||
write_str(&mut self.w, "name").await?;
|
||||
write_str(&mut self.w, &name).await?;
|
||||
|
||||
write_str(&mut self.w, "origin").await?;
|
||||
write_u32(&mut self.w, origin).await?;
|
||||
|
||||
// TODO: parse param instead of serializeing as is
|
||||
write_str(&mut self.w, "param").await?;
|
||||
write_str(&mut self.w, ¶m).await?;
|
||||
|
||||
write_str(&mut self.w, "selfavatar_sent").await?;
|
||||
write_i64(&mut self.w, selfavatar_sent).await?;
|
||||
|
||||
if let Some(status) = status {
|
||||
if !status.is_empty() {
|
||||
write_str(&mut self.w, "status").await?;
|
||||
write_str(&mut self.w, &status).await?;
|
||||
}
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_dns_cache(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT hostname, address, timestamp FROM dns_cache")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let hostname: String = row.get("hostname")?;
|
||||
let address: String = row.get("address")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "address").await?;
|
||||
write_str(&mut self.w, &address).await?;
|
||||
|
||||
write_str(&mut self.w, "hostname").await?;
|
||||
write_str(&mut self.w, &hostname).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_imap(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, rfc724_mid, folder, target, uid, uidvalidity FROM imap")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let rfc724_mid: String = row.get("rfc724_mid")?;
|
||||
let folder: String = row.get("folder")?;
|
||||
let target: String = row.get("target")?;
|
||||
let uid: i64 = row.get("uid")?;
|
||||
let uidvalidity: i64 = row.get("uidvalidity")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "folder").await?;
|
||||
write_str(&mut self.w, &folder).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "rfc724_mid").await?;
|
||||
write_str(&mut self.w, &rfc724_mid).await?;
|
||||
|
||||
write_str(&mut self.w, "target").await?;
|
||||
write_str(&mut self.w, &target).await?;
|
||||
|
||||
write_str(&mut self.w, "uid").await?;
|
||||
write_i64(&mut self.w, uid).await?;
|
||||
|
||||
write_str(&mut self.w, "uidvalidity").await?;
|
||||
write_i64(&mut self.w, uidvalidity).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_imap_sync(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT folder, uidvalidity, uid_next, modseq FROM imap_sync")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let folder: String = row.get("folder")?;
|
||||
let uidvalidity: i64 = row.get("uidvalidity")?;
|
||||
let uidnext: i64 = row.get("uid_next")?;
|
||||
let modseq: i64 = row.get("modseq")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "folder").await?;
|
||||
write_str(&mut self.w, &folder).await?;
|
||||
|
||||
write_str(&mut self.w, "modseq").await?;
|
||||
write_i64(&mut self.w, modseq).await?;
|
||||
|
||||
write_str(&mut self.w, "uidnext").await?;
|
||||
write_i64(&mut self.w, uidnext).await?;
|
||||
|
||||
write_str(&mut self.w, "uidvalidity").await?;
|
||||
write_i64(&mut self.w, uidvalidity).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_keypairs(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id,addr,private_key,public_key,created FROM keypairs")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: u32 = row.get("id")?;
|
||||
let addr: String = row.get("addr")?;
|
||||
let private_key: Vec<u8> = row.get("private_key")?;
|
||||
let public_key: Vec<u8> = row.get("public_key")?;
|
||||
let created: i64 = row.get("created")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "addr").await?;
|
||||
write_str(&mut self.w, &addr).await?;
|
||||
|
||||
write_str(&mut self.w, "created").await?;
|
||||
write_i64(&mut self.w, created).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_u32(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "private_key").await?;
|
||||
write_bytes(&mut self.w, &private_key).await?;
|
||||
|
||||
write_str(&mut self.w, "public_key").await?;
|
||||
write_bytes(&mut self.w, &public_key).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_leftgroups(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare("SELECT grpid FROM leftgrps")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let grpid: String = row.get("grpid")?;
|
||||
write_str(&mut self.w, &grpid).await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_locations(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, latitude, longitude, accuracy, timestamp, chat_id, from_id, independent FROM locations")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let latitude: f64 = row.get("latitude")?;
|
||||
let longitude: f64 = row.get("longitude")?;
|
||||
let accuracy: f64 = row.get("accuracy")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
let chat_id: u32 = row.get("chat_id")?;
|
||||
let from_id: u32 = row.get("from_id")?;
|
||||
let independent: u32 = row.get("independent")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "accuracy").await?;
|
||||
write_f64(&mut self.w, accuracy).await?;
|
||||
|
||||
write_str(&mut self.w, "chat_id").await?;
|
||||
write_u32(&mut self.w, chat_id).await?;
|
||||
|
||||
write_str(&mut self.w, "from_id").await?;
|
||||
write_u32(&mut self.w, from_id).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "independent").await?;
|
||||
write_u32(&mut self.w, independent).await?;
|
||||
|
||||
write_str(&mut self.w, "latitude").await?;
|
||||
write_f64(&mut self.w, latitude).await?;
|
||||
|
||||
write_str(&mut self.w, "longitude").await?;
|
||||
write_f64(&mut self.w, longitude).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes MDNs.
|
||||
async fn serialize_mdns(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT msg_id, contact_id, timestamp_sent FROM msgs_mdns")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let msg_id: u32 = row.get("msg_id")?;
|
||||
let contact_id: u32 = row.get("contact_id")?;
|
||||
let timestamp_sent: i64 = row.get("timestamp_sent")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "contact_id").await?;
|
||||
write_u32(&mut self.w, contact_id).await?;
|
||||
|
||||
write_str(&mut self.w, "msg_id").await?;
|
||||
write_u32(&mut self.w, msg_id).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp_sent").await?;
|
||||
write_i64(&mut self.w, timestamp_sent).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes messages.
|
||||
async fn serialize_messages(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare(
|
||||
"SELECT
|
||||
id,
|
||||
rfc724_mid,
|
||||
chat_id,
|
||||
from_id, to_id,
|
||||
timestamp,
|
||||
type,
|
||||
state,
|
||||
msgrmsg,
|
||||
bytes,
|
||||
txt,
|
||||
txt_raw,
|
||||
param,
|
||||
timestamp_sent,
|
||||
timestamp_rcvd,
|
||||
hidden,
|
||||
mime_compressed,
|
||||
mime_headers,
|
||||
mime_in_reply_to,
|
||||
mime_references,
|
||||
location_id FROM msgs",
|
||||
)?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let rfc724_mid: String = row.get("rfc724_mid")?;
|
||||
let chat_id: i64 = row.get("chat_id")?;
|
||||
let from_id: i64 = row.get("from_id")?;
|
||||
let to_id: i64 = row.get("to_id")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
let typ: i64 = row.get("type")?;
|
||||
let state: i64 = row.get("state")?;
|
||||
let msgrmsg: i64 = row.get("msgrmsg")?;
|
||||
let bytes: i64 = row.get("bytes")?;
|
||||
let txt: String = row.get("txt")?;
|
||||
let txt_raw: String = row.get("txt_raw")?;
|
||||
let param: String = row.get("param")?;
|
||||
let timestamp_sent: i64 = row.get("timestamp_sent")?;
|
||||
let timestamp_rcvd: i64 = row.get("timestamp_rcvd")?;
|
||||
let hidden: i64 = row.get("hidden")?;
|
||||
let mime_compressed: i64 = row.get("mime_compressed")?;
|
||||
let mime_headers: Vec<u8> =
|
||||
row.get("mime_headers")
|
||||
.or_else(|err| match row.get_ref("mime_headers")? {
|
||||
ValueRef::Null => Ok(Vec::new()),
|
||||
ValueRef::Text(text) => Ok(text.to_vec()),
|
||||
ValueRef::Blob(blob) => Ok(blob.to_vec()),
|
||||
ValueRef::Integer(_) | ValueRef::Real(_) => Err(err),
|
||||
})?;
|
||||
let mime_in_reply_to: Option<String> = row.get("mime_in_reply_to")?;
|
||||
let mime_references: Option<String> = row.get("mime_references")?;
|
||||
let location_id: i64 = row.get("location_id")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "bytes").await?;
|
||||
write_i64(&mut self.w, bytes).await?;
|
||||
|
||||
write_str(&mut self.w, "chat_id").await?;
|
||||
write_i64(&mut self.w, chat_id).await?;
|
||||
|
||||
write_str(&mut self.w, "from_id").await?;
|
||||
write_i64(&mut self.w, from_id).await?;
|
||||
|
||||
write_str(&mut self.w, "hidden").await?;
|
||||
write_i64(&mut self.w, hidden).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "location_id").await?;
|
||||
write_i64(&mut self.w, location_id).await?;
|
||||
|
||||
write_str(&mut self.w, "mime_compressed").await?;
|
||||
write_i64(&mut self.w, mime_compressed).await?;
|
||||
|
||||
write_str(&mut self.w, "mime_headers").await?;
|
||||
write_bytes(&mut self.w, &mime_headers).await?;
|
||||
|
||||
if let Some(mime_in_reply_to) = mime_in_reply_to {
|
||||
write_str(&mut self.w, "mime_in_reply_to").await?;
|
||||
write_str(&mut self.w, &mime_in_reply_to).await?;
|
||||
}
|
||||
|
||||
if let Some(mime_references) = mime_references {
|
||||
write_str(&mut self.w, "mime_references").await?;
|
||||
write_str(&mut self.w, &mime_references).await?;
|
||||
}
|
||||
|
||||
write_str(&mut self.w, "msgrmsg").await?;
|
||||
write_i64(&mut self.w, msgrmsg).await?;
|
||||
|
||||
write_str(&mut self.w, "param").await?;
|
||||
write_str(&mut self.w, ¶m).await?;
|
||||
|
||||
write_str(&mut self.w, "rfc724_mid").await?;
|
||||
write_str(&mut self.w, &rfc724_mid).await?;
|
||||
|
||||
write_str(&mut self.w, "state").await?;
|
||||
write_i64(&mut self.w, state).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp_rcvd").await?;
|
||||
write_i64(&mut self.w, timestamp_rcvd).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp_sent").await?;
|
||||
write_i64(&mut self.w, timestamp_sent).await?;
|
||||
|
||||
write_str(&mut self.w, "to_id").await?;
|
||||
write_i64(&mut self.w, to_id).await?;
|
||||
|
||||
write_str(&mut self.w, "txt").await?;
|
||||
write_str(&mut self.w, &txt).await?;
|
||||
|
||||
write_str(&mut self.w, "txt_raw").await?;
|
||||
write_str(&mut self.w, &txt_raw).await?;
|
||||
|
||||
write_str(&mut self.w, "type").await?;
|
||||
write_i64(&mut self.w, typ).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_msgs_status_updates(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, msg_id, uid, update_item FROM msgs_status_updates")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let msg_id: i64 = row.get("msg_id")?;
|
||||
let uid: String = row.get("uid")?;
|
||||
let update_item: String = row.get("update_item")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "msg_id").await?;
|
||||
write_i64(&mut self.w, msg_id).await?;
|
||||
|
||||
write_str(&mut self.w, "uid").await?;
|
||||
write_str(&mut self.w, &uid).await?;
|
||||
|
||||
write_str(&mut self.w, "update_item").await?;
|
||||
write_str(&mut self.w, &update_item).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes reactions.
|
||||
async fn serialize_reactions(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT msg_id, contact_id, reaction FROM reactions")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let msg_id: u32 = row.get("msg_id")?;
|
||||
let contact_id: u32 = row.get("contact_id")?;
|
||||
let reaction: String = row.get("reaction")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "contact_id").await?;
|
||||
write_u32(&mut self.w, contact_id).await?;
|
||||
|
||||
write_str(&mut self.w, "msg_id").await?;
|
||||
write_u32(&mut self.w, msg_id).await?;
|
||||
|
||||
write_str(&mut self.w, "reaction").await?;
|
||||
write_str(&mut self.w, &reaction).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_sending_domains(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT domain, dkim_works FROM sending_domains")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let domain: String = row.get("domain")?;
|
||||
let dkim_works: i64 = row.get("dkim_works")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "dkim_works").await?;
|
||||
write_i64(&mut self.w, dkim_works).await?;
|
||||
|
||||
write_str(&mut self.w, "domain").await?;
|
||||
write_str(&mut self.w, &domain).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_tokens(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, namespc, foreign_id, token, timestamp FROM tokens")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let namespace: u32 = row.get("namespc")?;
|
||||
let foreign_id: u32 = row.get("foreign_id")?;
|
||||
let token: String = row.get("token")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "foreign_id").await?;
|
||||
write_u32(&mut self.w, foreign_id).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "namespace").await?;
|
||||
write_u32(&mut self.w, namespace).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "token").await?;
|
||||
write_str(&mut self.w, &token).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize(&mut self) -> Result<()> {
|
||||
let dbversion: String = self.tx.query_row(
|
||||
"SELECT value FROM config WHERE keyname='dbversion'",
|
||||
(),
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
if dbversion != SERIALIZE_DBVERSION {
|
||||
return Err(anyhow!(
|
||||
"cannot serialize database version {dbversion}, expected {SERIALIZE_DBVERSION}"
|
||||
));
|
||||
}
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "_config").await?;
|
||||
self.serialize_config().await?;
|
||||
|
||||
write_str(&mut self.w, "acpeerstates").await?;
|
||||
self.serialize_acpeerstates()
|
||||
.await
|
||||
.context("serialize autocrypt peerstates")?;
|
||||
|
||||
write_str(&mut self.w, "chats").await?;
|
||||
self.serialize_chats().await?;
|
||||
|
||||
write_str(&mut self.w, "chats_contacts").await?;
|
||||
self.serialize_chats_contacts()
|
||||
.await
|
||||
.context("serialize chats_contacts")?;
|
||||
|
||||
write_str(&mut self.w, "contacts").await?;
|
||||
self.serialize_contacts().await?;
|
||||
|
||||
write_str(&mut self.w, "dns_cache").await?;
|
||||
self.serialize_dns_cache()
|
||||
.await
|
||||
.context("serialize dns_cache")?;
|
||||
|
||||
write_str(&mut self.w, "imap").await?;
|
||||
self.serialize_imap().await.context("serialize imap")?;
|
||||
|
||||
write_str(&mut self.w, "imap_sync").await?;
|
||||
self.serialize_imap_sync()
|
||||
.await
|
||||
.context("serialize imap_sync")?;
|
||||
|
||||
write_str(&mut self.w, "keypairs").await?;
|
||||
self.serialize_keypairs().await?;
|
||||
|
||||
write_str(&mut self.w, "leftgroups").await?;
|
||||
self.serialize_leftgroups().await?;
|
||||
|
||||
write_str(&mut self.w, "locations").await?;
|
||||
self.serialize_locations().await?;
|
||||
|
||||
write_str(&mut self.w, "mdns").await?;
|
||||
self.serialize_mdns().await?;
|
||||
|
||||
write_str(&mut self.w, "messages").await?;
|
||||
self.serialize_messages()
|
||||
.await
|
||||
.context("serialize messages")?;
|
||||
|
||||
write_str(&mut self.w, "msgs_status_updates").await?;
|
||||
self.serialize_msgs_status_updates()
|
||||
.await
|
||||
.context("serialize msgs_status_updates")?;
|
||||
|
||||
write_str(&mut self.w, "reactions").await?;
|
||||
self.serialize_reactions().await?;
|
||||
|
||||
write_str(&mut self.w, "sending_domains").await?;
|
||||
self.serialize_sending_domains()
|
||||
.await
|
||||
.context("serialize sending_domains")?;
|
||||
|
||||
write_str(&mut self.w, "tokens").await?;
|
||||
self.serialize_tokens().await?;
|
||||
|
||||
// jobs table is skipped
|
||||
// multi_device_sync is skipped
|
||||
// imap_markseen is skipped, it is usually empty and the device exporting the
|
||||
// database should still be able to clear it.
|
||||
// smtp, smtp_mdns and smtp_status_updates tables are skipped, they are part of the
|
||||
// outgoing message queue.
|
||||
// devmsglabels is skipped, it is reset in `delete_and_reset_all_device_msgs()` on import
|
||||
// anyway
|
||||
// bobstate is not serialized, it is temporary for joining or adding a contact.
|
||||
//
|
||||
// TODO insert welcome message on import like done in `delete_and_reset_all_device_msgs()`?
|
||||
self.w.write_all(b"e").await?;
|
||||
self.w.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sql {
|
||||
/// Serializes the database into a bytestream.
|
||||
pub async fn serialize(&self, w: impl AsyncWrite + Unpin) -> Result<()> {
|
||||
let mut conn = self.get_connection().await?;
|
||||
|
||||
// Start a read transaction to take a database snapshot.
|
||||
let transaction = conn.transaction()?;
|
||||
let mut encoder = Encoder::new(transaction, w);
|
||||
encoder.serialize().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -429,6 +429,12 @@ pub enum StockMessage {
|
||||
fallback = "⚠️ It seems you are using Delta Chat on multiple devices that cannot decrypt each other's outgoing messages. To fix this, on the older device use \"Settings / Add Second Device\" and follow the instructions."
|
||||
))]
|
||||
CantDecryptOutgoingMsgs = 175,
|
||||
|
||||
#[strum(props(fallback = "You reacted %1$s to \"%2$s\""))]
|
||||
MsgYouReacted = 176,
|
||||
|
||||
#[strum(props(fallback = "%1$s reacted %2$s to \"%3$s\""))]
|
||||
MsgReactedBy = 177,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -730,6 +736,27 @@ pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactI
|
||||
}
|
||||
}
|
||||
|
||||
/// Stock string: `You reacted %1$s to "%2$s"` or `%1$s reacted %2$s to "%3$s"`.
|
||||
pub(crate) async fn msg_reacted(
|
||||
context: &Context,
|
||||
by_contact: ContactId,
|
||||
reaction: &str,
|
||||
summary: &str,
|
||||
) -> String {
|
||||
if by_contact == ContactId::SELF {
|
||||
translated(context, StockMessage::MsgYouReacted)
|
||||
.await
|
||||
.replace1(reaction)
|
||||
.replace2(summary)
|
||||
} else {
|
||||
translated(context, StockMessage::MsgReactedBy)
|
||||
.await
|
||||
.replace1(&by_contact.get_stock_name(context).await)
|
||||
.replace2(reaction)
|
||||
.replace3(summary)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stock string: `GIF`.
|
||||
pub(crate) async fn gif(context: &Context) -> String {
|
||||
translated(context, StockMessage::Gif).await
|
||||
|
||||
142
src/summary.rs
142
src/summary.rs
@@ -10,7 +10,9 @@ use crate::context::Context;
|
||||
use crate::message::{Message, MessageState, Viewtype};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::stock_str;
|
||||
use crate::stock_str::msg_reacted;
|
||||
use crate::tools::truncate;
|
||||
use anyhow::Result;
|
||||
|
||||
/// Prefix displayed before message and separated by ":" in the chatlist.
|
||||
#[derive(Debug)]
|
||||
@@ -62,7 +64,24 @@ impl Summary {
|
||||
msg: &Message,
|
||||
chat: &Chat,
|
||||
contact: Option<&Contact>,
|
||||
) -> Self {
|
||||
) -> Result<Summary> {
|
||||
if let Some((reaction_msg, reaction_contact_id, reaction)) = chat
|
||||
.get_last_reaction_if_newer_than(context, msg.timestamp_sort)
|
||||
.await?
|
||||
{
|
||||
// there is a reaction newer than the latest message, show that.
|
||||
// sorting and therefore date is still the one of the last message,
|
||||
// the reaction is is more sth. that overlays temporarily.
|
||||
let summary = reaction_msg.get_summary_text_without_prefix(context).await;
|
||||
return Ok(Summary {
|
||||
prefix: None,
|
||||
text: msg_reacted(context, reaction_contact_id, &reaction, &summary).await,
|
||||
timestamp: msg.get_timestamp(), // message timestamp (not reaction) to make timestamps more consistent with chats ordering
|
||||
state: msg.state, // message state (not reaction) - indicating if it was me sending the last message
|
||||
thumbnail_path: None,
|
||||
});
|
||||
}
|
||||
|
||||
let prefix = if msg.state == MessageState::OutDraft {
|
||||
Some(SummaryPrefix::Draft(stock_str::draft(context).await))
|
||||
} else if msg.from_id == ContactId::SELF {
|
||||
@@ -102,13 +121,13 @@ impl Summary {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
Ok(Summary {
|
||||
prefix,
|
||||
text,
|
||||
timestamp: msg.get_timestamp(),
|
||||
state: msg.state,
|
||||
thumbnail_path,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the [`Summary::text`] attribute truncated to an approximate length.
|
||||
@@ -120,6 +139,17 @@ impl Summary {
|
||||
impl Message {
|
||||
/// Returns a summary text.
|
||||
async fn get_summary_text(&self, context: &Context) -> String {
|
||||
let summary = self.get_summary_text_without_prefix(context).await;
|
||||
|
||||
if self.is_forwarded() {
|
||||
format!("{}: {}", stock_str::forwarded(context).await, summary)
|
||||
} else {
|
||||
summary
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a summary text without "Forwarded:" prefix.
|
||||
async fn get_summary_text_without_prefix(&self, context: &Context) -> String {
|
||||
let (emoji, type_name, type_file, append_text);
|
||||
match self.viewtype {
|
||||
Viewtype::Image => {
|
||||
@@ -230,12 +260,6 @@ impl Message {
|
||||
summary
|
||||
};
|
||||
|
||||
let summary = if self.is_forwarded() {
|
||||
format!("{}: {}", stock_str::forwarded(context).await, summary)
|
||||
} else {
|
||||
summary
|
||||
};
|
||||
|
||||
summary.split_whitespace().collect::<Vec<&str>>().join(" ")
|
||||
}
|
||||
}
|
||||
@@ -246,6 +270,11 @@ mod tests {
|
||||
use crate::param::Param;
|
||||
use crate::test_utils as test;
|
||||
|
||||
async fn assert_summary_texts(msg: &Message, ctx: &Context, expected: &str) {
|
||||
assert_eq!(msg.get_summary_text(ctx).await, expected);
|
||||
assert_eq!(msg.get_summary_text_without_prefix(ctx).await, expected);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_summary_text() {
|
||||
let d = test::TestContext::new().await;
|
||||
@@ -255,131 +284,81 @@ mod tests {
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(some_text.to_string());
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "bla bla").await; // for simple text, the type is not added to the summary
|
||||
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
msg.set_file("foo.jpg", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"📷 Image" // file names are not added for images
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "📷 Image").await; // file names are not added for images
|
||||
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
msg.set_text(some_text.to_string());
|
||||
msg.set_file("foo.jpg", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"📷 bla bla" // type is visible by emoji if text is set
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "📷 bla bla").await; // type is visible by emoji if text is set
|
||||
|
||||
let mut msg = Message::new(Viewtype::Video);
|
||||
msg.set_file("foo.mp4", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"🎥 Video" // file names are not added for videos
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "🎥 Video").await; // file names are not added for videos
|
||||
|
||||
let mut msg = Message::new(Viewtype::Video);
|
||||
msg.set_text(some_text.to_string());
|
||||
msg.set_file("foo.mp4", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"🎥 bla bla" // type is visible by emoji if text is set
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "🎥 bla bla").await; // type is visible by emoji if text is set
|
||||
|
||||
let mut msg = Message::new(Viewtype::Gif);
|
||||
msg.set_file("foo.gif", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"GIF" // file names are not added for GIFs
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "GIF").await; // file names are not added for GIFs
|
||||
|
||||
let mut msg = Message::new(Viewtype::Gif);
|
||||
msg.set_text(some_text.to_string());
|
||||
msg.set_file("foo.gif", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"GIF \u{2013} bla bla" // file names are not added for GIFs
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "GIF \u{2013} bla bla").await; // file names are not added for GIFs
|
||||
|
||||
let mut msg = Message::new(Viewtype::Sticker);
|
||||
msg.set_file("foo.png", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"Sticker" // file names are not added for stickers
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "Sticker").await; // file names are not added for stickers
|
||||
|
||||
let mut msg = Message::new(Viewtype::Voice);
|
||||
msg.set_file("foo.mp3", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"🎤 Voice message" // file names are not added for voice messages
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "🎤 Voice message").await; // file names are not added for voice messages
|
||||
|
||||
let mut msg = Message::new(Viewtype::Voice);
|
||||
msg.set_text(some_text.clone());
|
||||
msg.set_file("foo.mp3", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"🎤 bla bla" // `\u{2013}` explicitly checks for "EN DASH"
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "🎤 bla bla").await;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Audio);
|
||||
msg.set_file("foo.mp3", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"🎵 foo.mp3" // file name is added for audio
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "🎵 foo.mp3").await; // file name is added for audio
|
||||
|
||||
let mut msg = Message::new(Viewtype::Audio);
|
||||
msg.set_file("foo.mp3", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"🎵 foo.mp3" // file name is added for audio, empty text is not added
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "🎵 foo.mp3").await; // file name is added for audio, empty text is not added
|
||||
|
||||
let mut msg = Message::new(Viewtype::Audio);
|
||||
msg.set_text(some_text.clone());
|
||||
msg.set_file("foo.mp3", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"🎵 foo.mp3 \u{2013} bla bla" // file name and text added for audio
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "🎵 foo.mp3 \u{2013} bla bla").await; // file name and text added for audio
|
||||
|
||||
let mut msg = Message::new(Viewtype::File);
|
||||
msg.set_file("foo.bar", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"📎 foo.bar" // file name is added for files
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "📎 foo.bar").await; // file name is added for files
|
||||
|
||||
let mut msg = Message::new(Viewtype::File);
|
||||
msg.set_text(some_text.clone());
|
||||
msg.set_file("foo.bar", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"📎 foo.bar \u{2013} bla bla" // file name is added for files
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "📎 foo.bar \u{2013} bla bla").await; // file name is added for files
|
||||
|
||||
let mut msg = Message::new(Viewtype::VideochatInvitation);
|
||||
msg.set_text(some_text.clone());
|
||||
msg.set_file("foo.bar", None);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"Video chat invitation" // text is not added for videochat invitations
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "Video chat invitation").await; // text is not added for videochat invitations
|
||||
|
||||
// Forwarded
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(some_text.clone());
|
||||
msg.param.set_int(Param::Forwarded, 1);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"Forwarded: bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
assert_eq!(msg.get_summary_text(ctx).await, "Forwarded: bla bla"); // for simple text, the type is not added to the summary
|
||||
assert_eq!(msg.get_summary_text_without_prefix(ctx).await, "bla bla"); // skipping prefix used for reactions summaries
|
||||
|
||||
let mut msg = Message::new(Viewtype::File);
|
||||
msg.set_text(some_text.clone());
|
||||
@@ -389,14 +368,15 @@ mod tests {
|
||||
msg.get_summary_text(ctx).await,
|
||||
"Forwarded: 📎 foo.bar \u{2013} bla bla"
|
||||
);
|
||||
assert_eq!(
|
||||
msg.get_summary_text_without_prefix(ctx).await,
|
||||
"📎 foo.bar \u{2013} bla bla"
|
||||
); // skipping prefix used for reactions summaries
|
||||
|
||||
let mut msg = Message::new(Viewtype::File);
|
||||
msg.set_text(some_text.clone());
|
||||
msg.param.set(Param::File, "foo.bar");
|
||||
msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
|
||||
assert_eq!(
|
||||
msg.get_summary_text(ctx).await,
|
||||
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
|
||||
);
|
||||
assert_summary_texts(&msg, ctx, "Autocrypt Setup Message").await; // file name is not added for autocrypt setup messages
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ pub fn get_release_timestamp() -> i64 {
|
||||
*crate::release::DATE,
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
)
|
||||
.and_utc()
|
||||
.timestamp_millis()
|
||||
/ 1_000
|
||||
}
|
||||
@@ -205,7 +206,7 @@ async fn maybe_warn_on_bad_time(context: &Context, now: i64, known_past_timestam
|
||||
),
|
||||
)
|
||||
.await;
|
||||
if let Some(timestamp) = chrono::NaiveDateTime::from_timestamp_opt(now, 0) {
|
||||
if let Some(timestamp) = chrono::DateTime::<chrono::Utc>::from_timestamp(now, 0) {
|
||||
add_device_msg_with_importance(
|
||||
context,
|
||||
Some(
|
||||
@@ -232,7 +233,7 @@ async fn maybe_warn_on_outdated(context: &Context, now: i64, approx_compile_time
|
||||
if now > approx_compile_time + DC_OUTDATED_WARNING_DAYS * 24 * 60 * 60 {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = stock_str::update_reminder_msg_body(context).await;
|
||||
if let Some(timestamp) = chrono::NaiveDateTime::from_timestamp_opt(now, 0) {
|
||||
if let Some(timestamp) = chrono::DateTime::<chrono::Utc>::from_timestamp(now, 0) {
|
||||
add_device_msg(
|
||||
context,
|
||||
Some(
|
||||
@@ -1214,6 +1215,7 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
|
||||
NaiveDate::from_ymd_opt(2020, 9, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
)
|
||||
.and_utc()
|
||||
.timestamp_millis()
|
||||
/ 1_000;
|
||||
|
||||
@@ -1329,6 +1331,7 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
|
||||
NaiveDate::from_ymd_opt(2020, 9, 9).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
)
|
||||
.and_utc()
|
||||
.timestamp_millis()
|
||||
/ 1_000;
|
||||
assert!(get_release_timestamp() <= time());
|
||||
|
||||
Reference in New Issue
Block a user