Compare commits

..
Author SHA1 Message Date
link2xt 5820c4ce95 Serialize mime_compressed 2024-04-06 16:37:51 +00:00
link2xt 12ba33d9d4 Serialize uid 2024-04-06 15:54:12 +00:00
link2xt 60a7bbc9b5 Do not serialize is_default
It is only stored for compatibility with old versions
2024-04-06 15:27:11 +00:00
link2xt e9f434b562 Serialize backward_verified_key_id 2024-04-06 02:48:27 +00:00
link2xt 2423cb8175 feat: add support for dumping the database to stream 2024-04-06 01:45:14 +00:00
link2xt 65c9e72bf4 test: test withdrawing group join QR codes 2024-04-05 22:34:02 +00:00
link2xt ea4d954c77 fix: do not emit MSGS_CHANGED event for outgoing hidden messages
This includes synchronization messages.
2024-04-05 22:34:02 +00:00
link2xt 43523a96a2 api(deltachat-rpc-client): add check_qr and set_config_from_qr APIs 2024-04-05 22:34:02 +00:00
link2xt 2e2fa9e74f chore: update lockfile in /fuzz 2024-04-05 19:44:51 +00:00
B. Petersen e43ffb20a1 chore(release): prepare for 1.137.2 2024-04-05 14:21:15 +00:00
link2xt 2f0f247e70 refactor: use Rust 1.77.0 support for recursion in async functions 2024-04-04 17:01:15 +00:00
Simon Laux 5bda4f0c26 update node constants for #5387 (#5429) 2024-04-04 15:31:02 +02:00
iequidoo d39c8a3a19 refactor: is_probably_private_reply: Remove reaction-specific code
Instead, look up the 1:1 chat in `receive_imf::add_parts()`. This is a more generic approach to fix
assigning outgoing reactions to 1:1 chats in the multi-device setup. Although currently both
approaches give the same result, this way we can even implement a "react privately"
functionality. Maybe it sounds useless, but it seems better to have less reaction-specific code.
2024-04-03 21:29:27 -03:00
link2xt e465415039 fix: do not ignore Message::load_from_db errors 2024-04-04 01:44:50 +02:00
B. Petersen 5cef77b8e6 fix: do not show empty summary if message reacted to is deleted
we checked for tombstones already using `is_trash()`,
however, we've overseen that tombstones get deleted at some point :)

therefore, just do not treat loading failures of the weak msg_id as errors -
usually, they are not - and if, just the normal summary is shown.
in theory, we could check for existance explicitly before tryong load_from_db,
however, that would be additional code (and maybe another database call)
and not worth the effort.

