Compare commits

..
Author SHA1 Message Date
link2xt d988f74cb1 test: test that MDN sent by core 2.23.0 can be received by the current core
There were reports that upgrading to 2.26.0 resulted in not receiving read receipts.
I did not manage to reproduce the problem, but created a cross-core test for MDNs.
2026-10-01 16:35:21 +00:00
56 changed files with 516 additions and 861 deletions
+14 -17
View File
@@ -20,21 +20,16 @@ permissions: {}
env:
RUSTFLAGS: -Dwarnings
RUST_VERSION: 1.99.0
RUST_VERSION: 1.98.1
# Minimum Supported Rust Version
MSRV: 1.89.0
PYTHON_VERSION: 3.15
jobs:
lint_rust:
name: Lint Rust
runs-on: ubuntu-latest
timeout-minutes: 60
env:
# Tests always unwind: match it so dependencies are only checked once.
CARGO_PROFILE_DEV_PANIC: unwind
steps:
- uses: actions/checkout@v7
with:
@@ -53,6 +48,8 @@ jobs:
run: cargo fmt --all -- --check
- name: Run clippy
run: scripts/clippy.sh
- name: Check with all features
run: cargo check --workspace --all-targets --all-features
- name: Check with only default features
run: cargo check --all-targets
@@ -135,19 +132,19 @@ jobs:
cache-bin: false
- name: Install nextest
uses: taiki-e/install-action@9983c65e42da123ff25d1f78505eb6de315aa172
uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc
with:
tool: nextest
- name: Tests
env:
RUST_BACKTRACE: 1
run: cargo nextest run --workspace --exclude deltachat-jsonrpc-bindings --locked
run: cargo nextest run --workspace --locked
- name: Doc-Tests
env:
RUST_BACKTRACE: 1
run: cargo test --workspace --exclude deltachat-jsonrpc-bindings --locked --doc
run: cargo test --workspace --locked --doc
- name: Test cargo vendor
run: cargo vendor
@@ -270,11 +267,11 @@ jobs:
fail-fast: false
matrix:
include:
# Currently used Python version.
# Currently used Rust version.
- os: ubuntu-latest
python: latest
python: 3.14
- os: macos-latest
python: latest
python: 3.14
# PyPy tests
- os: ubuntu-latest
@@ -305,7 +302,7 @@ jobs:
- name: Install python
uses: actions/setup-python@v7.0.0
with:
python-version: ${{ matrix.python == 'latest' && env.PYTHON_VERSION || matrix.python }}
python-version: ${{ matrix.python }}
- name: Install tox
run: pip install tox
@@ -326,11 +323,11 @@ jobs:
matrix:
include:
- os: ubuntu-latest
python: latest
python: 3.14
- os: macos-latest
python: latest
python: 3.14
- os: windows-latest
python: latest
python: 3.14
# PyPy tests
- os: ubuntu-latest
@@ -353,7 +350,7 @@ jobs:
- name: Install python
uses: actions/setup-python@v7.0.0
with:
python-version: ${{ matrix.python == 'latest' && env.PYTHON_VERSION || matrix.python }}
python-version: ${{ matrix.python }}
- name: Install tox
run: pip install tox
+7 -7
View File
@@ -34,7 +34,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build deltachat-rpc-server binaries
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
@@ -58,7 +58,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build deltachat-rpc-server wheels
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux-wheel
@@ -82,7 +82,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build deltachat-rpc-server binaries
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
@@ -106,7 +106,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build deltachat-rpc-server wheels
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-wheel
@@ -157,7 +157,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build deltachat-rpc-server binaries
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
@@ -181,7 +181,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build deltachat-rpc-server wheels
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android-wheel
@@ -208,7 +208,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Download Linux aarch64 binary
uses: actions/download-artifact@v7
+3 -3
View File
@@ -27,7 +27,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- run: nix fmt flake.nix nix/ -- --check
build:
@@ -85,7 +85,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- run: nix build .#${{ matrix.installable }}
build-macos:
@@ -106,5 +106,5 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- run: nix build .#${{ matrix.installable }}
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build
run: nix build .#deltachat-repl-win64
- name: Upload binary
+2 -2
View File
@@ -41,7 +41,7 @@ jobs:
show-progress: false
persist-credentials: false
fetch-depth: 0 # Fetch history to calculate VCS version number.
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build Python documentation
run: nix build .#python-docs
- name: Upload to py.delta.chat
@@ -63,7 +63,7 @@ jobs:
show-progress: false
persist-credentials: false
fetch-depth: 0 # Fetch history to calculate VCS version number.
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
- name: Build C documentation
run: nix build .#docs
- name: Upload to c.delta.chat
+1 -1
View File
@@ -23,4 +23,4 @@ jobs:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
-80
View File
@@ -1,84 +1,5 @@
# Changelog
## [2.63.0] - 2026-10-09
### API-Changes
- add `PinnedMessagesChanged` event.
- [**breaking**]: remove unused `dc_chatlist_get_summary2()` ([#8812](https://github.com/chatmail/core/pull/8812))
- new `dc_msg_get_summary_text()` ([#8813](https://github.com/chatmail/core/pull/8813)).
### Features / Changes
- connect to the most recently successfully used SMTP transport first.
- re-add "second device added" message if deleted soon after transfer ([#8758](https://github.com/chatmail/core/pull/8758)).
- do not request MDNs for unencrypted messages.
- do not send unencrypted MDNs.
- add context to HTTP(S) proxy connection errors.
- deltachat-repl: remove "reset" command.
### Fixes
- Correctly percent-encode addresses in securejoin invite codes ([#8747](https://github.com/chatmail/core/pull/8747)).
- regenerate deltachat.pc if env changed ([#8752](https://github.com/chatmail/core/pull/8752)).
- delete handled securejoin messages on all relays.
- add headers to unencrypted MDNs.
- In SMTP loop, reset `transport_id` and `from` when disconnecting ([#8797](https://github.com/chatmail/core/pull/8797)).
- deltachat-rpc-client: shutdown all threads after RPC server crash.
- (linux-only) make sure large attachments return memory to kernel.
- do not reencode animated WebPs into JPEG.
### CI
- update Rust to 1.99.0.
- speedup lint job and the Rust test builds
### Refactor
- [**breaking**] stop saving legacy config values when configuring a transport.
- core does not set deprecated config values `addr`, `mail_pw` etc. Use `list_transports()` API instead.
- get rid of dead sticker-related code in check_or_recode_to_size().
- factor add_headers_to_part() out of mimefactory::add_headers_to_protected_part().
- replace MimeFactory::from_mdn() with a standalone mimefactory::mdn().
- take securejoin addresses from transport list.
- deltachat-repl: move all commands to cmdline.rs.
- do not guess image format from file extension on I/O error.
- reduce macro-generated lines by >70%, and drop tracing.
- stop excluding `COPYUID` responses from the logs.
### Tests
- move pinned messages tests into pinned_messages_tests module.
- Add basic tests for rendering unencrypted messages ([#8736](https://github.com/chatmail/core/pull/8736)).
- do not ignore errors in add_pseudo_transport().
- test sending and receiving long filename with emojis.
### Miscellaneous Tasks
- update rPGP from 0.20.0 to 0.21.0.
- reduce noise created by key-contact migration.
- enable `clippy::unnecessary_wraps`, fix `clippy::redundant_clone` and `clippy::string_lit_as_bytes` suggestions.
- add chatmail.au.
- add chatmail.cc ([#8756](https://github.com/chatmail/core/pull/8756)).
- remove some `unwrap()` calls.
- remove unused "sdp" dependency.
- cargo: bump smallvec from 1.15.2 to 1.16.1.
- cargo: bump syn from 3.0.4 to 3.0.6.
- deps: bump zizmorcore/zizmor-action from 0.6.2 to 0.6.4.
- cargo: bump mailparse from 0.16.1 to 0.17.0.
- cargo: bump uuid from 1.25.0 to 1.26.1.
- deps: bump cachix/install-nix-action from 31.11.0 to 31.11.1.
- cargo: bump hyper from 1.10.1 to 1.11.1.
- cargo: bump tokio-rustls from 0.26.4 to 0.26.5.
- cargo: bump thiserror from 2.0.20 to 2.0.21.
- deps: bump taiki-e/install-action from 2.86.7 to 2.87.20.
- cargo: bump hyper-util from 0.1.20 to 0.1.21.
- cargo: bump dirs from 6.0.0 to 7.0.0.
- move deltachat-time to dev dependencies.
- update astral-tokio-tar from 0.6.4 to 0.7.0.
- update async-imap to 0.12.0.
- update mail-builder to 1.0.
## [2.62.0] - 2026-09-22
### API-Changes
@@ -8982,4 +8903,3 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
[2.60.0]: https://github.com/chatmail/core/compare/v2.59.0..v2.60.0
[2.61.0]: https://github.com/chatmail/core/compare/v2.60.0..v2.61.0
[2.62.0]: https://github.com/chatmail/core/compare/v2.61.0..v2.62.0
[2.63.0]: https://github.com/chatmail/core/compare/v2.62.0..v2.63.0
Generated
+82 -110
View File
@@ -194,9 +194,9 @@ dependencies = [
[[package]]
name = "astral-tokio-tar"
version = "0.7.0"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f2e989b33246fe9240d39accf4dd9a01e0b6c1f3ce9dd095e0a47fa02505523"
checksum = "b18457efd137254e016bbde5e1d88df61c4e1a5ae2223746e56123bac6af2463"
dependencies = [
"futures-core",
"libc",
@@ -205,7 +205,6 @@ dependencies = [
"rustix 1.1.4",
"tokio",
"tokio-stream",
"zerocopy 0.8.59",
]
[[package]]
@@ -272,19 +271,19 @@ dependencies = [
[[package]]
name = "async-imap"
version = "0.12.0"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f97b87216c9f0ccc63c516263169085fa34bba25633f6d96c6a9b9ca1fd5f70a"
checksum = "9a6728e0f7931b36d725ac234fcb02539e9f7888dbeaaa8a18d9ea5792181570"
dependencies = [
"async-channel 2.5.0",
"async-compression",
"base64 0.23.1",
"base64",
"bytes",
"chrono",
"futures-util",
"futures",
"imap-proto",
"log",
"nom 8.0.0",
"nom 7.1.3",
"pin-project",
"pin-utils",
"self_cell",
@@ -311,7 +310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37dd6b179962fe4048a6f81d4c0d7ed419a21fdf49204b4c6b04971693358e79"
dependencies = [
"native-tls",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"url",
]
@@ -323,12 +322,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55219982f938e74491ba85dc4e49cefe8096b1e8f49348c67180a7d244988dca"
dependencies = [
"anyhow",
"base64 0.22.1",
"base64",
"futures",
"log",
"nom 8.0.0",
"pin-project",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
]
@@ -364,7 +363,7 @@ dependencies = [
"crc32fast",
"futures-lite",
"pin-project",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tokio-util",
]
@@ -436,12 +435,6 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "base64ct"
version = "1.6.0"
@@ -466,7 +459,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.118",
"thiserror 2.0.21",
"thiserror 2.0.20",
]
[[package]]
@@ -839,7 +832,7 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e"
dependencies = [
"base64 0.22.1",
"base64",
"encoding_rs",
]
@@ -1334,7 +1327,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "2.64.0-dev"
version = "2.63.0-dev"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -1344,7 +1337,7 @@ dependencies = [
"async-native-tls",
"async-smtp",
"async_zip",
"base64 0.23.1",
"base64",
"blake3",
"brotli",
"bytes",
@@ -1405,13 +1398,14 @@ dependencies = [
"tempfile",
"testdir",
"textwrap",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tokio-io-timeout",
"tokio-rustls",
"tokio-stream",
"tokio-util",
"toml",
"tracing",
"url",
"uuid",
"walkdir",
@@ -1440,11 +1434,11 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "2.64.0-dev"
version = "2.63.0-dev"
dependencies = [
"anyhow",
"async-channel 2.5.0",
"base64 0.23.1",
"base64",
"deltachat",
"deltachat-contact-tools",
"futures",
@@ -1461,14 +1455,14 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc-bindings"
version = "2.64.0-dev"
version = "2.63.0-dev"
dependencies = [
"deltachat-jsonrpc",
]
[[package]]
name = "deltachat-repl"
version = "2.64.0-dev"
version = "2.63.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1484,13 +1478,12 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "2.64.0-dev"
version = "2.63.0-dev"
dependencies = [
"anyhow",
"deltachat",
"deltachat-jsonrpc",
"futures-lite",
"libc",
"log",
"serde",
"serde_json",
@@ -1509,12 +1502,12 @@ name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote",
"syn 3.0.6",
"syn 3.0.4",
]
[[package]]
name = "deltachat_ffi"
version = "2.64.0-dev"
version = "2.63.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1524,7 +1517,7 @@ dependencies = [
"num-traits",
"rand 0.9.4",
"serde_json",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"yerpc",
]
@@ -1683,9 +1676,9 @@ dependencies = [
[[package]]
name = "dirs"
version = "7.0.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
]
@@ -2228,7 +2221,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
"syn 3.0.4",
]
[[package]]
@@ -2471,7 +2464,7 @@ dependencies = [
"once_cell",
"rand 0.9.4",
"ring",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tinyvec",
"tokio",
"tracing",
@@ -2494,7 +2487,7 @@ dependencies = [
"rand 0.9.4",
"resolv-conf",
"smallvec",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tracing",
]
@@ -2660,9 +2653,9 @@ dependencies = [
[[package]]
name = "hyper"
version = "1.11.1"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [
"atomic-waker",
"bytes",
@@ -2700,16 +2693,15 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.21"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.1.0",
"http-body",
"httparse",
"hyper",
"libc",
"pin-project-lite",
@@ -2947,11 +2939,11 @@ dependencies = [
[[package]]
name = "imap-proto"
version = "0.17.0"
version = "0.16.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ccf963d57074747b455398a1763d174da80bcaba6f51e3671a82252b531a68b"
checksum = "de555d9526462b6f9ece826a26fb7c67eca9a0245bd9ff84fa91972a5d5d8856"
dependencies = [
"nom 8.0.0",
"nom 7.1.3",
]
[[package]]
@@ -3052,7 +3044,7 @@ dependencies = [
"strum 0.26.2",
"stun-rs",
"surge-ping",
"thiserror 2.0.21",
"thiserror 2.0.20",
"time",
"tokio",
"tokio-stream",
@@ -3077,7 +3069,7 @@ dependencies = [
"ed25519-dalek",
"rand_core 0.6.4",
"serde",
"thiserror 2.0.21",
"thiserror 2.0.20",
"url",
]
@@ -3119,7 +3111,7 @@ dependencies = [
"rand_core 0.6.4",
"serde",
"serde-error",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tokio-util",
"tracing",
@@ -3164,7 +3156,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2 0.5.9",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tracing",
"web-time",
@@ -3184,7 +3176,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tinyvec",
"tracing",
"web-time",
@@ -3239,7 +3231,7 @@ dependencies = [
"sha1",
"strum 0.26.2",
"stun-rs",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tokio-rustls",
"tokio-util",
@@ -3451,15 +3443,15 @@ checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd"
[[package]]
name = "mail-builder"
version = "1.0.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccbc27cb02a58a311a404769a22f5abf63fe3173b290d80be7c534f9c7023151"
checksum = "4c942e8a4b83f9351236c1e531ea9fa0237913d63c7fc36818430e0128a1ddf3"
[[package]]
name = "mailparse"
version = "0.17.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8469955699e572455f8e1a40689e07ab309911d659632f65062598192254c091"
checksum = "60819a97ddcb831a5614eb3b0174f3620e793e97e09195a395bfa948fd68ed2f"
dependencies = [
"charset",
"data-encoding",
@@ -3724,7 +3716,7 @@ dependencies = [
"log",
"netlink-packet-core",
"netlink-sys",
"thiserror 2.0.21",
"thiserror 2.0.20",
]
[[package]]
@@ -4187,7 +4179,7 @@ version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
dependencies = [
"base64 0.22.1",
"base64",
"serde",
]
@@ -4213,7 +4205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
dependencies = [
"memchr",
"thiserror 2.0.21",
"thiserror 2.0.20",
"ucd-trie",
]
@@ -4253,16 +4245,16 @@ dependencies = [
[[package]]
name = "pgp"
version = "0.21.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae70f4d9325a391db30d115d6191a7bb67cec856aa0bba131290f0c4e09a532a"
checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1"
dependencies = [
"aead",
"aes",
"aes-gcm",
"aes-kw",
"argon2",
"base64 0.22.1",
"base64",
"bitfields",
"block-padding",
"blowfish",
@@ -4389,7 +4381,7 @@ dependencies = [
"serde",
"sha1_smol",
"simple-dns",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tracing",
"url",
@@ -4541,7 +4533,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6db66007eac4a0ec8331d0d20c734bd64f6445d64bbaf0d0a27fea7a054e36"
dependencies = [
"base64 0.22.1",
"base64",
"bytes",
"derive_more 1.0.0",
"futures-lite",
@@ -4602,7 +4594,7 @@ version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy 0.7.35",
"zerocopy",
]
[[package]]
@@ -4787,7 +4779,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2 0.5.9",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tracing",
]
@@ -4808,7 +4800,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tinyvec",
"tracing",
"web-time",
@@ -5047,7 +5039,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.21",
"thiserror 2.0.20",
]
[[package]]
@@ -5097,7 +5089,7 @@ version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
dependencies = [
"base64 0.22.1",
"base64",
"bytes",
"futures-core",
"futures-util",
@@ -5556,7 +5548,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
"syn 3.0.4",
]
[[package]]
@@ -5691,7 +5683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78db9c9912c90ea7487f49bc149b329b535806bfa12b740fbade73f573a3d9f"
dependencies = [
"aes",
"base64 0.22.1",
"base64",
"blake3",
"byte_string",
"bytes",
@@ -5712,7 +5704,7 @@ dependencies = [
"shadowsocks-crypto",
"socket2 0.5.9",
"spin 0.10.1",
"thiserror 2.0.21",
"thiserror 2.0.20",
"tokio",
"tokio-tfo",
"trait-variant",
@@ -5819,14 +5811,14 @@ dependencies = [
"sha3",
"signature",
"typenum",
"zerocopy 0.7.35",
"zerocopy",
]
[[package]]
name = "smallvec"
version = "1.16.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "smawk"
@@ -5991,7 +5983,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb921f10397d5669e1af6455e9e2d367bf1f9cebcd6b1dd1dc50e19f6a9ac2ac"
dependencies = [
"base64 0.22.1",
"base64",
"bounded-integer",
"byteorder",
"crc",
@@ -6055,9 +6047,9 @@ dependencies = [
[[package]]
name = "syn"
version = "3.0.6"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
@@ -6201,11 +6193,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.21"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl 2.0.21",
"thiserror-impl 2.0.20",
]
[[package]]
@@ -6221,13 +6213,13 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.21"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
"syn 3.0.4",
]
[[package]]
@@ -6347,9 +6339,9 @@ dependencies = [
[[package]]
name = "tokio-rustls"
version = "0.26.5"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
@@ -6406,7 +6398,7 @@ version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fcaf159b4e7a376b05b5bfd77bfd38f3324f5fce751b4213bfc7eaa47affb4e"
dependencies = [
"base64 0.22.1",
"base64",
"bytes",
"futures-core",
"futures-sink",
@@ -6749,9 +6741,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.26.1"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc"
dependencies = [
"getrandom 0.4.3",
"js-sys",
@@ -7415,7 +7407,7 @@ dependencies = [
"futures",
"log",
"serde",
"thiserror 2.0.21",
"thiserror 2.0.20",
"windows 0.59.0",
"windows-core 0.59.0",
]
@@ -7589,16 +7581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb"
dependencies = [
"zerocopy-derive 0.8.59",
"zerocopy-derive",
]
[[package]]
@@ -7612,17 +7595,6 @@ dependencies = [
"syn 2.0.118",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.118",
]
[[package]]
name = "zerofrom"
version = "0.1.5"
+9 -8
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "2.64.0-dev"
version = "2.63.0-dev"
edition = "2024"
license = "MPL-2.0"
rust-version = "1.89"
@@ -36,6 +36,7 @@ strip = true
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
deltachat-time = { path = "./deltachat-time" }
deltachat-contact-tools = { workspace = true }
format-flowed = { path = "./format-flowed" }
ratelimit = { path = "./deltachat-ratelimit" }
@@ -43,7 +44,7 @@ ratelimit = { path = "./deltachat-ratelimit" }
anyhow = { workspace = true }
async-broadcast = "0.7.2"
async-channel = { workspace = true }
async-imap = { version = "0.12.0", default-features = false, features = ["runtime-tokio", "compress"] }
async-imap = { version = "0.11.3", default-features = false, features = ["runtime-tokio", "compress"] }
async-native-tls = { version = "0.6", default-features = false, features = ["runtime-tokio"] }
async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] }
async_zip = { version = "0.0.18", default-features = false, features = ["deflate", "tokio-fs"] }
@@ -69,7 +70,7 @@ iroh-gossip = { version = "0.35", default-features = false, features = ["net"] }
iroh = { version = "0.35", default-features = false }
kamadak-exif = "0.6.1"
libc = { workspace = true }
mail-builder = { version = "1.0.0", default-features = false }
mail-builder = { version = "0.5.0", default-features = false }
mailparse = { workspace = true }
mime = "0.3.17"
num_cpus = "1.17"
@@ -77,7 +78,7 @@ num-derive = "0.4"
num-traits = { workspace = true }
parking_lot = "0.12.4"
percent-encoding = "2.3"
pgp = { version = "0.21.0", features = ["pqc"], default-features = false }
pgp = { version = "0.20.0", features = ["draft-pqc"], default-features = false }
pin-project = "1"
qrcodegen = "1.7.0"
quick-xml = { version = "0.41", features = ["escape-html"] }
@@ -100,10 +101,11 @@ thiserror = { workspace = true }
tokio-io-timeout = "1.2.1"
tokio-rustls = { version = "0.26.2", default-features = false, features = ["tls12", "brotli"] }
tokio-stream = { version = "0.1.17", features = ["fs"] }
astral-tokio-tar = { version = "0.7.0", default-features = false }
astral-tokio-tar = { version = "0.6.3", default-features = false }
tokio-util = { workspace = true }
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
toml = "0.9"
tracing = "0.1.41"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
walkdir = "2.5.0"
@@ -112,7 +114,6 @@ webpki-roots = "0.26.8"
[dev-dependencies]
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
criterion = { version = "0.8.1", features = ["async_tokio"] }
deltachat-time = { path = "./deltachat-time" }
futures-lite = { workspace = true }
log = { workspace = true }
nu-ansi-term = { workspace = true }
@@ -178,7 +179,7 @@ harness = false
[workspace.dependencies]
anyhow = "1"
async-channel = "2.5.0"
base64 = { version = "0.23", default-features = false, features = ["std"] }
base64 = "0.22"
chrono = { version = "0.4.44", default-features = false }
deltachat-contact-tools = { path = "deltachat-contact-tools" }
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
@@ -187,7 +188,7 @@ futures = "0.3.32"
futures-lite = "2.6.1"
libc = "0.2"
log = "0.4"
mailparse = "0.17.0"
mailparse = "0.16.1"
nu-ansi-term = "0.50"
num-traits = "0.2"
rand = "0.9"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "2.64.0-dev"
version = "2.63.0-dev"
description = "Deltachat FFI"
edition = "2024"
license = "MPL-2.0"
+22 -29
View File
@@ -3543,6 +3543,28 @@ uint32_t dc_chatlist_get_msg_id (const dc_chatlist_t* chatlist, siz
dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, size_t index, dc_chat_t* chat);
/**
* Create a chatlist summary item when the chatlist object is already unref()'d.
*
* This function is similar to dc_chatlist_get_summary(), however,
* it takes the chat ID and the message ID as returned by dc_chatlist_get_chat_id() and dc_chatlist_get_msg_id()
* as arguments. The chatlist object itself is not needed directly.
*
* This maybe useful if you convert the complete object into a different representation
* as done e.g. in the node-bindings.
* If you have access to the chatlist object in some way, using this function is not recommended,
* use dc_chatlist_get_summary() in this case instead.
*
* @memberof dc_context_t
* @param context The context object.
* @param chat_id The chat ID to get a summary for.
* @param msg_id The message ID to get a summary for.
* @return The summary as an dc_lot_t object, see dc_chatlist_get_summary() for details.
* Must be freed using dc_lot_unref(). NULL is never returned.
*/
dc_lot_t* dc_chatlist_get_summary2 (dc_context_t* context, uint32_t chat_id, uint32_t msg_id);
/**
* Get info summary for a chat, in JSON format.
*
@@ -4291,7 +4313,6 @@ dc_lot_t* dc_msg_get_summary (const dc_msg_t* msg, const dc_cha
* notifications.
*
* @memberof dc_msg_t
* @deprecated 2026-10-08, use dc_msg_get_summary_text(msg, 1, 1, approx_chars) instead
* @param msg The message object.
* @param approx_characters A rough length of the expected string.
* @return A summary for the given messages.
@@ -4301,34 +4322,6 @@ dc_lot_t* dc_msg_get_summary (const dc_msg_t* msg, const dc_cha
char* dc_msg_get_summarytext (const dc_msg_t* msg, int approx_characters);
/**
* Get a message summary.
* Used for notifications, pinned messages and for copying texts to clipboard.
*
* @memberof dc_msg_t
* @param msg The message object.
* @param add_forwarded 1=add a hint if a message was forwarded.
* 0=do not add the hint;
* this is allowed only if the message is shown without a username, e.g. for pinned messages.
* @param add_type_emoji 1=add the type of the message as an emoji prefix.
* 0=do not add type emoji;
* this is allowed only if the message type is visible otherwise,
* close to where the emoji would be displayed otherwise
* (eg. images in pinned messages preceding summary)
* @param approx_chars >0=convert linebreaks to spaces and truncate the line at about the given number of characters;
* for single-line UI elements such as pinned messages or notifications;
* additionally, UIs will most times clip the text to the available space.
* 0=leave linebreaks as is and return the full text, useful e.g. for copying to clipboard.
* @return A summary for the given message.
* The returned string must be released using dc_str_unref().
* Returns an empty string on errors, never returns NULL.
*/
char* dc_msg_get_summary_text (const dc_msg_t* msg, int add_forwarded, int add_type_emoji, int approx_chars);
/**
* Get the name that should be shown over the message (in a group chat) instead of the contact
* display name, or NULL.
+37 -31
View File
@@ -12,6 +12,7 @@
extern crate human_panic;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt::Write;
use std::future::Future;
use std::mem::ManuallyDrop;
@@ -46,6 +47,7 @@ mod dc_array;
mod lot;
mod string;
use deltachat::chatlist::Chatlist;
use self::string::*;
@@ -2842,6 +2844,34 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
Box::into_raw(Box::new(summary.into()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dc_chatlist_get_summary2(
context: *mut dc_context_t,
chat_id: u32,
msg_id: u32,
) -> *mut dc_lot_t {
if context.is_null() {
eprintln!("ignoring careless call to dc_chatlist_get_summary2()");
return ptr::null_mut();
}
let ctx = unsafe { &*context };
let msg_id = if msg_id == 0 {
None
} else {
Some(MsgId::new(msg_id))
};
let summary = block_on(Chatlist::get_summary2(
ctx,
ChatId::new(chat_id),
msg_id,
None,
))
.context("get_summary2 failed")
.log_err(ctx)
.unwrap_or_default();
Box::into_raw(Box::new(summary.into()))
}
// dc_chat_t
/// FFI struct for [dc_chat_t]
@@ -3472,7 +3502,6 @@ pub unsafe extern "C" fn dc_msg_get_summary(
Box::into_raw(Box::new(summary.into()))
}
// deprecated, use dc_msg_get_summary_text instead
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dc_msg_get_summarytext(
msg: *mut dc_msg_t,
@@ -3484,37 +3513,14 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
}
let ffi_msg = unsafe { &mut *msg };
let add_forwarded = true;
let add_type_emoji = true;
block_on(ffi_msg.message.get_summary_text_ext(
&ffi_msg.context,
add_forwarded,
add_type_emoji,
usize::try_from(approx_characters).unwrap_or_default(),
))
.strdup()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dc_msg_get_summary_text(
msg: *mut dc_msg_t,
add_forwarded: libc::c_int,
add_type_emoji: libc::c_int,
approx_chars: libc::c_int,
) -> *mut libc::c_char {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_get_summary_text()");
return "".strdup();
let summary = block_on(ffi_msg.message.get_summary(&ffi_msg.context, None))
.context("dc_msg_get_summarytext failed")
.log_err(&ffi_msg.context)
.unwrap_or_default();
match usize::try_from(approx_characters) {
Ok(chars) => summary.truncated_text(chars).strdup(),
Err(_) => summary.text.strdup(),
}
let ffi_msg = unsafe { &mut *msg };
block_on(ffi_msg.message.get_summary_text_ext(
&ffi_msg.context,
add_forwarded != 0,
add_type_emoji != 0,
usize::try_from(approx_chars).unwrap_or(0),
))
.strdup()
}
#[unsafe(no_mangle)]
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc-bindings"
version = "2.64.0-dev"
version = "2.63.0-dev"
description = "Autogenerate DeltaChat JSON-RPC API bindings at build time"
edition = "2024"
license = "MPL-2.0"
@@ -54,5 +54,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "2.64.0-dev"
"version": "2.63.0-dev"
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "2.64.0-dev"
version = "2.63.0-dev"
description = "DeltaChat JSON-RPC API"
edition = "2024"
license = "MPL-2.0"
@@ -31,6 +31,8 @@ pub enum ChatListItemFetchResult {
summary_text1: String,
summary_text2: String,
summary_status: u32,
/// showing preview if last chat message is image
summary_preview_image: Option<String>,
/// True if the chat is encrypted.
/// This means that all messages in the chat are encrypted,
@@ -101,6 +103,8 @@ pub(crate) async fn get_chat_list_item_by_id(
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
let summary_text2 = summary.text.to_owned();
let summary_preview_image = summary.thumbnail_path;
let visibility = chat.get_visibility();
let avatar_path = chat
@@ -153,6 +157,7 @@ pub(crate) async fn get_chat_list_item_by_id(
summary_text1,
summary_text2,
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
summary_preview_image,
is_encrypted: chat.is_encrypted(ctx).await?,
is_group: chat.get_type() == Chattype::Group,
fresh_message_counter,
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "2.64.0-dev"
version = "2.63.0-dev"
license = "MPL-2.0"
edition = "2024"
repository = "https://github.com/chatmail/core"
@@ -8,7 +8,7 @@ repository = "https://github.com/chatmail/core"
[dependencies]
anyhow = { workspace = true }
deltachat = { workspace = true, features = ["internals"]}
dirs = "7"
dirs = "6"
log = { workspace = true }
nu-ansi-term = { workspace = true }
qr2term = "0.3.3"
+1 -49
View File
@@ -20,7 +20,7 @@ use deltachat::message::{self, Message, MessageState, MsgId, Viewtype};
use deltachat::mimeparser::SystemMessage;
use deltachat::peer_channels::{send_webxdc_realtime_advertisement, send_webxdc_realtime_data};
use deltachat::qr::*;
use deltachat::qr_code_generator::{create_qr_svg, get_securejoin_qr_svg};
use deltachat::qr_code_generator::create_qr_svg;
use deltachat::reaction::send_reaction;
use deltachat::receive_imf::*;
use deltachat::sql;
@@ -346,57 +346,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
============================================="
),
},
"connect" => {
context.start_io().await;
}
"disconnect" => {
context.stop_io().await;
}
"fetch" => {
context.background_fetch().await?;
}
"configure" => {
context.configure().await?;
}
"has-backup" => {
has_backup(&context, blobdir).await?;
}
"clear" => {
println!("\n\n\n");
print!("\x1b[1;1H\x1b[2J");
}
"getqr" | "getbadqr" => {
context.start_io().await;
let group = arg1.parse::<u32>().ok().map(ChatId::new);
let mut qr = deltachat::securejoin::get_securejoin_qr(&context, group).await?;
if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 {
qr.replace_range(12..22, "0000000000")
}
println!("{qr}");
qr2term::print_qr(qr.as_str())?;
}
}
"getqrsvg" => {
context.start_io().await;
let group = arg1.parse::<u32>().ok().map(ChatId::new);
let file = dirs::home_dir().unwrap_or_default().join("qr.svg");
match get_securejoin_qr_svg(&context, group).await {
Ok(svg) => {
fs::write(&file, svg).await?;
println!("QR code svg written to: {file:#?}");
}
Err(err) => {
bail!("Failed to get QR code svg: {err}");
}
}
}
"joinqr" => {
context.start_io().await;
if !arg0.is_empty() {
deltachat::securejoin::join_securejoin(&context, arg1).await?;
}
}
"export-backup" => {
let dir = dirs::home_dir().unwrap_or_default();
imex(
+89 -8
View File
@@ -13,6 +13,8 @@ use anyhow::{Error, bail};
use deltachat::EventType;
use deltachat::chat::ChatId;
use deltachat::context::*;
use deltachat::qr_code_generator::get_securejoin_qr_svg;
use deltachat::securejoin::*;
use log::{error, info, warn};
use nu_ansi_term::Color;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
@@ -23,6 +25,7 @@ use rustyline::validate::Validator;
use rustyline::{
Cmd, CompletionType, Config, Context as RustyContext, EditMode, Editor, Helper, KeyEvent,
};
use tokio::fs;
use tokio::runtime::Handle;
use tracing_subscriber::EnvFilter;
@@ -349,16 +352,23 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
Ok(line) => {
// TODO: ignore "set mail_pw"
rl.add_history_entry(line.as_str())?;
let line = line.trim();
if matches!(line, "exit" | "quit") {
println!("Exiting ...");
let should_continue = Handle::current().block_on(async {
match handle_cmd(line.trim(), ctx.clone(), &mut selected_chat).await {
Ok(ExitResult::Continue) => true,
Ok(ExitResult::Exit) => {
println!("Exiting ...");
false
}
Err(err) => {
eprintln!("Error: {err:#}");
true
}
}
});
if !should_continue {
break;
}
if let Err(err) =
Handle::current().block_on(cmdline(ctx.clone(), line, &mut selected_chat))
{
eprintln!("Error: {err:#}");
}
}
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
println!("Exiting...");
@@ -382,6 +392,77 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
Ok(())
}
#[derive(Debug)]
enum ExitResult {
Continue,
Exit,
}
async fn handle_cmd(
line: &str,
ctx: Context,
selected_chat: &mut ChatId,
) -> Result<ExitResult, Error> {
let mut args = line.splitn(2, ' ');
let arg0 = args.next().unwrap_or_default();
let arg1 = args.next().unwrap_or_default();
match arg0 {
"connect" => {
ctx.start_io().await;
}
"disconnect" => {
ctx.stop_io().await;
}
"fetch" => {
ctx.background_fetch().await?;
}
"configure" => {
ctx.configure().await?;
}
"clear" => {
println!("\n\n\n");
print!("\x1b[1;1H\x1b[2J");
}
"getqr" | "getbadqr" => {
ctx.start_io().await;
let group = arg1.parse::<u32>().ok().map(ChatId::new);
let mut qr = get_securejoin_qr(&ctx, group).await?;
if !qr.is_empty() {
if arg0 == "getbadqr" && qr.len() > 40 {
qr.replace_range(12..22, "0000000000")
}
println!("{qr}");
qr2term::print_qr(qr.as_str())?;
}
}
"getqrsvg" => {
ctx.start_io().await;
let group = arg1.parse::<u32>().ok().map(ChatId::new);
let file = dirs::home_dir().unwrap_or_default().join("qr.svg");
match get_securejoin_qr_svg(&ctx, group).await {
Ok(svg) => {
fs::write(&file, svg).await?;
println!("QR code svg written to: {file:#?}");
}
Err(err) => {
bail!("Failed to get QR code svg: {err}");
}
}
}
"joinqr" => {
ctx.start_io().await;
if !arg0.is_empty() {
join_securejoin(&ctx, arg1).await?;
}
}
"exit" | "quit" => return Ok(ExitResult::Exit),
_ => cmdline(ctx.clone(), line, selected_chat).await?,
}
Ok(ExitResult::Continue)
}
#[tokio::main]
async fn main() -> Result<(), Error> {
tracing_subscriber::fmt()
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "2.64.0-dev"
version = "2.63.0-dev"
license = "MPL-2.0"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
@@ -149,10 +149,7 @@ class Rpc:
def close(self) -> None:
"""Terminate RPC server process and wait until the reader loop finishes."""
self.closing = True
# JSON-RPC error may happen if RPC server process has crashed already.
# We still want to shutdown all threads in this case.
with contextlib.suppress(JsonRpcError):
self.stop_io_for_all_accounts()
self.stop_io_for_all_accounts()
# Let `events_loop` stop cleanly on `closing` before the pipe goes away,
# otherwise it might exit through an "RPC server closed" error instead.
self.events_thread.join()
+14 -1
View File
@@ -3,7 +3,7 @@ import time
import pytest
from deltachat_rpc_client import DeltaChat, Rpc
from deltachat_rpc_client import DeltaChat, EventType, Rpc
def test_install_venv_and_use_other_core(tmp_path, get_core_python_env):
@@ -74,6 +74,19 @@ def test_send_and_receive_message(alice_and_remote_bob) -> None:
assert msg.get_snapshot().text == "hello"
def test_old_core_mdn(alice_and_remote_bob) -> None:
"""Test that old core can send a read receipt that we can receive."""
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.23.0")
# Create chat with Alice so the message does not arrive as contact request.
remote_eval("bob_contact_alice.create_chat()")
alice_chat = alice_contact_bob.create_chat()
alice_chat.send_text("Hello!")
remote_eval("bob.wait_for_incoming_msg().mark_seen()")
alice.wait_for_event(EventType.MSG_READ)
def test_second_device(acf, alice_and_remote_bob) -> None:
"""Test setting up current version as a second device for old version."""
_alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.23.0")
-33
View File
@@ -1,33 +0,0 @@
import os
import sys
import pytest
def anonymous_mib(pid):
with open(f"/proc/{pid}/smaps_rollup") as f:
for line in f:
if line.startswith("Anonymous:"):
return int(line.split()[1]) // 1024
raise LookupError("Anonymous")
@pytest.mark.skipif(sys.platform != "linux", reason="reads /proc")
def test_attachment_memory_is_returned(acf, rpc, tmp_path):
# See also comments for `tune_malloc` in `deltachat-rpc-server/src/main.rs`
ac1, ac2 = acf.get_online_accounts(2)
chat1 = acf.get_accepted_chat(ac1, ac2)
chat2 = ac2.create_chat(ac1)
blob = tmp_path / "blob.bin"
blob.write_bytes(os.urandom(20 << 20))
before = anonymous_mib(rpc.process.pid)
for sender_chat, receiver in ((chat1, ac2), (chat2, ac1)):
sender_chat.send_file(str(blob))
event = receiver.wait_for_incoming_msg_event()
assert receiver.get_message_by_id(event.msg_id).get_snapshot().file_bytes == 20 << 20
for ac in (ac1, ac2):
rpc.wait_for_all_work_done(ac.id)
grown = anonymous_mib(rpc.process.pid) - before
assert grown < 64, f"the server kept {grown} MiB after two 20 MiB attachments"
@@ -351,11 +351,12 @@ def test_qr_works_after_removing_primary_transport(acf, log) -> None:
log.section("Alice creates a QR code")
chat_qr = alice.get_qr_code()
chat_qr_unquoted = urllib.parse.unquote(chat_qr)
assert f"&a={third_addr}" in chat_qr_unquoted
assert f"&r={second_addr},{first_addr}" in chat_qr_unquoted
assert f"&a={first_addr}" in chat_qr_unquoted
assert f"&r={third_addr},{second_addr}" in chat_qr_unquoted
log.section("Alice removes the transport named by the a= parameter")
alice.delete_transport(third_addr)
log.section("Alice removes first and second transport")
alice.set_config("configured_addr", third_addr)
alice.delete_transport(first_addr)
alice.delete_transport(second_addr)
log.section("Bob scans the QR code, which still works")
@@ -35,19 +35,6 @@ def test_sleep(rpc) -> None:
assert sleep_5_future in pending
def test_shutdown(rpc) -> None:
"""Test RPC client shutdown if RPC server process is terminated.
This is a regression test, at the time of adding it RPC client
did not terminate all threads and python process did not exit.
Shutting down all threads is tested implicitly by pytest-timeout.
"""
rpc.process.kill()
rpc.process.wait()
with pytest.raises(JsonRpcError):
rpc.get_system_info()
def test_email_address_validity(rpc) -> None:
valid_addresses = [
"email@example.com",
+1 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "2.64.0-dev"
version = "2.63.0-dev"
description = "DeltaChat JSON-RPC server"
edition = "2024"
license = "MPL-2.0"
@@ -14,7 +14,6 @@ deltachat = { workspace = true }
anyhow = { workspace = true }
futures-lite = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true, features = ["derive"] }
@@ -15,5 +15,5 @@
},
"type": "module",
"types": "index.d.ts",
"version": "2.64.0-dev"
"version": "2.63.0-dev"
}
-24
View File
@@ -22,32 +22,8 @@ use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use yerpc::{RpcClient, RpcSession};
/// Pins Linux glibc's mmap threshold so that freed message buffers go back to the kernel.
///
/// See M_MMAP_THRESHOLD in <https://man7.org/linux/man-pages/man3/mallopt.3.html>:
/// glibc by default starts with a M_MMAP_THRESHOLD threshold of 128 KiB
/// but raises it to the size of every freed block that exceeds it,
/// up to 32 MiB on 64-bit systems,
/// and trims the heap only from its top end once twice that much is free.
/// Fixating the threshold disables the adjustment: allocations at or above it
/// that the free list cannot satisfy are mmapped and unmapped on free,
/// at the price of the kernel zeroing each such buffer after unmap.
/// Large message processing (allocations above 128KiB) very slightly slows
/// down to the kernel zeroing the buffers, but it's hardly measurable,
/// while overall process memory allocation behaviour significantly improves.
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn tune_malloc() {
unsafe {
libc::mallopt(libc::M_MMAP_THRESHOLD, 128 * 1024);
}
}
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
fn tune_malloc() {}
#[tokio::main(flavor = "multi_thread")]
async fn main() {
tune_malloc();
// Logs from `log` crate and traces from `tracing` crate
// are configurable with `RUST_LOG` environment variable
// and go to stderr to avoid interfering with JSON-RPC using stdout.
-3
View File
@@ -63,7 +63,6 @@ ignore = [
# Please keep this list alphabetically sorted.
skip = [
{ name = "async-channel", version = "1.9.0" },
{ name = "base64", version = "0.22.1" },
{ name = "bitflags", version = "1.3.2" },
{ name = "constant_time_eq", version = "0.3.1" },
{ name = "cpufeatures", version = "0.2.17" },
@@ -114,8 +113,6 @@ skip = [
{ name = "windows_x86_64_gnu" },
{ name = "windows_x86_64_gnullvm" },
{ name = "windows_x86_64_msvc" },
{ name = "zerocopy-derive", version = "0.7.35" },
{ name = "zerocopy", version = "0.7.35" },
]
-15
View File
@@ -456,21 +456,6 @@ CREATE TABLE smtp_status_updates (
descr TEXT NOT NULL -- text to send along with the updates
);
-- Table to record the successful usage transports for sending.
-- Sorting the table by rowid in descending order
-- returns most recently successfully used transport first.
CREATE TABLE smtp_success (
-- Sequentially increasing ID of the success.
-- Transport with the highest ID is to be used first.
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-- ID of the transport that was used to send a message.
transport_id INTEGER UNIQUE NOT NULL,
-- Delete `smtp_success` rows when the transport is deleted.
FOREIGN KEY(transport_id) REFERENCES transports(id) ON DELETE CASCADE
) STRICT;
-- Table of "sync items" to be grouped into sync messages
-- and sent to own devices.
CREATE TABLE multi_device_sync (
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "2.64.0-dev"
version = "2.63.0-dev"
license = "MPL-2.0"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"
+1 -1
View File
@@ -188,7 +188,7 @@ class Message:
def get_summarytext(self, width: int) -> str:
"""Get a message summary as a single line of text. Typically used for notifications."""
return from_dc_charpointer(lib.dc_msg_get_summary_text(self._dc_msg, 1, 1, width))
return from_dc_charpointer(lib.dc_msg_get_summarytext(self._dc_msg, width))
@props.with_doc
def time_sent(self):
+1 -1
View File
@@ -1 +1 @@
2026-10-09
2026-09-22
+1 -1
View File
@@ -6,4 +6,4 @@
#
# To automatically fix warnings, run
# scripts/clippy.sh --fix --allow-dirty
cargo clippy --locked --workspace --exclude deltachat-jsonrpc-bindings --all-targets --all-features "$@" -- -D warnings
cargo clippy --locked --workspace --all-targets --all-features "$@" -- -D warnings
+22 -3
View File
@@ -76,8 +76,12 @@ impl Accounts {
Accounts::open(events, dir, writable).await
}
fn log_info(&self, file: &str, line: u32, msg: String) {
self.emit_event(EventType::Info(format!("{file}:{line}: {msg}")));
/// Get the ID used to log events.
///
/// Account manager logs events with ID 0
/// which is not used by any accounts.
fn get_id(&self) -> u32 {
0
}
/// Ensures the accounts directory and config file exist.
@@ -391,6 +395,11 @@ impl Accounts {
"Starting background fetch for {n_accounts} accounts."
)),
});
::tracing::event!(
::tracing::Level::INFO,
account_id = 0,
"Starting background fetch for {n_accounts} accounts."
);
let mut set = JoinSet::new();
for account in accounts {
set.spawn(async move {
@@ -406,6 +415,11 @@ impl Accounts {
"Finished background fetch for {n_accounts} accounts."
)),
});
::tracing::event!(
::tracing::Level::INFO,
account_id = 0,
"Finished background fetch for {n_accounts} accounts."
);
}
/// Auxiliary function for [Accounts::background_fetch].
@@ -448,6 +462,11 @@ impl Accounts {
id: 0,
typ: EventType::Warning("Background fetch timed out.".to_string()),
});
::tracing::event!(
::tracing::Level::WARN,
account_id = 0,
"Background fetch timed out."
);
}
events.emit(Event {
id: 0,
@@ -530,7 +549,7 @@ impl Accounts {
}
}
/// Emits a single event with ID 0, which is not used by any accounts.
/// Emits a single event.
pub fn emit_event(&self, event: EventType) {
self.events.emit(Event { id: 0, typ: event })
}
+12 -21
View File
@@ -342,32 +342,23 @@ impl<'a> BlobObject<'a> {
// It's strange that BufReader modifies a file position while it takes a non-mut
// reference. Ok, just rewind it.
file.rewind()?;
let imgreader = ImageReader::new(std::io::BufReader::new(&file))
.with_guessed_format()
.context("Failed to guess image format due to I/O error")?;
let imgreader = ImageReader::new(std::io::BufReader::new(&file)).with_guessed_format();
let imgreader = match imgreader {
Ok(ir) => ir,
_ => {
file.rewind()?;
ImageReader::with_format(
std::io::BufReader::new(&file),
ImageFormat::from_path(self.to_abs_path())?,
)
}
};
let fmt = imgreader.format().context("Unknown format")?;
if *vt == Viewtype::File {
*vt = Viewtype::Image;
return Ok(name);
}
let mut img = match fmt {
image::ImageFormat::WebP => {
// `with_guessed_format()` restores file position,
// so `buf_reader` is at the beginning of the file.
let buf_reader = imgreader.into_inner();
let webp_decoder = image::codecs::webp::WebPDecoder::new(buf_reader)
.context("Failed to create WebP decoder")?;
// If WebP has animation, do not try to recode it.
// Recoding into JPEG will result in losing the animation.
if !is_avatar && webp_decoder.has_animation() {
return Ok(name);
}
DynamicImage::from_decoder(webp_decoder)?
}
_ => imgreader.decode().context("Failed to decode image")?,
};
let mut img = imgreader.decode().context("image decode failure")?;
let orientation = exif
.as_ref()
.map(|exif| exif_orientation(exif, context))
+1 -67
View File
@@ -4,9 +4,7 @@ use super::*;
use crate::message::{Message, Viewtype};
use crate::param::Param;
use crate::sql;
use crate::test_utils::{
self, AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, TestContext, TestContextManager,
};
use crate::test_utils::{self, AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, TestContext};
use crate::tools::SystemTime;
fn check_image_size(path: impl AsRef<Path>, width: u32, height: u32) -> image::DynamicImage {
@@ -740,70 +738,6 @@ async fn test_send_gif_as_sticker() -> Result<()> {
Ok(())
}
/// Tests that animated WebP is sent without reencoding.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_send_animated_webp_as_image() -> Result<()> {
let bytes = include_bytes!("../../test-data/image/animated.webp");
assert!(
bytes.len() > constants::WORSE_IMAGE_SIZE as usize,
"Animated WebP should be large enough to trigger reencoding"
);
let (width, height) = (900u32, 900u32);
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
alice
.set_config(
Config::MediaQuality,
Some(&(MediaQuality::Worse as i32).to_string()),
)
.await?;
let file = alice.get_blobdir().join("file").with_extension("webp");
fs::write(&file, &bytes)
.await
.context("Failed to write file")?;
let mut msg = Message::new(Viewtype::Image);
msg.set_file_and_deduplicate(alice, &file, Some("file.webp"), None)?;
let chat = alice.create_chat(bob).await;
let sent = alice.send_msg(chat.id, &mut msg).await;
let bob_msg = bob.recv_msg(&sent).await;
assert_eq!(bob_msg.get_viewtype(), Viewtype::Image);
assert_eq!(bob_msg.get_width() as u32, width);
assert_eq!(bob_msg.get_height() as u32, height);
assert_eq!(
bob_msg.get_filebytes(bob).await?.unwrap(),
bytes.len() as u64
);
Ok(())
}
/// Tests that if user sets animated WebP as an avatar, it may be recoded.
///
/// We don't want to recode animated WebPs into JPEG and lose animation,
/// but for avatars we don't want animation and transparency anyway.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_recode_animated_webp_avatar() -> Result<()> {
let mut tcm = TestContextManager::new();
let t = &tcm.alice().await;
let avatar_src = t.dir.path().join("avatar.webp");
let avatar_bytes = include_bytes!("../../test-data/image/animated.webp");
fs::write(&avatar_src, avatar_bytes).await.unwrap();
t.set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap()))
.await?;
let avatar_blob = t.get_config(Config::Selfavatar).await?.unwrap();
assert!(avatar_blob.ends_with(".jpg"));
let scaled_avatar_size = fs::metadata(&avatar_blob).await.unwrap().len();
assert!(
scaled_avatar_size < avatar_bytes.len() as u64,
"Animated WebP avatar must be recoded"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_create_and_deduplicate() -> Result<()> {
let t = TestContext::new().await;
+3 -1
View File
@@ -3,6 +3,7 @@
use std::cmp;
use std::collections::{BTreeSet, HashMap};
use std::fmt;
use std::io::Cursor;
use std::marker::Sync;
use std::path::{Path, PathBuf};
use std::time::Duration;
@@ -1917,7 +1918,8 @@ impl Chat {
let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
let html_part = MimePart::new("text/html", s);
let mut buffer = Vec::new();
html_part.write_part(&mut buffer);
let cursor = Cursor::new(&mut buffer);
html_part.write_part(cursor).ok();
String::from_utf8_lossy(&buffer).to_string()
});
let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
+21 -11
View File
@@ -1457,6 +1457,7 @@ impl Session {
fn drain_unsolicited_responses(&self, context: &Context) -> bool {
use UnsolicitedResponse::*;
use async_imap::imap_proto::Response;
use async_imap::imap_proto::ResponseCode;
let folder = self.selected_folder.as_deref().unwrap_or_default();
let mut should_refetch = false;
@@ -1471,19 +1472,28 @@ impl Session {
}
Expunge(_) | Recent(_) => {}
Other(ref response_data) => match response_data.parsed() {
Response::Fetch { .. } => {
info!(
context,
"Need to refetch {folder:?}, got unsolicited FETCH {response:?}"
);
should_refetch = true;
}
Other(ref response_data) => {
match response_data.parsed() {
Response::Fetch { .. } => {
info!(
context,
"Need to refetch {folder:?}, got unsolicited FETCH {response:?}"
);
should_refetch = true;
}
_ => {
info!(context, "{folder:?}: got unsolicited response {response:?}")
// We are not interested in the following responses and they are are
// sent quite frequently, so, we ignore them without logging them.
Response::Done {
code: Some(ResponseCode::CopyUid(_, _, _)),
..
} => {}
_ => {
info!(context, "{folder:?}: got unsolicited response {response:?}")
}
}
},
}
_ => {
info!(context, "{folder:?}: got unsolicited response {response:?}")
}
+30 -23
View File
@@ -3,7 +3,6 @@
#![allow(missing_docs)]
use crate::context::Context;
use crate::events::EventType;
mod stream;
@@ -13,9 +12,15 @@ macro_rules! info {
($ctx:expr, $msg:expr) => {
info!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
$ctx.log_info(file!(), line!(), format!($msg, $($args),*))
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let full = format!("{file}:{line}: {msg}",
file = file!(),
line = line!(),
msg = &formatted);
::tracing::event!(::tracing::Level::INFO, account_id = $ctx.get_id(), "{}", &formatted);
$ctx.emit_event($crate::EventType::Info(full));
}};
}
// Workaround for <https://github.com/rust-lang/rust/issues/133708>.
@@ -25,9 +30,15 @@ mod warn_macro_mod {
($ctx:expr, $msg:expr) => {
warn_macro!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
$ctx.log_warn(file!(), line!(), format!($msg, $($args),*))
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let full = format!("{file}:{line}: {msg}",
file = file!(),
line = line!(),
msg = &formatted);
::tracing::event!(::tracing::Level::WARN, account_id = $ctx.get_id(), "{}", &formatted);
$ctx.emit_event($crate::EventType::Warning(full));
}};
}
pub(crate) use warn_macro;
@@ -39,25 +50,15 @@ macro_rules! error {
($ctx:expr, $msg:expr) => {
error!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
$ctx.log_error(format!($msg, $($args),*))
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
::tracing::event!(::tracing::Level::ERROR, account_id = $ctx.get_id(), "{}", &formatted);
$ctx.set_last_error(&formatted);
$ctx.emit_event($crate::EventType::Error(formatted));
}};
}
impl Context {
pub(crate) fn log_info(&self, file: &str, line: u32, msg: String) {
self.emit_event(EventType::Info(format!("{file}:{line}: {msg}")));
}
pub(crate) fn log_warn(&self, file: &str, line: u32, msg: String) {
self.emit_event(EventType::Warning(format!("{file}:{line}: {msg}")));
}
pub(crate) fn log_error(&self, msg: String) {
self.set_last_error(&msg);
self.emit_event(EventType::Error(msg));
}
/// Set last error string.
/// Implemented as blocking as used from macros in different, not always async blocks.
pub fn set_last_error(&self, error: &str) {
@@ -115,6 +116,12 @@ impl<T, E: std::fmt::Display> LogExt<T, E> for Result<T, E> {
);
// We can't use the warn!() macro here as the file!() and line!() macros
// don't work with #[track_caller]
tracing::event!(
::tracing::Level::WARN,
account_id = context.get_id(),
"{}",
&full
);
context.emit_event(crate::EventType::Warning(full));
};
self
+5
View File
@@ -93,6 +93,11 @@ impl<S: SessionStream> AsyncRead for LoggingStream<S> {
"Read error on stream {peer_addr:?} after reading {} and writing {} bytes: {err}.",
this.metrics.total_read, this.metrics.total_written
);
tracing::event!(
::tracing::Level::WARN,
account_id = *this.account_id,
log_message
);
this.events.emit(Event {
id: *this.account_id,
typ: EventType::Warning(log_message),
+15 -8
View File
@@ -1,6 +1,7 @@
//! # MIME message production.
use std::collections::{BTreeSet, HashSet};
use std::io::Cursor;
use anyhow::{Context as _, Result, bail, format_err};
use base64::Engine as _;
@@ -244,20 +245,25 @@ pub(crate) fn render_queued_mail(
let is_encrypted = encryption.is_encrypted();
fn add_header(name: &[u8], value: &impl mail_builder::headers::Header, headers: &mut Vec<u8>) {
fn add_header(
name: &[u8],
value: &impl mail_builder::headers::Header,
headers: &mut Vec<u8>,
) -> Result<()> {
headers.extend(name);
value.write_header(headers, name.len());
value.write_header(headers, name.len())?;
Ok(())
}
let from_header = new_address_with_name(&display_name, from_addr.clone());
add_header(b"From: ", &from_header, &mut inner_headers);
add_header(b"From: ", &from_header, &mut inner_headers)?;
if is_encrypted {
let unencrypted_from = Address::new_address(None::<&'static str>, from_addr.clone());
add_header(b"From: ", &unencrypted_from, &mut outer_headers);
add_header(b"HP-Outer: From: ", &unencrypted_from, &mut inner_headers);
add_header(b"From: ", &unencrypted_from, &mut outer_headers)?;
add_header(b"HP-Outer: From: ", &unencrypted_from, &mut inner_headers)?;
} else {
add_header(b"From: ", &from_header, &mut outer_headers);
add_header(b"From: ", &from_header, &mut outer_headers)?;
}
if is_encrypted {
@@ -328,7 +334,7 @@ pub(crate) fn render_queued_mail(
} else {
&mut outer_headers
},
);
)?;
}
if is_encrypted {
@@ -2427,7 +2433,8 @@ pub(crate) async fn keyupdate_message(
/// Renders MIME part into a vector of bytes.
pub(crate) fn part_to_bytes(message: MimePart<'static>) -> Vec<u8> {
let mut raw_message = Vec::new();
message.write_part(&mut raw_message);
let cursor = Cursor::new(&mut raw_message);
message.write_part(cursor).ok();
raw_message
}
+38 -36
View File
@@ -5,7 +5,7 @@ use pgp::armor;
use pgp::packet::{Packet, PacketParser};
use pretty_assertions::assert_eq;
use regex::regex;
use std::io::{BufReader, Cursor};
use std::io::BufReader;
use std::str;
use std::time::Duration;
@@ -30,7 +30,8 @@ fn render_email_address(display_name: &str, addr: &str) -> String {
let mut output = Vec::<u8>::new();
new_address_with_name(display_name, addr.to_string())
.unwrap_address()
.write_header(&mut output, 0);
.write_header(&mut output, 0)
.unwrap();
String::from_utf8(output).unwrap()
}
@@ -49,7 +50,9 @@ fn test_render_email_address() {
let s = render_email_address(display_name, addr);
assert_eq!(s, "=?utf-8?B?w6Qgc3BhY2U=?= <x@y.org>\r\n");
println!("{s}");
assert_eq!(s, "=?utf-8?B?w6Qgc3BhY2U=?= <x@y.org>");
}
#[test]
@@ -67,14 +70,14 @@ fn test_render_email_address_noescape() {
let s = render_email_address(display_name, addr);
// Addresses should not be unnecessarily be encoded, see <https://github.com/deltachat/deltachat-core-rust/issues/1575>:
assert_eq!(s, "\"a space\" <x@y.org>\r\n");
assert_eq!(s, r#""a space" <x@y.org>"#);
}
#[test]
fn test_render_email_address_duplicated_as_name() {
let addr = "x@y.org";
let s = render_email_address(addr, addr);
assert_eq!(s, "<x@y.org>\r\n");
assert_eq!(s, "<x@y.org>");
}
#[test]
@@ -99,7 +102,8 @@ fn render_header_text(text: &str) -> String {
// Some non-zero length of the header name.
let bytes_written = 20;
mail_builder::headers::text::Text::new(text.to_string())
.write_header(&mut output, bytes_written);
.write_header(&mut output, bytes_written)
.unwrap();
String::from_utf8(output).unwrap()
}
@@ -951,7 +955,7 @@ async fn test_no_empty_to_header() -> Result<()> {
assert!(
// It would be equally fine if the payload contained `To: alice@example.org` or similar,
// as long as it's a valid header
payload.contains("To: \"hidden-recipients\":;"),
payload.contains("To: \"hidden-recipients\": ;"),
"Payload doesn't contain correct To: header: {payload}"
);
@@ -1060,8 +1064,8 @@ MIME-Version: 1.0
To: "hidden-recipients": ;
Subject: [...]
Chat-Version: 1.0
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
boundary="BOUNDARY"
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
boundary="BOUNDARY"
--BOUNDARY
@@ -1102,18 +1106,17 @@ async fn test_render_unencrypted_msg_basic() -> Result<()> {
let expected = r#"From: <alice@example.org>
Message-ID: <MESSAGE_ID@localhost>
MIME-Version: 1.0
Autocrypt: addr=alice@example.org; prefer-encrypt=mutual;
keydata=mDMEXlh13RYJKwYBBAHaRw8BAQdAzfVIAleCXMJrq8VeLlEVof6ITCviMktKjmcBKAu4m5
DCtAQfFggAZgUCXlh13RYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDAhsDAh4JBAsJCAcFFQgJCgsDFgIB
AycJAgIZASwUgAAAAAASABFyZWxheXNAY2hhdG1haWwuYXRhbGljZUBleGFtcGxlLm9yZwAAb1QA/0
HbvPN3/Vn02Gk1dcQMEcyGyETld9dSsRo8uwHAyW35AQCrFJjAQFLTud7XK61uYt9BC/QHipCfIGbq
X1FjMbTUC80TPGFsaWNlQGV4YW1wbGUub3JnPsKRBBMWCAA5BQJeWHXdFiEELm+iyyO1MtcoY0tYZL
CPYantlEMCGwMCHgkECwkIBwUVCAkKCwMWAgEDJwkCAhkBAAoJEGSwj2Gp7ZRD1m4A/iOifEzIOiP8
wW0O8I/sg69gQtG8Czn4MsVV6Ea1EyIqAP4uByHaUJdy8MSQPfv/Usr09KsidNgy2Jh37yg82fKUBr
g4BF5Ydd0SCisGAQQBl1UBBQEBB0AG7cjWy2SFAU8KnltlubVW67rFiyfp01JrRe6Xqy22HQMBCAeI
eAQYFggAIBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsMAAoJEGSwj2Gp7ZRDLo8BAObE8G
nsGVwKzNqCvHeWgJsqhjS3C6gvSlV3tEm9XmF6AQDXucIyVfoBwoyMh2h6cSn/ATn5QJb35pgo+ivp
3jsMAg==
Autocrypt: addr=alice@example.org; prefer-encrypt=mutual; keydata=mDMEXlh13RYJKwYBBAHaRw8BAQdAzfVIAleCXMJrq8VeLlEVof6ITCviMktKjmcBKAu4m5
DCtAQfFggAZgUCXlh13RYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDAhsDAh4JBAsJCAcFFQgJCgsDFgIB
AycJAgIZASwUgAAAAAASABFyZWxheXNAY2hhdG1haWwuYXRhbGljZUBleGFtcGxlLm9yZwAAb1QA/0
HbvPN3/Vn02Gk1dcQMEcyGyETld9dSsRo8uwHAyW35AQCrFJjAQFLTud7XK61uYt9BC/QHipCfIGbq
X1FjMbTUC80TPGFsaWNlQGV4YW1wbGUub3JnPsKRBBMWCAA5BQJeWHXdFiEELm+iyyO1MtcoY0tYZL
CPYantlEMCGwMCHgkECwkIBwUVCAkKCwMWAgEDJwkCAhkBAAoJEGSwj2Gp7ZRD1m4A/iOifEzIOiP8
wW0O8I/sg69gQtG8Czn4MsVV6Ea1EyIqAP4uByHaUJdy8MSQPfv/Usr09KsidNgy2Jh37yg82fKUBr
g4BF5Ydd0SCisGAQQBl1UBBQEBB0AG7cjWy2SFAU8KnltlubVW67rFiyfp01JrRe6Xqy22HQMBCAeI
eAQYFggAIBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsMAAoJEGSwj2Gp7ZRDLo8BAObE8G
nsGVwKzNqCvHeWgJsqhjS3C6gvSlV3tEm9XmF6AQDXucIyVfoBwoyMh2h6cSn/ATn5QJb35pgo+ivp
3jsMAg==
Content-Type: text/plain; charset="utf-8"
Date: DATE
To: <bob@example.net>
@@ -1123,7 +1126,7 @@ Chat-Version: 1.0
Content-Transfer-Encoding: 7bit
Hello!"#
.replace("\n", "\r\n");
.replace("\n", "\r\n");
assert_eq!(
unencrypted, expected,
"---------------- Actual: ----------------
@@ -1153,20 +1156,19 @@ async fn test_render_unencrypted_msg_with_attachment() -> Result<()> {
let expected = r#"From: <alice@example.org>
Message-ID: <MESSAGE_ID@localhost>
MIME-Version: 1.0
Autocrypt: addr=alice@example.org; prefer-encrypt=mutual;
keydata=mDMEXlh13RYJKwYBBAHaRw8BAQdAzfVIAleCXMJrq8VeLlEVof6ITCviMktKjmcBKAu4m5
DCtAQfFggAZgUCXlh13RYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDAhsDAh4JBAsJCAcFFQgJCgsDFgIB
AycJAgIZASwUgAAAAAASABFyZWxheXNAY2hhdG1haWwuYXRhbGljZUBleGFtcGxlLm9yZwAAb1QA/0
HbvPN3/Vn02Gk1dcQMEcyGyETld9dSsRo8uwHAyW35AQCrFJjAQFLTud7XK61uYt9BC/QHipCfIGbq
X1FjMbTUC80TPGFsaWNlQGV4YW1wbGUub3JnPsKRBBMWCAA5BQJeWHXdFiEELm+iyyO1MtcoY0tYZL
CPYantlEMCGwMCHgkECwkIBwUVCAkKCwMWAgEDJwkCAhkBAAoJEGSwj2Gp7ZRD1m4A/iOifEzIOiP8
wW0O8I/sg69gQtG8Czn4MsVV6Ea1EyIqAP4uByHaUJdy8MSQPfv/Usr09KsidNgy2Jh37yg82fKUBr
g4BF5Ydd0SCisGAQQBl1UBBQEBB0AG7cjWy2SFAU8KnltlubVW67rFiyfp01JrRe6Xqy22HQMBCAeI
eAQYFggAIBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsMAAoJEGSwj2Gp7ZRDLo8BAObE8G
nsGVwKzNqCvHeWgJsqhjS3C6gvSlV3tEm9XmF6AQDXucIyVfoBwoyMh2h6cSn/ATn5QJb35pgo+ivp
3jsMAg==
Content-Type: multipart/mixed;
boundary="BOUNDARY"
Autocrypt: addr=alice@example.org; prefer-encrypt=mutual; keydata=mDMEXlh13RYJKwYBBAHaRw8BAQdAzfVIAleCXMJrq8VeLlEVof6ITCviMktKjmcBKAu4m5
DCtAQfFggAZgUCXlh13RYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDAhsDAh4JBAsJCAcFFQgJCgsDFgIB
AycJAgIZASwUgAAAAAASABFyZWxheXNAY2hhdG1haWwuYXRhbGljZUBleGFtcGxlLm9yZwAAb1QA/0
HbvPN3/Vn02Gk1dcQMEcyGyETld9dSsRo8uwHAyW35AQCrFJjAQFLTud7XK61uYt9BC/QHipCfIGbq
X1FjMbTUC80TPGFsaWNlQGV4YW1wbGUub3JnPsKRBBMWCAA5BQJeWHXdFiEELm+iyyO1MtcoY0tYZL
CPYantlEMCGwMCHgkECwkIBwUVCAkKCwMWAgEDJwkCAhkBAAoJEGSwj2Gp7ZRD1m4A/iOifEzIOiP8
wW0O8I/sg69gQtG8Czn4MsVV6Ea1EyIqAP4uByHaUJdy8MSQPfv/Usr09KsidNgy2Jh37yg82fKUBr
g4BF5Ydd0SCisGAQQBl1UBBQEBB0AG7cjWy2SFAU8KnltlubVW67rFiyfp01JrRe6Xqy22HQMBCAeI
eAQYFggAIBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsMAAoJEGSwj2Gp7ZRDLo8BAObE8G
nsGVwKzNqCvHeWgJsqhjS3C6gvSlV3tEm9XmF6AQDXucIyVfoBwoyMh2h6cSn/ATn5QJb35pgo+ivp
3jsMAg==
Content-Type: multipart/mixed;
boundary="BOUNDARY"
Date: DATE
To: <bob@example.net>
Subject: Message from alice@example.org
+2 -4
View File
@@ -410,8 +410,7 @@ impl ProxyConfig {
};
let tcp_stream =
crate::net::connect_tcp(context, &hostname, http_config.port, load_cache)
.await
.context("Failed to connect to HTTP proxy")?;
.await?;
let auth = if let Some((username, password)) = &http_config.user_password {
Some((username.as_str(), password.as_str()))
} else {
@@ -430,8 +429,7 @@ impl ProxyConfig {
let tcp_stream =
crate::net::connect_tcp(context, &hostname, https_config.port, load_cache)
.await
.context("Failed to connect to HTTPS proxy")?;
.await?;
let use_sni = true;
let tls_stream = wrap_rustls(
&hostname,
+1
View File
@@ -1032,6 +1032,7 @@ Content-Disposition: reaction\n\
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
-1
View File
@@ -2957,7 +2957,6 @@ async fn test_weird_and_duplicated_filenames() -> Result<()> {
"a.tar.gz",
"a.a..a.a.a.a.tar.gz",
"a. tar.tar.gz",
"very long filename with emoji \u{1F600} very long file name with emoji \u{1FAE0} very long file name with emoji.tar.gz",
] {
let attachment = alice.blobdir.join(filename_sent);
let content = "File content of tar.gz archive".to_string();
+10 -6
View File
@@ -119,12 +119,16 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
let fingerprint = self_fingerprint(context).await?;
let self_addrs = context.get_self_addrs().await?;
let mut encoded_addrs = self_addrs
.iter()
.map(|addr| utf8_percent_encode(addr, DISALLOWED_CHARACTERS).to_string());
let self_addr_urlencoded = encoded_addrs.next().context("No self addr configured")?;
let encoded_extra_relays: Vec<String> = encoded_addrs.collect();
let self_addr = context.get_primary_self_addr().await?;
let self_addr_urlencoded = utf8_percent_encode(&self_addr, DISALLOWED_CHARACTERS).to_string();
let encoded_extra_relays: Vec<String> = context
.get_self_addrs()
.await?
.into_iter()
.filter(|addr| *addr != self_addr)
.map(|addr| utf8_percent_encode(&addr, DISALLOWED_CHARACTERS).to_string())
.collect();
let r_param = if encoded_extra_relays.is_empty() {
"".to_string()
+4 -4
View File
@@ -1160,12 +1160,12 @@ async fn test_get_securejoin_qr_encoding() -> Result<()> {
let qr = get_securejoin_qr(alice, None).await?;
assert!(
qr.contains("a=jk%25l@example.net"),
"{qr} doesn't contain 'a=jk%25l@example.net'"
qr.contains("a=alice@example.org"),
"{qr} doesn't contain 'a=alice@example.org'"
);
assert!(
qr.contains("r=asdf@example.org,alice@example.org"),
"{qr} doesn't contain 'r=asdf@example.org,alice@example.org'"
qr.contains("r=jk%25l@example.net,asdf@example.org"),
"{qr} doesn't contain 'r=jk%25l@example.net,asdf@example.org'"
);
let qr = check_qr(bob, &qr).await?;
+7 -51
View File
@@ -54,39 +54,6 @@ pub(crate) struct Smtp {
pub(crate) last_send_error: Option<String>,
}
/// Returns transports with their IDs in the order in which they should be tried.
async fn sorted_transports(context: &Context) -> Result<Vec<(u32, ConfiguredLoginParam)>> {
context
.sql
.query_map_vec(
"SELECT transports.id, configured_param FROM transports
LEFT JOIN smtp_success ON smtp_success.transport_id=transports.id
ORDER BY IFNULL(smtp_success.id, 0) DESC, transports.id ASC",
(),
|row| {
let id: u32 = row.get(0)?;
let json: String = row.get(1)?;
let param = ConfiguredLoginParam::from_json(&json)?;
Ok((id, param))
},
)
.await
}
/// Records successful use of SMTP transport so it is tried first next time we connect to SMTP.
async fn record_success(context: &Context, transport_id: u32) -> Result<()> {
// INSERT OR REPLACE essentially replaces rowid of the row
// if the row exists already, so it becomes the highest rowid in the table.
context
.sql
.execute(
"INSERT OR REPLACE INTO smtp_success (transport_id) VALUES (?)",
(transport_id,),
)
.await?;
Ok(())
}
impl Smtp {
/// Create a new Smtp instances.
pub fn new() -> Self {
@@ -101,8 +68,6 @@ impl Smtp {
// separate task to avoid waiting for reply or timeout.
task::spawn(async move { transport.quit().await });
}
self.transport_id = None;
self.from = None;
self.last_success = None;
}
@@ -134,7 +99,13 @@ impl Smtp {
self.connectivity.set_connecting(context);
let proxy_config = ProxyConfig::load(context).await?;
for (transport_id, lp) in sorted_transports(context).await? {
let transports = ConfiguredLoginParam::load_all(context).await?;
// Try to connect to the newest transport first. If sending is unreliable,
// user can configure a new transport and it will be the one used.
// Conversely, if user just added a new transport and sending got less reliable,
// user can restore old state by removing the just added transport.
for (transport_id, lp) in transports.into_iter().rev() {
info!(context, "Trying to connect to transport {transport_id}.");
match self
.connect(
@@ -354,18 +325,6 @@ pub(crate) async fn smtp_send(
Ok(()) => SendResult::Success,
};
if matches!(status, SendResult::Success) {
debug_assert!(smtp.transport_id.is_some());
if let Some(transport_id) = smtp.transport_id
&& let Err(err) = record_success(context, transport_id).await
{
warn!(
context,
"Failed to record successful use of transport {transport_id} in smtp_success table: {err:#}."
);
}
}
if let SendResult::Failure(err) = &status
&& let Some(msg_id) = msg_id
{
@@ -897,6 +856,3 @@ pub(crate) async fn add_self_recipients(
Ok(())
}
#[cfg(test)]
mod smtp_tests;
-49
View File
@@ -1,49 +0,0 @@
use anyhow::Result;
use crate::test_utils::TestContextManager;
use crate::transport;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_smtp_candidates() -> Result<()> {
let mut tcm = TestContextManager::new();
let t = &tcm.unconfigured().await;
transport::add_pseudo_transport(t, "foo@example.net").await?;
transport::add_pseudo_transport(t, "bar@example.net").await?;
transport::add_pseudo_transport(t, "baz@example.net").await?;
let transports = super::sorted_transports(t).await?;
let [
(transport_id1, ref transport1),
(transport_id2, ref transport2),
(transport_id3, ref transport3),
] = transports[..]
else {
panic!("Unexpected number of transports");
};
// By default first added transport is used first.
assert_eq!(transport1.addr, "foo@example.net");
assert_eq!(transport2.addr, "bar@example.net");
assert_eq!(transport3.addr, "baz@example.net");
super::record_success(t, transport_id3).await?;
let transports2 = super::sorted_transports(t).await?;
assert_eq!(transports2[0].0, transport_id3);
assert_eq!(transports2[1].0, transport_id1);
assert_eq!(transports2[2].0, transport_id2);
super::record_success(t, transport_id2).await?;
let transports3 = super::sorted_transports(t).await?;
assert_eq!(transports3[0].0, transport_id2);
assert_eq!(transports3[1].0, transport_id3);
assert_eq!(transports3[2].0, transport_id1);
super::record_success(t, transport_id3).await?;
let transports4 = super::sorted_transports(t).await?;
assert_eq!(transports4[0].0, transport_id3);
assert_eq!(transports4[1].0, transport_id2);
assert_eq!(transports4[2].0, transport_id1);
Ok(())
}
-15
View File
@@ -2672,21 +2672,6 @@ CREATE TABLE smtp2 (
.await?;
}
inc_and_check(&mut migration_version, 168)?;
if dbversion < migration_version {
sql.execute_migration(
"
CREATE TABLE smtp_success (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
transport_id INTEGER UNIQUE NOT NULL,
FOREIGN KEY(transport_id) REFERENCES transports(id) ON DELETE CASCADE
) STRICT;
",
migration_version,
)
.await?;
}
let new_version = sql
.get_raw_config_int(VERSION_CFG)
.await?
+35 -98
View File
@@ -10,7 +10,6 @@ use num_traits::FromPrimitive;
use crate::calls::{CallState, call_state};
use crate::chat::Chat;
use crate::constants::Chattype;
use crate::constants::DC_DESIRED_TEXT_LEN;
use crate::contact::{Contact, ContactId};
use crate::context::Context;
use crate::message::{Message, MessageState, Viewtype};
@@ -57,6 +56,9 @@ pub struct Summary {
/// Message state.
pub state: MessageState,
/// Message preview image path
pub thumbnail_path: Option<String>,
}
impl Summary {
@@ -81,6 +83,7 @@ impl Summary {
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,
});
}
Self::new(context, msg, chat, contact).await
@@ -118,11 +121,30 @@ impl Summary {
None
};
let mut text = msg.get_summary_text(context).await;
if text.is_empty() && msg.quoted_text().is_some() {
text = stock_str::reply_noun(context)
}
let thumbnail_path = if msg.viewtype == Viewtype::Image
|| msg.viewtype == Viewtype::Gif
|| msg.viewtype == Viewtype::Sticker
{
msg.get_file(context)
.and_then(|path| path.to_str().map(|p| p.to_owned()))
} else if msg.viewtype == Viewtype::Webxdc {
Some("webxdc-icon://last-msg-id".to_string())
} else {
None
};
Ok(Summary {
prefix,
text: msg.get_summary_text(context).await,
text,
timestamp: msg.get_timestamp(),
state: msg.state,
thumbnail_path,
})
}
@@ -133,32 +155,19 @@ impl Summary {
}
impl Message {
/// Returns a summary text with emoji and "Forwarded:" prefixes.
/// This is the standard summary to be used in chatlists, notifications etc.
/// Returns a summary text.
pub(crate) async fn get_summary_text(&self, context: &Context) -> String {
let add_forwarded = true;
let add_type_emoji = true;
self.get_summary_text_ext(context, add_forwarded, add_type_emoji, DC_DESIRED_TEXT_LEN)
.await
let summary = self.get_summary_text_without_prefix(context).await;
if self.is_forwarded() {
format!("{}: {}", stock_str::forwarded(context), summary)
} else {
summary
}
}
/// Returns a summary text with emoji prefixes but without "Forwarded:" prefix.
/// Used for shorter reaction summaries as "USER reacts 👋 to SUMMARY"
/// Returns a summary text without "Forwarded:" prefix.
async fn get_summary_text_without_prefix(&self, context: &Context) -> String {
let add_forwarded = false;
let add_type_emoji = true;
self.get_summary_text_ext(context, add_forwarded, add_type_emoji, DC_DESIRED_TEXT_LEN)
.await
}
/// Returns a summary text with optional "Forwarded:" and emoji prefixes and optionally converting to one line.
pub async fn get_summary_text_ext(
&self,
context: &Context,
add_forwarded: bool,
add_type_emoji: bool,
approx_chars: usize,
) -> String {
let (emoji, type_name, type_file, append_text);
let viewtype = match self
.param
@@ -272,11 +281,7 @@ impl Message {
}
};
let text = if approx_chars > 0 {
truncate(self.text.trim(), approx_chars).to_string()
} else {
self.text.trim().to_string()
};
let text = self.text.clone();
let summary = if let Some(type_file) = type_file {
if append_text && !text.is_empty() {
@@ -298,30 +303,13 @@ impl Message {
"".to_string()
};
let emoji = emoji.filter(|_| add_type_emoji);
let summary = if let Some(emoji) = emoji {
format!("{emoji} {summary}")
} else {
summary
};
let summary = if summary.is_empty() && self.quoted_text().is_some() {
stock_str::reply_noun(context)
} else {
summary
};
let summary = if add_forwarded && self.is_forwarded() {
format!("{}: {}", stock_str::forwarded(context), summary)
} else {
summary
};
if approx_chars > 0 {
summary.split_whitespace().collect::<Vec<&str>>().join(" ")
} else {
summary
}
summary.split_whitespace().collect::<Vec<&str>>().join(" ")
}
}
@@ -343,13 +331,6 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_summary_text() {
let forwarded = true;
let emoji = true;
let one_line = 2000;
let no_forwarded = false;
let no_emoji = false;
let multi_line = 0;
let d = TestContext::new_alice().await;
let ctx = &d.ctx;
let chat_id = ChatId::create_for_contact(ctx, ContactId::SELF)
@@ -366,22 +347,12 @@ mod tests {
let msg = Message::new_text(some_text.to_string());
assert_summary_texts(&msg, ctx, "bla bla").await; // for simple text, the type is not added to the summary
assert_eq!(
msg.get_summary_text_ext(ctx, forwarded, emoji, multi_line)
.await,
"bla \t\n\tbla" // lineends are preserved, but text is still trimmed
);
let file = write_file_to_blobdir(&d).await;
let mut msg = Message::new(Viewtype::Image);
msg.set_file_and_deduplicate(&d, &file, Some("foo.jpg"), None)
.unwrap();
assert_summary_texts(&msg, ctx, "📷 Image").await; // file names are not added for images
assert_eq!(
msg.get_summary_text_ext(ctx, no_forwarded, no_emoji, one_line)
.await,
"Image"
);
let file = write_file_to_blobdir(&d).await;
let mut msg = Message::new(Viewtype::Image);
@@ -389,16 +360,6 @@ mod tests {
msg.set_file_and_deduplicate(&d, &file, Some("foo.jpg"), None)
.unwrap();
assert_summary_texts(&msg, ctx, "📷 bla bla").await; // type is visible by emoji if text is set
assert_eq!(
msg.get_summary_text_ext(ctx, no_forwarded, no_emoji, one_line)
.await,
"bla bla"
);
assert_eq!(
msg.get_summary_text_ext(ctx, forwarded, emoji, multi_line)
.await,
"📷 bla \t\n\tbla"
);
let file = write_file_to_blobdir(&d).await;
let mut msg = Message::new(Viewtype::Video);
@@ -529,30 +490,6 @@ mod tests {
msg.get_summary_text_without_prefix(ctx).await,
"📎 foo.bar \u{2013} bla bla"
); // skipping prefix used for reactions summaries
assert_eq!(
msg.get_summary_text_ext(ctx, no_forwarded, no_emoji, one_line)
.await,
"foo.bar \u{2013} bla bla"
);
assert_eq!(
msg.get_summary_text_ext(ctx, forwarded, no_emoji, one_line)
.await,
"Forwarded: foo.bar \u{2013} bla bla"
);
d.assert_warn("Not a valid DeltaChat vCard").await;
// If nothing else is present, but the message is a reply, we say so. needed for summary of draft
let mut msg = Message::new_text("".to_string());
msg.set_quote_text(Some(("blubb".to_string(), true)));
assert_summary_texts(&msg, ctx, "Reply").await;
assert_eq!(
msg.get_summary_text_ext(ctx, no_forwarded, no_emoji, multi_line)
.await,
"Reply"
);
// If there is nothing, the summary is empty
let msg = Message::new_text("".to_string());
assert_summary_texts(&msg, ctx, "").await;
}
}
+1 -4
View File
@@ -105,10 +105,7 @@ async fn check_aeap_transition(chat_for_transition: ChatForTransition) {
check_that_transition_worked(bob, &groups, alice_contact, ALICE_NEW_ADDR).await;
tcm.section("Test switching back");
alice
.set_primary_self_addr("alice@example.org")
.await
.unwrap();
tcm.change_addr(alice, "alice@example.org").await;
let sent = alice
.send_text(chat_to_send, "Hello from my old addr!")
.await;
+2 -2
View File
@@ -740,14 +740,14 @@ pub(crate) fn maybe_update_sending_transport(
pub async fn add_pseudo_transport(context: &Context, addr: &str) -> Result<()> {
context.sql
.execute(
"INSERT INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
"INSERT OR IGNORE INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
(
addr,
serde_json::to_string(&EnteredLoginParam{addr: addr.to_string(), ..Default::default()})?,
format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
),
)
.await.with_context(|| format!("Failed to insert pseudo transport for {addr:?}"))?;
.await?;
Ok(())
}
+1
View File
@@ -1664,6 +1664,7 @@ async fn test_webxdc_chatlist_summary() -> Result<()> {
assert_eq!(chatlist.len(), 1);
let summary = chatlist.get_summary(&t, 0, None).await?;
assert_eq!(summary.text, "📱 nice app!".to_string());
assert_eq!(summary.thumbnail_path.unwrap(), "webxdc-icon://last-msg-id");
Ok(())
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 KiB