anyways, this commit also adds an explicit test
for physical deletion after housekeeping.
2024-04-04 01:44:50 +02:00
dependabot[bot] 60e733c30c chore(cargo): bump fast-socks5 from 0.9.5 to 0.9.6
Bumps [fast-socks5](https://github.com/dizda/fast-socks5) from 0.9.5 to 0.9.6.
- [Release notes](https://github.com/dizda/fast-socks5/releases)
- [Commits](https://github.com/dizda/fast-socks5/commits/v0.9.6)

---
updated-dependencies:
- dependency-name: fast-socks5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 21:51:57 +00:00
dependabot[bot] 8b98816eb9 Merge pull request #5404 from deltachat/dependabot/cargo/rustyline-14.0.0 2024-04-03 21:19:38 +00:00
link2xt 50165b3e35 chore: upgrade image from 0.24.9 to 0.25.1 2024-04-03 20:59:06 +00:00
link2xt 0be8b5a5c4 chore: upgrade h2 from 0.4.3 to 0.4.4
This upgrade contains a fix for RUSTSEC-2024-0332
2024-04-03 20:34:14 +00:00
B. Petersen 451bb6e9db add tests for get_summary_text_without_prefix() 2024-04-03 20:55:22 +02:00
B. Petersen 83196d4cb5 add get_summary_text_without_prefix()
use get_summary_text_without_prefix() to get raw summaries without prefixes,
this is needed for reaction summaries,
where we also do not show the name, so we do not want to show "Forwarded" as well.
2024-04-03 20:55:22 +02:00
B. Petersen 0003e55ad5 test reactions for forwarded messages 2024-04-03 20:55:22 +02:00
link2xt 02014eda6c chore: update from brotli 3.4.0 to brotli 4.0.0 2024-04-03 17:25:28 +00:00
link2xt f1c6cd69e9 chore: update chrono from 0.4.34 to 0.4.37 2024-04-03 16:49:19 +00:00
bjoern ace281ff6c feat: show reactions in summaries (#5387)
shows the last reaction in chatlist's summaries if there is no
newer message.

the reason to show reactions in the summary, is to make them a _little_
more visible when one is not in the chat. esp. in not-so-chatty or in
one-to-ones chats this becomes handy: imaging a question and someone
"answers" with "thumbs up" ... 

otoh, reactions are still tuned down on purpose: no notifications, chats
are opend as usual, the chatlist is not sorted by reactions and also the
date in the summary refer to the last message - i thought quite a bit
about that, this seems to be good compromise and will raise the fewest
questions. it is somehow clear to the users that reactions are not the
same as a real message. also, it is comparable easy to implement - no
UI changes required :)

all that is very close to what whatsapp is doing (figured that out by
quite some testing ... to cite @adbenitez: if in doubt, we can blame
whatsapp :)

technically, i first wanted to go for the "big solution" and add two
more columns, chat_id and timestamp, however, it seemed a bit bloated if
we really only need the last one. therefore, i just added the last
reaction information to the chat's param, which seems more performant
but also easier to code :)
2024-04-03 08:50:05 +00:00
dependabot[bot] c9edd525e0 chore(cargo): bump rustyline from 13.0.0 to 14.0.0
Bumps [rustyline](https://github.com/kkawakam/rustyline) from 13.0.0 to 14.0.0.
- [Release notes](https://github.com/kkawakam/rustyline/releases)
- [Changelog](https://github.com/kkawakam/rustyline/blob/master/History.md)
- [Commits](https://github.com/kkawakam/rustyline/compare/v13.0.0...v14.0.0)

---
updated-dependencies:
- dependency-name: rustyline
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 06:12:01 +00:00
link2xt 3f35b442c3 chore(release): prepare for 1.137.1 2024-04-03 01:28:13 +00:00
link2xt 87e9365016 ci: remove android builds for x86 and x86_64
They are failing to build.
2024-04-03 01:17:21 +00:00
link2xt 9806509f4a chore(release): prepare for 1.137.0 2024-04-02 21:22:58 +00:00
link2xt 91600a34b6 chore(cargo): update aho-corasick from 1.1.2 to 1.1.3 2024-04-02 21:11:04 +00:00
dependabot[bot] d16351d207 chore(cargo): bump regex from 1.10.3 to 1.10.4
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.3 to 1.10.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.3...1.10.4)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 21:03:12 +00:00
dependabot[bot] 4caf638201 chore(cargo): bump backtrace from 0.3.69 to 0.3.71
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.69 to 0.3.71.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.69...0.3.71)

---
updated-dependencies:
- dependency-name: backtrace
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 20:59:21 +00:00
B. Petersen 375fcbd63c reactions are not a 'probably private reply'
`is_probably_private_reply()` checks
if a message should better go to the one to one chat
instead of the chat already identified by the `In-Reply-To`.
this functionality is needed to make "Reply Privately" work.

however, this functionality is never true for reactions.
if we would return `true` here, own reactions seen by a second device
would not get the correct chat assiged.
2024-04-02 22:33:36 +02:00
B. Petersen 6ff3a2cf7c add failing test for #5418 (wrong DC_EVENT_REACTIONS_CHANGED) 2024-04-02 22:33:36 +02:00
link2xt a890fe3a9a chore(cargo): bump reqwest from 0.11.24 to 0.12.2
This also removes duplicate dependencies
for `http-body`, `http` and `hyper`.
2024-04-02 18:58:13 +00:00
link2xt 2b8bf29fce api(deltachat-rpc-client): add futures
futures allow to call multiple methods in parallel
without threads.

This introduces RpcFuture class and futuremethod decorator.
2024-04-02 16:54:25 +00:00
link2xt 26400a9e4e chore: update deny.toml 2024-04-02 16:39:45 +00:00
dependabot[bot] f8b9bb9083 chore(cargo): bump axum from 0.7.4 to 0.7.5
Bumps [axum](https://github.com/tokio-rs/axum) from 0.7.4 to 0.7.5.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.7.4...axum-v0.7.5)

---
updated-dependencies:
- dependency-name: axum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 16:35:51 +00:00
dependabot[bot] 42f9047a54 chore(cargo): bump futures-lite from 2.2.0 to 2.3.0
Bumps [futures-lite](https://github.com/smol-rs/futures-lite) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/smol-rs/futures-lite/releases)
- [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/futures-lite/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: futures-lite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 16:33:31 +00:00
dependabot[bot] 6433a3a5f3 chore(cargo): bump syn from 2.0.52 to 2.0.57
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.52 to 2.0.57.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.52...2.0.57)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 16:32:58 +00:00
dependabot[bot] 4b6a03c904 chore(cargo): bump anyhow from 1.0.80 to 1.0.81
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.80 to 1.0.81.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.80...1.0.81)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 16:31:50 +00:00
dependabot[bot] ff3df01d98 chore(cargo): bump serde_json from 1.0.114 to 1.0.115
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.114 to 1.0.115.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.114...v1.0.115)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 16:30:56 +00:00
dependabot[bot] cdc99854b2 chore(cargo): bump strum_macros from 0.26.1 to 0.26.2
Bumps [strum_macros](https://github.com/Peternator7/strum) from 0.26.1 to 0.26.2.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/compare/v0.26.1...v0.26.2)

---
updated-dependencies:
- dependency-name: strum_macros
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 16:29:31 +00:00
dependabot[bot] e7072bcb75 chore(cargo): bump async-smtp from 0.9.0 to 0.9.1
Bumps [async-smtp](https://github.com/async-email/async-smtp) from 0.9.0 to 0.9.1.
- [Commits](https://github.com/async-email/async-smtp/compare/v0.9.0...v0.9.1)

---
updated-dependencies:
- dependency-name: async-smtp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 14:46:20 +00:00
dependabot[bot] 7950bde3c6 chore(cargo): bump smallvec from 1.13.1 to 1.13.2
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.13.1 to 1.13.2.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.13.1...v1.13.2)

---
updated-dependencies:
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 13:18:17 +00:00
dependabot[bot] a259669c98 chore(cargo): bump tokio-stream from 0.1.14 to 0.1.15
Bumps [tokio-stream](https://github.com/tokio-rs/tokio) from 0.1.14 to 0.1.15.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-stream-0.1.14...tokio-stream-0.1.15)

---
updated-dependencies:
- dependency-name: tokio-stream
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 13:11:11 +00:00
dependabot[bot] 603e6be9b4 chore(cargo): bump toml from 0.8.10 to 0.8.12
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.10 to 0.8.12.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.10...toml-v0.8.12)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 13:00:15 +00:00
dependabot[bot] a78c484467 chore(cargo): bump uuid from 1.7.0 to 1.8.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.7.0...1.8.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 12:58:52 +00:00
dependabot[bot] e78f07b343 chore(cargo): bump strum from 0.26.1 to 0.26.2
Bumps [strum](https://github.com/Peternator7/strum) from 0.26.1 to 0.26.2.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/compare/v0.26.1...v0.26.2)

---
updated-dependencies:
- dependency-name: strum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 12:55:39 +00:00
dependabot[bot] 8abf10aacb chore(cargo): bump pin-project from 1.1.4 to 1.1.5
Bumps [pin-project](https://github.com/taiki-e/pin-project) from 1.1.4 to 1.1.5.
- [Release notes](https://github.com/taiki-e/pin-project/releases)
- [Changelog](https://github.com/taiki-e/pin-project/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project/compare/v1.1.4...v1.1.5)

---
updated-dependencies:
- dependency-name: pin-project
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 12:53:42 +00:00
dependabot[bot] 2fef4acdd6 chore(cargo): bump tokio from 1.36.0 to 1.37.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.36.0 to 1.37.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.36.0...tokio-1.37.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 12:49:13 +00:00
Sebastian KlähnandSeptias de27be3a36 build: add development shell (#5390)
Add nix development shell to flake.nix.

---------

Co-authored-by: Septias <scoreplayer2000@gmail.comclear>
2024-04-02 11:20:34 +02:00
dependabot[bot] c62e8539a1 chore(cargo): bump thiserror from 1.0.57 to 1.0.58
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.57 to 1.0.58.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.57...1.0.58)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 04:49:18 +00:00
link2xt 22c0aef9c0 build(python): remove setuptools_scm dependency
We update version in several .toml and .json files
on every release anyway, so updating pyproject.toml is easy.

setuptools_scm makes it more difficult to build
python packages for software distributions
because it requires full git checkout
with tags rather than just a worktree.
It is also possible to remove or move tags
after the release, so git revision no longer
pins python package version if setuptools_scm is used.
2024-04-02 04:43:39 +00:00
link2xt 87805bc36d build: add repository to Cargo.toml 2024-03-31 22:40:01 +00:00
B. Petersen 99c4d24eab cleanup jobs and Params relicts
- the `jobs` table is no longer in use,
  no need to track files on housekeeping,
  no need to clear it from repl tool

- some `Params` were used for jobs table only,
  they can be used freely for other purposes on other tables.
  param 'protection settings timestamp' was never used in practise,
  its code is removed as well, so we can free the Param as well.
2024-03-30 08:10:57 +01:00
bjoern 7bf9c4a2d9 api: remove unused dc_accounts_all_work_done() (#5384)
it was used by iOS to know when a background fetch was complete;
meanwhile the superiour `dc_accounts_background_fetch()` is used for
that.

there is still the corresponding context function `dc_all_work_done()`,
this not used by any UI as well, however, it is in use by a python
tests.

not sure, what to do with it, at a first glance, the test still seems
useful.
2024-03-30 01:18:17 +01:00
iequidoo 304e902fce fix: Don't send selfavatar in SecureJoin messages before contact verification (#5354)
Don't attach selfavatar in "v{c,g}-request" and "v{c,g}-auth-required" messages:
- These messages are deleted right after processing, so other devices won't see the avatar.
- It's also good for privacy because the contact isn't yet verified and these messages are auto-sent
  unlike usual unencrypted messages.
2024-03-28 21:29:05 -03:00
B. Petersen 0155d93622 test: Remove flaky time check from test_list_from()
the rendered time output seems different on different systems and timezomes,
eg. on my local machine, it is
`Sent: 2024.03.20 10:00:01 ` and not `Sent: 2024.03.20 09:00:01`,
maybe because of winter/summer time, idk.

as the gist of the test is to check the name,
however, i just removed the whole time check.
2024-03-28 16:40:41 +01:00
link2xt ebd097bdbe ci: shorter names for deltachat-rpc-server jobs 2024-03-28 06:57:44 +00:00
link2xt a11d01f8a3 ci: build deltachat-rpc-server for Android 2024-03-28 06:57:44 +00:00
link2xt 38491b694b build(nix): add outputs for Android binaries
Using NDK 24 because NDK 23 does not have getauxval() function.
2024-03-28 06:57:44 +00:00
55 changed files with 3801 additions and 702 deletions
+6 -6
View File
@@ -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
+41 -3
View File
@@ -21,7 +21,7 @@ jobs:
# Build a version statically linked against musl libc
# to avoid problems with glibc version incompatibility.
build_linux:
name: Build deltachat-rpc-server for Linux
name: Linux
strategy:
fail-fast: false
matrix:
@@ -45,7 +45,7 @@ jobs:
if-no-files-found: error
build_windows:
name: Build deltachat-rpc-server for Windows
name: Windows
strategy:
fail-fast: false
matrix:
@@ -69,7 +69,7 @@ jobs:
if-no-files-found: error
build_macos:
name: Build deltachat-rpc-server for macOS
name: macOS
strategy:
fail-fast: false
matrix:
@@ -94,6 +94,30 @@ jobs:
path: target/${{ matrix.arch }}-apple-darwin/release/deltachat-rpc-server
if-no-files-found: error
build_android:
name: Android
strategy:
fail-fast: false
matrix:
arch: [arm64-v8a, armeabi-v7a]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
show-progress: false
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Build deltachat-rpc-server binaries
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: deltachat-rpc-server-${{ matrix.arch }}-android
path: result/bin/deltachat-rpc-server
if-no-files-found: error
publish:
name: Build wheels and upload binaries to the release
needs: ["build_linux", "build_windows", "build_macos"]
@@ -165,6 +189,18 @@ jobs:
name: deltachat-rpc-server-aarch64-macos
path: deltachat-rpc-server-aarch64-macos.d
- name: Download Android binary for arm64-v8a
uses: actions/download-artifact@v4
with:
name: deltachat-rpc-server-arm64-v8a-android
path: deltachat-rpc-server-arm64-v8a-android.d
- name: Download Android binary for armeabi-v7a
uses: actions/download-artifact@v4
with:
name: deltachat-rpc-server-armeabi-v7a-android
path: deltachat-rpc-server-armeabi-v7a-android.d
- name: Create bin/ directory
run: |
mkdir -p bin
@@ -177,6 +213,8 @@ jobs:
mv deltachat-rpc-server-win64.d/deltachat-rpc-server.exe bin/deltachat-rpc-server-win64.exe
mv deltachat-rpc-server-x86_64-macos.d/deltachat-rpc-server bin/deltachat-rpc-server-x86_64-macos
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
- name: List binaries
run: ls -l bin/
+4
View File
@@ -47,3 +47,7 @@ node/.nyc_output/
# Nix symlink.
result
# direnv
.envrc
.direnv
+110
View File
@@ -1,5 +1,112 @@
# 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
- [**breaking**] Remove data from `DC_EVENT_INCOMING_MSG_BUNCH`.
- [**breaking**] Remove unused `dc_accounts_all_work_done()` ([#5384](https://github.com/deltachat/deltachat-core-rust/pull/5384)).
- deltachat-rpc-client: Add futures.
### Build system
- cmake: Build outside the source tree.
- nix: Add outputs for Android binaries.
- Add `repository` to Cargo.toml.
- python: Remove `setuptools_scm` dependency.
- Add development shell ([#5390](https://github.com/deltachat/deltachat-core-rust/pull/5390)).
### CI
- Update to Rust 1.77.0.
- Build deltachat-rpc-server for Android.
- Shorter names for deltachat-rpc-server jobs.
### Features / Changes
- Do not include provider hostname in `Message-ID`.
- Include 3 recent Message-IDs in `References` header.
- Include more entries into DNS fallback cache.
### Fixes
- Preserve upper-/lowercase of links parsed by `dehtml()` ([#5362](https://github.com/deltachat/deltachat-core-rust/pull/5362)).
- Rescan folders after changing `Config::SentboxWatch`.
- Do not ignore `Contact::get_by_id()` error in `from_field_to_contact_id()`.
- Put overridden sender name into message info.
- Don't send selfavatar in `SecureJoin` messages before contact verification ([#5354](https://github.com/deltachat/deltachat-core-rust/pull/5354)).
- Always set correct `chat_id` for `DC_EVENT_REACTIONS_CHANGED` ([#5419](https://github.com/deltachat/deltachat-core-rust/pull/5419)).
### Refactor
- Remove `MessageObject::from_message_id()`.
- jsonrpc: Add `msg_id` and `account_id` to `get_message()` errors.
- Cleanup `jobs` and `Params` relicts.
### Tests
- `Test_mvbox_sentbox_threads`: Check that sentbox gets configured after setting `sentbox_watch` ([#5105](https://github.com/deltachat/deltachat-core-rust/pull/5105)).
- Remove flaky time check from `test_list_from()`.
- Add failing test for #5418 (wrong `DC_EVENT_REACTIONS_CHANGED`)
### Miscellaneous Tasks
- Add `result` to .gitignore.
- cargo: Bump thiserror from 1.0.57 to 1.0.58.
- cargo: Bump tokio from 1.36.0 to 1.37.0.
- cargo: Bump pin-project from 1.1.4 to 1.1.5.
- cargo: Bump strum from 0.26.1 to 0.26.2.
- cargo: Bump uuid from 1.7.0 to 1.8.0.
- cargo: Bump toml from 0.8.10 to 0.8.12.
- cargo: Bump tokio-stream from 0.1.14 to 0.1.15.
- cargo: Bump smallvec from 1.13.1 to 1.13.2.
- cargo: Bump async-smtp from 0.9.0 to 0.9.1.
- cargo: Bump strum_macros from 0.26.1 to 0.26.2.
- cargo: Bump serde_json from 1.0.114 to 1.0.115.
- cargo: Bump anyhow from 1.0.80 to 1.0.81.
- cargo: Bump syn from 2.0.52 to 2.0.57.
- cargo: Bump futures-lite from 2.2.0 to 2.3.0.
- cargo: Bump axum from 0.7.4 to 0.7.5.
- cargo: Bump reqwest from 0.11.24 to 0.12.2.
- cargo: Bump backtrace from 0.3.69 to 0.3.71.
- cargo: Bump regex from 1.10.3 to 1.10.4.
- cargo: Update aho-corasick from 1.1.2 to 1.1.3.
- Update deny.toml.
## [1.136.6] - 2024-03-19
### Build system
@@ -3762,3 +3869,6 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.136.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.136.3...v1.136.4
[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
Generated
+168 -190
View File
@@ -55,9 +55,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
@@ -121,9 +121,9 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anyhow"
version = "1.0.80"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
dependencies = [
"backtrace",
]
@@ -267,9 +267,9 @@ dependencies = [
[[package]]
name = "async-smtp"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8709c0d4432be428a88a06746689a9cb543e8e27ef7f61ca4d0455003a3d8c5b"
checksum = "928ea96f04e1260036cb01f82c1e5dc7c3b478e7b9463c87712297784ce3cdc6"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -290,7 +290,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -315,19 +315,19 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
dependencies = [
"async-trait",
"axum-core",
"base64 0.21.7",
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"http",
"http-body",
"http-body-util",
"hyper 1.2.0",
"hyper",
"hyper-util",
"itoa",
"matchit",
@@ -341,7 +341,7 @@ dependencies = [
"serde_path_to_error",
"serde_urlencoded",
"sha1",
"sync_wrapper",
"sync_wrapper 1.0.0",
"tokio",
"tokio-tungstenite",
"tower",
@@ -359,13 +359,13 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"sync_wrapper 0.1.2",
"tower-layer",
"tower-service",
"tracing",
@@ -373,9 +373,9 @@ dependencies = [
[[package]]
name = "backtrace"
version = "0.3.69"
version = "0.3.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [
"addr2line",
"cc",
@@ -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",
@@ -627,12 +627,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.83"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
[[package]]
name = "cfb-mode"
@@ -649,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"
@@ -661,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",
@@ -990,7 +993,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -1088,7 +1091,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.136.6"
version = "1.137.2"
dependencies = [
"ansi_term",
"anyhow",
@@ -1099,7 +1102,9 @@ dependencies = [
"async_zip",
"backtrace",
"base64 0.21.7",
"bitflags 1.3.2",
"brotli",
"bstr",
"chrono",
"criterion",
"deltachat-time",
@@ -1169,7 +1174,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.136.6"
version = "1.137.2"
dependencies = [
"anyhow",
"async-channel 2.2.0",
@@ -1193,7 +1198,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.136.6"
version = "1.137.2"
dependencies = [
"ansi_term",
"anyhow",
@@ -1208,7 +1213,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.136.6"
version = "1.137.2"
dependencies = [
"anyhow",
"deltachat",
@@ -1232,12 +1237,12 @@ name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
name = "deltachat_ffi"
version = "1.136.6"
version = "1.137.2"
dependencies = [
"anyhow",
"deltachat",
@@ -1441,7 +1446,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -1750,7 +1755,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -1763,7 +1768,7 @@ dependencies = [
"num-traits",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -1851,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",
@@ -2042,9 +2047,9 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[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",
"futures-core",
@@ -2061,7 +2066,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -2169,35 +2174,16 @@ 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",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "h2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 1.1.0",
"http",
"indexmap",
"slab",
"tokio",
@@ -2335,17 +2321,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.1.0"
@@ -2357,17 +2332,6 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]]
name = "http-body"
version = "1.0.0"
@@ -2375,7 +2339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [
"bytes",
"http 1.1.0",
"http",
]
[[package]]
@@ -2386,8 +2350,8 @@ checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
dependencies = [
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"http",
"http-body",
"pin-project-lite",
]
@@ -2432,30 +2396,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.24",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper"
version = "1.2.0"
@@ -2465,28 +2405,32 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2 0.4.2",
"http 1.1.0",
"http-body 1.0.0",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"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",
"hyper 0.14.28",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
@@ -2496,13 +2440,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"hyper 1.2.0",
"http",
"http-body",
"hyper",
"pin-project-lite",
"socket2",
"tokio",
"tower",
"tower-service",
"tracing",
]
[[package]]
@@ -2565,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]]
@@ -2700,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"
@@ -3032,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",
]
@@ -3113,7 +3068,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -3174,7 +3129,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -3236,7 +3191,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -3462,22 +3417,22 @@ dependencies = [
[[package]]
name = "pin-project"
version = "1.1.4"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.4"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -3977,9 +3932,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.3"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
@@ -4021,20 +3976,22 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.11.24"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.3.24",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.28",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
@@ -4047,7 +4004,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"sync_wrapper 0.1.2",
"system-configuration",
"tokio",
"tokio-native-tls",
@@ -4283,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",
@@ -4300,7 +4257,7 @@ dependencies = [
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
"windows-sys 0.52.0",
]
[[package]]
@@ -4478,7 +4435,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -4494,9 +4451,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.114"
version = "1.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
dependencies = [
"itoa",
"ryu",
@@ -4645,9 +4602,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.13.1"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smawk"
@@ -4749,21 +4706,21 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.26.1"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
[[package]]
name = "strum_macros"
version = "0.26.1"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -4785,9 +4742,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.52"
version = "2.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
dependencies = [
"proc-macro2",
"quote",
@@ -4800,6 +4757,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "sync_wrapper"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c"
[[package]]
name = "synstructure"
version = "0.12.6"
@@ -4901,22 +4864,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.57"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.57"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -4998,9 +4961,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.36.0"
version = "1.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
dependencies = [
"backtrace",
"bytes",
@@ -5033,7 +4996,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -5063,9 +5026,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",
@@ -5115,14 +5078,14 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.10"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.22.6",
"toml_edit 0.22.9",
]
[[package]]
@@ -5147,9 +5110,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.6"
version = "0.22.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
dependencies = [
"indexmap",
"serde",
@@ -5206,7 +5169,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -5273,7 +5236,7 @@ dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http 1.1.0",
"http",
"httparse",
"log",
"rand 0.8.5",
@@ -5410,9 +5373,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom 0.2.12",
"serde",
@@ -5500,7 +5463,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
"wasm-bindgen-shared",
]
@@ -5534,7 +5497,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -5930,7 +5893,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.57",
]
[[package]]
@@ -5950,5 +5913,20 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"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",
]
+11 -8
View File
@@ -1,9 +1,10 @@
[package]
name = "deltachat"
version = "1.136.6"
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]
debug = 0
@@ -44,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" }
@@ -52,11 +55,11 @@ escaper = "0.1"
fast-socks5 = "0.9"
fd-lock = "4"
futures = "0.3"
futures-lite = "2.2.0"
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" }
@@ -77,7 +80,7 @@ quick-xml = "0.31"
quoted_printable = "0.5"
rand = "0.8"
regex = "1.10"
reqwest = { version = "0.11.24", features = ["json"] }
reqwest = { version = "0.12.2", features = ["json"] }
rusqlite = { version = "0.31", features = ["sqlcipher"] }
rust-hsluv = "0.1"
sanitize-filename = "0.5"
@@ -93,7 +96,7 @@ textwrap = "0.16.1"
thiserror = "1"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
tokio-io-timeout = "1.2.0"
tokio-stream = { version = "0.1.14", features = ["fs"] }
tokio-stream = { version = "0.1.15", features = ["fs"] }
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
tokio-util = "0.7.9"
toml = "0.8"
@@ -112,7 +115,7 @@ openssl-src = "~300.1"
ansi_term = "0.12.0"
anyhow = { version = "1", features = ["backtrace"] } # Enable `backtrace` feature in tests.
criterion = { version = "0.5.1", features = ["async_tokio"] }
futures-lite = "2.2.0"
futures-lite = "2.3.0"
log = "0.4"
pretty_env_logger = "0.5"
proptest = { version = "1", default-features = false, features = ["std"] }
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.136.6"
version = "1.137.2"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
+16 -18
View File
@@ -705,7 +705,6 @@ int dc_get_push_state (dc_context_t* context);
/**
* Standalone version of dc_accounts_all_work_done().
* Only used by the python tests.
*/
int dc_all_work_done (dc_context_t* context);
@@ -3099,23 +3098,6 @@ dc_context_t* dc_accounts_get_selected_account (dc_accounts_t* accounts);
int dc_accounts_select_account (dc_accounts_t* accounts, uint32_t account_id);
/**
* This is meant especially for iOS, because iOS needs to tell the system when its background work is done.
*
* iOS can:
* - call dc_start_io() (in case IO was not running)
* - call dc_maybe_network()
* - while dc_accounts_all_work_done() returns false:
* - Wait for #DC_EVENT_CONNECTIVITY_CHANGED
*
* @memberof dc_accounts_t
* @param accounts The account manager as created by dc_accounts_new().
* @return Whether all accounts finished their background work.
* #DC_EVENT_CONNECTIVITY_CHANGED will be sent when this turns to true.
*/
int dc_accounts_all_work_done (dc_accounts_t* accounts);
/**
* Start job and IMAP/SMTP tasks for all accounts managed by the account manager.
* If IO is already running, nothing happens.
@@ -7314,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
/**
* @}
-10
View File
@@ -4851,16 +4851,6 @@ pub unsafe extern "C" fn dc_accounts_get_all(accounts: *mut dc_accounts_t) -> *m
Box::into_raw(Box::new(array))
}
#[no_mangle]
pub unsafe extern "C" fn dc_accounts_all_work_done(accounts: *mut dc_accounts_t) -> libc::c_int {
if accounts.is_null() {
eprintln!("ignoring careless call to dc_accounts_all_work_done()");
return 0;
}
let accounts = &*accounts;
block_on(async move { accounts.read().await.all_work_done().await as libc::c_int })
}
#[no_mangle]
pub unsafe extern "C" fn dc_accounts_start_io(accounts: *mut dc_accounts_t) {
if accounts.is_null() {
+4 -3
View File
@@ -1,10 +1,11 @@
[package]
name = "deltachat-jsonrpc"
version = "1.136.6"
version = "1.137.2"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"
license = "MPL-2.0"
repository = "https://github.com/deltachat/deltachat-core-rust"
[[bin]]
name = "deltachat-jsonrpc-server"
@@ -24,7 +25,7 @@ futures = { version = "0.3.30" }
serde_json = "1"
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
typescript-type-def = { version = "0.5.8", features = ["json_value"] }
tokio = { version = "1.33.0" }
tokio = { version = "1.37.0" }
sanitize-filename = "0.5"
walkdir = "2.5.0"
base64 = "0.21"
@@ -34,7 +35,7 @@ axum = { version = "0.7", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
tokio = { version = "1.33.0", features = ["full", "rt-multi-thread"] }
tokio = { version = "1.37.0", features = ["full", "rt-multi-thread"] }
[features]
+1 -1
View File
@@ -53,5 +53,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.136.6"
"version": "1.137.2"
}
+3 -2
View File
@@ -1,8 +1,9 @@
[package]
name = "deltachat-repl"
version = "1.136.6"
version = "1.137.2"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/deltachat/deltachat-core-rust"
[dependencies]
ansi_term = "0.12.1"
@@ -12,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]
+10 -8
View File
@@ -33,14 +33,6 @@ use tokio::fs;
/// e.g. bitmask 7 triggers actions defined with bits 1, 2 and 4.
async fn reset_tables(context: &Context, bits: i32) {
println!("Resetting tables ({bits})...");
if 0 != bits & 1 {
context
.sql()
.execute("DELETE FROM jobs;", ())
.await
.unwrap();
println!("(1) Jobs reset.");
}
if 0 != bits & 2 {
context
.sql()
@@ -347,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\
@@ -522,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");
}
+2 -7
View File
@@ -1,9 +1,10 @@
[build-system]
requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"]
requires = ["setuptools>=45"]
build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "1.137.2"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -19,9 +20,6 @@ classifiers = [
"Topic :: Communications :: Chat",
"Topic :: Communications :: Email"
]
dynamic = [
"version"
]
readme = "README.md"
[tool.setuptools.package-data]
@@ -32,9 +30,6 @@ deltachat_rpc_client = [
[project.entry-points.pytest11]
"deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin"
[tool.setuptools_scm]
root = ".."
[tool.black]
line-length = 120
@@ -168,3 +168,33 @@ def parse_system_add_remove(text: str) -> Optional[Tuple[str, str, str]]:
return "removed", addr, addr
return None
class futuremethod: # noqa: N801
"""Decorator for async methods."""
def __init__(self, func):
self._func = func
def __get__(self, instance, owner=None):
if instance is None:
return self
def future(*args):
generator = self._func(instance, *args)
res = next(generator)
def f():
try:
generator.send(res())
except StopIteration as e:
return e.value
return f
def wrapper(*args):
f = future(*args)
return f()
wrapper.future = future
return wrapper
@@ -2,7 +2,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
from warnings import warn
from ._utils import AttrDict
from ._utils import AttrDict, futuremethod
from .chat import Chat
from .const import ChatlistFlag, ContactFlag, EventType, SpecialContactId
from .contact import Contact
@@ -76,9 +76,16 @@ class Account:
"""Get self avatar."""
return self.get_config("selfavatar")
def configure(self) -> None:
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."""
self._rpc.configure(self.id)
yield self._rpc.configure.future(self.id)
def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
"""Create a new Contact or return an existing one.
@@ -5,6 +5,7 @@ from typing import AsyncGenerator, List, Optional
import pytest
from . import Account, AttrDict, Bot, Client, DeltaChat, EventType, Message
from ._utils import futuremethod
from .rpc import Rpc
@@ -37,9 +38,10 @@ class ACFactory:
assert not account.is_configured()
return account
def new_configured_account(self) -> Account:
@futuremethod
def new_configured_account(self):
account = self.new_preconfigured_account()
account.configure()
yield account.configure.future()
assert account.is_configured()
return account
@@ -49,8 +51,9 @@ class ACFactory:
bot.configure(credentials["email"], credentials["password"])
return bot
def get_online_account(self) -> Account:
account = self.new_configured_account()
@futuremethod
def get_online_account(self):
account = yield self.new_configured_account.future()
account.start_io()
while True:
event = account.wait_for_event()
@@ -59,7 +62,8 @@ class ACFactory:
return account
def get_online_accounts(self, num: int) -> List[Account]:
return [self.get_online_account() for _ in range(num)]
futures = [self.get_online_account.future() for _ in range(num)]
return [f() for f in futures]
def resetup_account(self, ac: Account) -> Account:
"""Resetup account from scratch, losing the encryption key."""
@@ -13,6 +13,48 @@ class JsonRpcError(Exception):
pass
class RpcFuture:
def __init__(self, rpc: "Rpc", request_id: int, event: Event):
self.rpc = rpc
self.request_id = request_id
self.event = event
def __call__(self):
self.event.wait()
response = self.rpc.request_results.pop(self.request_id)
if "error" in response:
raise JsonRpcError(response["error"])
if "result" in response:
return response["result"]
return None
class RpcMethod:
def __init__(self, rpc: "Rpc", name: str):
self.rpc = rpc
self.name = name
def __call__(self, *args) -> Any:
"""Synchronously calls JSON-RPC method."""
future = self.future(*args)
return future()
def future(self, *args) -> Any:
"""Asynchronously calls JSON-RPC method."""
request_id = next(self.rpc.id_iterator)
request = {
"jsonrpc": "2.0",
"method": self.name,
"params": args,
"id": request_id,
}
event = Event()
self.rpc.request_events[request_id] = event
self.rpc.request_queue.put(request)
return RpcFuture(self.rpc, request_id, event)
class Rpc:
def __init__(self, accounts_dir: Optional[str] = None, **kwargs):
"""The given arguments will be passed to subprocess.Popen()"""
@@ -145,24 +187,4 @@ class Rpc:
return queue.get()
def __getattr__(self, attr: str):
def method(*args) -> Any:
request_id = next(self.id_iterator)
request = {
"jsonrpc": "2.0",
"method": attr,
"params": args,
"id": request_id,
}
event = Event()
self.request_events[request_id] = event
self.request_queue.put(request)
event.wait()
response = self.request_results.pop(request_id)
if "error" in response:
raise JsonRpcError(response["error"])
if "result" in response:
return response["result"]
return None
return method
return RpcMethod(self, attr)
+38 -1
View File
@@ -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"))
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.136.6"
version = "1.137.2"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -15,11 +15,11 @@ deltachat = { path = "..", default-features = false }
anyhow = "1"
env_logger = { version = "0.10.0" }
futures-lite = "2.2.0"
futures-lite = "2.3.0"
log = "0.4"
serde_json = "1"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.33.0", features = ["io-std"] }
tokio = { version = "1.37.0", features = ["io-std"] }
tokio-util = "0.7.9"
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
+1 -4
View File
@@ -39,10 +39,6 @@ skip = [
{ name = "ed25519", version = "1.5.3" },
{ name = "event-listener", version = "2.5.3" },
{ name = "getrandom", version = "<0.2" },
{ name = "h2", version = "0.3.22" },
{ name = "http-body", version = "0.4.5" },
{ name = "http", version = "0.2.11" },
{ name = "hyper", version = "0.14.27" },
{ name = "idna", version = "0.4.0" },
{ name = "pem-rfc7468", version = "0.6.0" },
{ name = "pkcs8", version = "0.9.0" },
@@ -59,6 +55,7 @@ skip = [
{ name = "signature", version = "1.6.4" },
{ name = "spin", version = "<0.9.6" },
{ name = "spki", version = "0.6.0" },
{ name = "sync_wrapper", version = "0.1.2" },
{ name = "syn", version = "1.0.109" },
{ name = "time", version = "<0.3" },
{ name = "toml_edit", version = "0.21.1" },
Generated
+131 -6
View File
@@ -1,8 +1,50 @@
{
"nodes": {
"android": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1710633978,
"narHash": "sha256-yemnwSvW7cdWtXGpivFA5jDO35rGPs6fqxlQ4l6ODXs=",
"owner": "tadfisher",
"repo": "android-nixpkgs",
"rev": "e91fb3d8517538e5ad9b422c9a4f604b56008a9e",
"type": "github"
},
"original": {
"owner": "tadfisher",
"repo": "android-nixpkgs",
"type": "github"
}
},
"devshell": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"android",
"nixpkgs"
]
},
"locked": {
"lastModified": 1708939976,
"narHash": "sha256-O5+nFozxz2Vubpdl1YZtPrilcIXPcRAjqNdNE8oCRoA=",
"owner": "numtide",
"repo": "devshell",
"rev": "5ddecd67edbd568ebe0a55905273e56cc82aabe3",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": "nixpkgs",
"nixpkgs": "nixpkgs_2",
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
@@ -23,6 +65,42 @@
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
@@ -39,7 +117,7 @@
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1698420672,
@@ -71,6 +149,22 @@
}
},
"nixpkgs": {
"locked": {
"lastModified": 1709237383,
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1710631334,
"narHash": "sha256-rL5LSYd85kplL5othxK5lmAtjyMOBg390sGBTb3LRMM=",
@@ -86,7 +180,7 @@
"type": "github"
}
},
"nixpkgs_2": {
"nixpkgs_3": {
"locked": {
"lastModified": 1710765496,
"narHash": "sha256-p7ryWEeQfMwTB6E0wIUd5V2cFTgq+DRRBz2hYGnJZyA=",
@@ -100,7 +194,7 @@
"type": "indirect"
}
},
"nixpkgs_3": {
"nixpkgs_4": {
"locked": {
"lastModified": 1710631334,
"narHash": "sha256-rL5LSYd85kplL5othxK5lmAtjyMOBg390sGBTb3LRMM=",
@@ -118,11 +212,12 @@
},
"root": {
"inputs": {
"android": "android",
"fenix": "fenix",
"flake-utils": "flake-utils",
"flake-utils": "flake-utils_3",
"naersk": "naersk",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_3"
"nixpkgs": "nixpkgs_4"
}
},
"rust-analyzer-src": {
@@ -156,6 +251,36 @@
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
+86 -11
View File
@@ -6,8 +6,9 @@
naersk.url = "github:nix-community/naersk";
nix-filter.url = "github:numtide/nix-filter";
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
android.url = "github:tadfisher/android-nixpkgs";
};
outputs = { self, nixpkgs, flake-utils, nix-filter, naersk, fenix }:
outputs = { self, nixpkgs, flake-utils, nix-filter, naersk, fenix, android }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
@@ -15,6 +16,11 @@
fenixPkgs = fenix.packages.${system};
naersk' = pkgs.callPackage naersk { };
manifest = (pkgs.lib.importTOML ./Cargo.toml).package;
androidSdk = android.sdk.${system} (sdkPkgs:
builtins.attrValues {
inherit (sdkPkgs) ndk-24-0-8215888 cmdline-tools-latest;
});
androidNdkRoot = "${androidSdk}/share/android-sdk/ndk/24.0.8215888";
rustSrc = nix-filter.lib {
root = ./.;
@@ -105,8 +111,6 @@
mkWin64RustPackage = packageName:
let
rustTarget = "x86_64-pc-windows-gnu";
in
let
toolchainWin = fenixPkgs.combine [
fenixPkgs.stable.rustc
fenixPkgs.stable.cargo
@@ -173,13 +177,12 @@
model = "win32";
package = null;
};
})).overrideAttrs (oldAttr: rec{
})).overrideAttrs (oldAttr: {
configureFlags = oldAttr.configureFlags ++ [
"--disable-sjlj-exceptions --with-dwarf2"
];
})
);
winStdenv = pkgsWin32.buildPackages.overrideCC pkgsWin32.stdenv winCC;
in
naerskWin.buildPackage rec {
pname = packageName;
@@ -251,6 +254,61 @@
LD = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc";
};
androidAttrs = {
armeabi-v7a = {
cc = "armv7a-linux-androideabi19-clang";
rustTarget = "armv7-linux-androideabi";
};
arm64-v8a = {
cc = "aarch64-linux-android21-clang";
rustTarget = "aarch64-linux-android";
};
};
mkAndroidRustPackage = arch: packageName:
let
rustTarget = androidAttrs.${arch}.rustTarget;
toolchain = fenixPkgs.combine [
fenixPkgs.stable.rustc
fenixPkgs.stable.cargo
fenixPkgs.targets.${rustTarget}.stable.rust-std
];
naersk-lib = pkgs.callPackage naersk {
cargo = toolchain;
rustc = toolchain;
};
targetToolchain = "${androidNdkRoot}/toolchains/llvm/prebuilt/linux-x86_64";
targetCcName = androidAttrs.${arch}.cc;
targetCc = "${targetToolchain}/bin/${targetCcName}";
in
naersk-lib.buildPackage rec {
pname = packageName;
cargoBuildOptions = x: x ++ [ "--package" packageName ];
version = manifest.version;
strictDeps = true;
src = rustSrc;
nativeBuildInputs = [
pkgs.perl # Needed to build vendored OpenSSL.
];
auditable = false; # Avoid cargo-auditable failures.
doCheck = false; # Disable test as it requires network access.
CARGO_BUILD_TARGET = rustTarget;
TARGET_CC = "${targetCc}";
CARGO_BUILD_RUSTFLAGS = [
"-C"
"linker=${TARGET_CC}"
];
CC = "${targetCc}";
LD = "${targetCc}";
};
mkAndroidPackages = arch: {
"deltachat-rpc-server-${arch}-android" = mkAndroidRustPackage arch "deltachat-rpc-server";
"deltachat-repl-${arch}-android" = mkAndroidRustPackage arch "deltachat-repl";
};
mkRustPackages = arch:
let
rpc-server = mkCrossRustPackage arch "deltachat-rpc-server";
@@ -295,7 +353,11 @@
mkRustPackages "i686-linux" //
mkRustPackages "x86_64-linux" //
mkRustPackages "armv7l-linux" //
mkRustPackages "armv6l-linux" // rec {
mkRustPackages "armv6l-linux" //
mkAndroidPackages "armeabi-v7a" //
mkAndroidPackages "arm64-v8a" //
mkAndroidPackages "x86" //
mkAndroidPackages "x86_64" // rec {
# Run with `nix run .#deltachat-repl foo.db`.
deltachat-repl = mkRustPackage "deltachat-repl";
deltachat-rpc-server = mkRustPackage "deltachat-rpc-server";
@@ -371,7 +433,7 @@
};
libdeltachat =
pkgs.stdenv.mkDerivation rec {
pkgs.stdenv.mkDerivation {
pname = "libdeltachat";
version = manifest.version;
src = rustSrc;
@@ -413,19 +475,18 @@
};
deltachat-rpc-client =
pkgs.python3Packages.buildPythonPackage rec {
pkgs.python3Packages.buildPythonPackage {
pname = "deltachat-rpc-client";
version = manifest.version;
src = pkgs.lib.cleanSource ./deltachat-rpc-client;
format = "pyproject";
propagatedBuildInputs = [
pkgs.python3Packages.setuptools
pkgs.python3Packages.setuptools_scm
];
};
deltachat-python =
pkgs.python3Packages.buildPythonPackage rec {
pkgs.python3Packages.buildPythonPackage {
pname = "deltachat-python";
version = manifest.version;
src = pkgs.lib.cleanSource ./python;
@@ -438,7 +499,6 @@
];
propagatedBuildInputs = [
pkgs.python3Packages.setuptools
pkgs.python3Packages.setuptools_scm
pkgs.python3Packages.pkgconfig
pkgs.python3Packages.cffi
pkgs.python3Packages.imap-tools
@@ -462,6 +522,21 @@
installPhase = ''mkdir -p $out; cp -av dist/html $out'';
};
};
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
(fenixPkgs.complete.withComponents [
"cargo"
"clippy"
"rust-src"
"rustc"
"rustfmt"
])
cargo-deny
fenixPkgs.rust-analyzer
perl # needed to build vendored OpenSSL
];
};
}
);
}
+147 -54
View File
@@ -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",
]
+2
View File
@@ -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,
+2
View File
@@ -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,
-9
View File
@@ -3048,14 +3048,6 @@ NAPI_METHOD(dcn_accounts_select_account) {
NAPI_RETURN_UINT32(result);
}
NAPI_METHOD(dcn_accounts_all_work_done) {
NAPI_ARGV(1);
NAPI_DCN_ACCOUNTS();
int result = dc_accounts_all_work_done(dcn_accounts->dc_accounts);
NAPI_RETURN_INT32(result);
}
NAPI_METHOD(dcn_accounts_start_io) {
NAPI_ARGV(1);
NAPI_DCN_ACCOUNTS();
@@ -3382,7 +3374,6 @@ NAPI_INIT() {
NAPI_EXPORT_FUNCTION(dcn_accounts_get_account);
NAPI_EXPORT_FUNCTION(dcn_accounts_get_selected_account);
NAPI_EXPORT_FUNCTION(dcn_accounts_select_account);
NAPI_EXPORT_FUNCTION(dcn_accounts_all_work_done);
NAPI_EXPORT_FUNCTION(dcn_accounts_start_io);
NAPI_EXPORT_FUNCTION(dcn_accounts_stop_io);
NAPI_EXPORT_FUNCTION(dcn_accounts_maybe_network);
+1 -1
View File
@@ -55,5 +55,5 @@
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.136.6"
"version": "1.137.2"
}
+2 -9
View File
@@ -1,9 +1,10 @@
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2", "cffi>=1.0.0", "pkgconfig"]
requires = ["setuptools>=45", "wheel", "cffi>=1.0.0", "pkgconfig"]
build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
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"
@@ -26,9 +27,6 @@ dependencies = [
"pluggy",
"requests",
]
dynamic = [
"version"
]
[project.urls]
"Home" = "https://github.com/deltachat/deltachat-core-rust/"
@@ -44,11 +42,6 @@ deltachat = [
"py.typed"
]
[tool.setuptools_scm]
root = ".."
tag_regex = '^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$'
git_describe_command = "git describe --dirty --tags --long --match v*.*"
[tool.black]
line-length = 120
+1 -1
View File
@@ -1 +1 @@
2024-03-19
2024-04-05
+1 -1
View File
@@ -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
+2
View File
@@ -73,6 +73,8 @@ def main():
"deltachat-jsonrpc/Cargo.toml",
"deltachat-rpc-server/Cargo.toml",
"deltachat-repl/Cargo.toml",
"python/pyproject.toml",
"deltachat-rpc-client/pyproject.toml",
]
try:
opts = parser.parse_args()
-19
View File
@@ -253,25 +253,6 @@ impl Accounts {
self.accounts.keys().copied().collect()
}
/// This is meant especially for iOS, because iOS needs to tell the system when its background work is done.
///
/// Returns whether all accounts finished their background work.
/// DC_EVENT_CONNECTIVITY_CHANGED will be sent when this turns to true.
///
/// iOS can:
/// - call dc_start_io() (in case IO was not running)
/// - call dc_maybe_network()
/// - while dc_accounts_all_work_done() returns false:
/// - Wait for DC_EVENT_CONNECTIVITY_CHANGED
pub async fn all_work_done(&self) -> bool {
for account in self.accounts.values() {
if !account.all_work_done().await {
return false;
}
}
true
}
/// Starts background tasks such as IMAP and SMTP loops for all accounts.
pub async fn start_io(&mut self) {
for account in self.accounts.values_mut() {
+22 -7
View File
@@ -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(())
}
+5 -12
View File
@@ -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)));
@@ -2742,17 +2744,8 @@ async fn prepare_send_msg(
/// The caller has to interrupt SMTP loop or otherwise process new rows.
pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
let attach_selfavatar = match shall_attach_selfavatar(context, msg.chat_id).await {
Ok(attach_selfavatar) => attach_selfavatar,
Err(err) => {
warn!(context, "SMTP job cannot get selfavatar-state: {err:#}.");
false
}
};
let mimefactory = MimeFactory::from_msg(context, msg, attach_selfavatar).await?;
let mimefactory = MimeFactory::from_msg(context, msg).await?;
let attach_selfavatar = mimefactory.attach_selfavatar;
let mut recipients = mimefactory.recipients();
let from = context.get_primary_self_addr().await?;
+1 -1
View File
@@ -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,
+76 -90
View File
@@ -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()
}
}
+16 -1
View File
@@ -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;
+2 -2
View File
@@ -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()
+14 -2
View File
@@ -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
+34 -16
View File
@@ -10,7 +10,7 @@ use lettre_email::{Address, Header, MimeMultipartType, PartBuilder};
use tokio::fs;
use crate::blob::BlobObject;
use crate::chat::Chat;
use crate::chat::{self, Chat};
use crate::config::Config;
use crate::constants::{Chattype, DC_FROM_HANDSHAKE};
use crate::contact::Contact;
@@ -83,7 +83,7 @@ pub struct MimeFactory<'a> {
sync_ids_to_delete: Option<String>,
/// True if the avatar should be attached.
attach_selfavatar: bool,
pub attach_selfavatar: bool,
}
/// Result of rendering a message, ready to be submitted to a send job.
@@ -138,11 +138,7 @@ struct MessageHeaders {
}
impl<'a> MimeFactory<'a> {
pub async fn from_msg(
context: &Context,
msg: &'a Message,
attach_selfavatar: bool,
) -> Result<MimeFactory<'a>> {
pub async fn from_msg(context: &Context, msg: &'a Message) -> Result<MimeFactory<'a>> {
let chat = Chat::load_from_db(context, msg.chat_id).await?;
let from_addr = context.get_primary_self_addr().await?;
@@ -217,6 +213,7 @@ impl<'a> MimeFactory<'a> {
},
)
.await?;
let attach_selfavatar = Self::should_attach_selfavatar(context, msg).await;
let factory = MimeFactory {
from_addr,
@@ -392,6 +389,31 @@ impl<'a> MimeFactory<'a> {
}
}
async fn should_attach_selfavatar(context: &Context, msg: &Message) -> bool {
let cmd = msg.param.get_cmd();
(cmd != SystemMessage::SecurejoinMessage || {
let step = msg.param.get(Param::Arg).unwrap_or_default();
// Don't attach selfavatar at the earlier SecureJoin steps:
// - The corresponding messages, i.e. "v{c,g}-request" and "v{c,g}-auth-required" are
// deleted right after processing, so other devices won't see the avatar.
// - It's also good for privacy because the contact isn't yet verified and these
// messages are auto-sent unlike usual unencrypted messages.
step == "vg-request-with-auth"
|| step == "vc-request-with-auth"
|| step == "vg-member-added"
|| step == "vc-contact-confirm"
}) && match chat::shall_attach_selfavatar(context, msg.chat_id).await {
Ok(should) => should,
Err(err) => {
warn!(
context,
"should_attach_selfavatar: cannot get selfavatar state: {err:#}."
);
false
}
}
}
fn grpimage(&self) -> Option<String> {
match &self.loaded {
Loaded::Message { chat } => {
@@ -553,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));
@@ -1836,7 +1854,7 @@ mod tests {
Original-Message-ID: <2893@example.com>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\
\n", &t).await;
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
let mf = MimeFactory::from_msg(&t, &new_msg).await.unwrap();
// The subject string should not be "Re: message opened"
assert_eq!("Re: Hello, Bob", mf.subject_str(&t).await.unwrap());
}
@@ -1971,7 +1989,7 @@ mod tests {
new_msg.chat_id = chat_id;
chat::prepare_msg(&t, chat_id, &mut new_msg).await.unwrap();
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
let mf = MimeFactory::from_msg(&t, &new_msg).await.unwrap();
mf.subject_str(&t).await.unwrap()
}
@@ -2056,7 +2074,7 @@ mod tests {
new_msg.set_quote(&t, Some(&incoming_msg)).await.unwrap();
}
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
let mf = MimeFactory::from_msg(&t, &new_msg).await.unwrap();
mf.subject_str(&t).await.unwrap()
}
@@ -2104,7 +2122,7 @@ mod tests {
)
.await;
let mimefactory = MimeFactory::from_msg(&t, &msg, false).await.unwrap();
let mimefactory = MimeFactory::from_msg(&t, &msg).await.unwrap();
let recipients = mimefactory.recipients();
assert_eq!(recipients, vec!["charlie@example.com"]);
+35 -43
View File
@@ -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(
+13 -19
View File
@@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize};
use crate::blob::BlobObject;
use crate::context::Context;
use crate::message::MsgId;
use crate::mimeparser::SystemMessage;
/// Available param keys.
@@ -18,7 +17,7 @@ use crate::mimeparser::SystemMessage;
)]
#[repr(u8)]
pub enum Param {
/// For messages and jobs
/// For messages
File = b'f',
/// For messages: original filename (as shown in chat)
@@ -65,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',
@@ -107,18 +115,12 @@ pub enum Param {
/// is used to also send all the forwarded messages.
PrepForwards = b'P',
/// For Jobs
/// For Messages
SetLatitude = b'l',
/// For Jobs
/// For Messages
SetLongitude = b'n',
/// For Jobs
AlsoMove = b'M',
/// For MDN-sending job
MsgId = b'I',
/// For Groups
///
/// An unpromoted group has not had any messages sent to it and thus only exists on the
@@ -173,9 +175,6 @@ pub enum Param {
/// For Chats: timestamp of member list update.
MemberListTimestamp = b'k',
/// For Chats: timestamp of protection settings update.
ProtectionSettingsTimestamp = b'L',
/// For Webxdc Message Instances: Current document name
WebxdcDocument = b'R',
@@ -190,6 +189,7 @@ pub enum Param {
/// For messages: Whether [crate::message::Viewtype::Sticker] should be forced.
ForceSticker = b'X',
// 'L' was defined as ProtectionSettingsTimestamp for Chats, however, never used in production.
}
/// An object for handling key=value parameter lists.
@@ -399,12 +399,6 @@ impl Params {
Ok(Some(path))
}
pub fn get_msg_id(&self) -> Option<MsgId> {
self.get(Param::MsgId)
.and_then(|x| x.parse().ok())
.map(MsgId::new)
}
/// Set the given parameter to the passed in `i32`.
pub fn set_int(&mut self, key: Param, value: i32) -> &mut Self {
self.set(key, format!("{value}"));
+241 -5
View File
@@ -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;
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;
@@ -653,4 +869,24 @@ Here's my footer -- bob@example.net"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_send_reaction_multidevice() -> Result<()> {
let alice0 = TestContext::new_alice().await;
let alice1 = TestContext::new_alice().await;
let bob_id = Contact::create(&alice0, "", "bob@example.net").await?;
let chat_id = ChatId::create_for_contact(&alice0, bob_id).await?;
let alice0_msg_id = send_text_msg(&alice0, chat_id, "foo".to_string()).await?;
let alice1_msg = alice1.recv_msg(&alice0.pop_sent_msg().await).await;
send_reaction(&alice0, alice0_msg_id, "👀").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)
.await?;
Ok(())
}
}
+7
View File
@@ -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?;
+31 -2
View File
@@ -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;
@@ -4408,7 +4437,7 @@ async fn test_list_from() -> Result<()> {
"clubinfo@donotreply.oeamtc.at"
);
let info = msg.id.get_info(t).await?;
assert!(info.contains("Sent: 2024.03.20 09:00:01 by ~ÖAMTC (clubinfo@donotreply.oeamtc.at)"));
assert!(info.contains(" by ~ÖAMTC (clubinfo@donotreply.oeamtc.at)"));
Ok(())
}
+1 -1
View File
@@ -170,7 +170,7 @@ impl ConnectivityStore {
}
/// Set all folder states to InterruptingIdle in case they were `Connected` before.
/// Called during `dc_maybe_network()` to make sure that `dc_accounts_all_work_done()`
/// Called during `dc_maybe_network()` to make sure that `dc_all_work_done()`
/// returns false immediately after `dc_maybe_network()`.
pub(crate) async fn idle_interrupted(inbox: ConnectivityStore, oboxes: Vec<ConnectivityStore>) {
let mut connectivity_lock = inbox.0.lock().await;
+1 -1
View File
@@ -349,7 +349,7 @@ pub(crate) async fn handle_securejoin_handshake(
)
.await
.context("failed sending auth-required handshake message")?;
Ok(HandshakeMessage::Ignore)
Ok(HandshakeMessage::Done)
}
"vg-auth-required" | "vc-auth-required" => {
/*========================================================
+1 -1
View File
@@ -104,7 +104,7 @@ pub(super) async fn handle_auth_required(
.set_peer_verified(context, message.timestamp_sent)
.await?;
bobstate.emit_progress(context, JoinerProgress::RequestWithAuthSent);
Ok(HandshakeMessage::Ignore)
Ok(HandshakeMessage::Done)
}
None => Ok(HandshakeMessage::Ignore),
}
+10 -11
View File
@@ -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)
}
@@ -806,13 +812,6 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
Param::File,
)
.await?;
maybe_add_from_param(
&context.sql,
&mut files_in_use,
"SELECT param FROM jobs;",
Param::File,
)
.await?;
maybe_add_from_param(
&context.sql,
&mut files_in_use,
File diff suppressed because it is too large Load Diff
+963
View 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, &param).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, &param).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, &param).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(())
}
}
+27
View File
@@ -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
+61 -81
View File
@@ -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
}
}
+5 -2
View File
@@ -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());