mirror of
https://github.com/chatmail/core.git
synced 2026-04-14 03:57:19 +03:00
Compare commits
100 Commits
v1.113.0
...
tokio-cons
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
482abe154e | ||
|
|
c68a2e3820 | ||
|
|
c7ad0b1f4f | ||
|
|
0dd9e3a77e | ||
|
|
d27e3d085e | ||
|
|
10b2aa5350 | ||
|
|
3a29a555bf | ||
|
|
f024f396bf | ||
|
|
24d52c5909 | ||
|
|
f9dc8edbcb | ||
|
|
2e6f98f4e4 | ||
|
|
50431d8cfe | ||
|
|
55fcd589db | ||
|
|
081178d623 | ||
|
|
92d5857150 | ||
|
|
bb45c249a3 | ||
|
|
8796e0472a | ||
|
|
3bd16ba045 | ||
|
|
4b7ff6f003 | ||
|
|
53449ea5b3 | ||
|
|
3b381c4862 | ||
|
|
ce729263a5 | ||
|
|
67480999c0 | ||
|
|
fb8b9f60ce | ||
|
|
9ed36d4e05 | ||
|
|
e3c01d76c4 | ||
|
|
cb7f96449d | ||
|
|
e4f4dacaf0 | ||
|
|
9fc1fe74ad | ||
|
|
991089d98e | ||
|
|
c7a250da31 | ||
|
|
c5b6bad956 | ||
|
|
c4534ff621 | ||
|
|
b2c299fa82 | ||
|
|
68aa15950a | ||
|
|
4a593a8d7e | ||
|
|
2328ba54be | ||
|
|
e216dfd655 | ||
|
|
86472aba2c | ||
|
|
15ce54edfb | ||
|
|
b7bbb3ee9a | ||
|
|
d3236e79fd | ||
|
|
aa212b2b7e | ||
|
|
a0c51b3c3a | ||
|
|
e50d7724e3 | ||
|
|
0d30e66dda | ||
|
|
5c3df7e452 | ||
|
|
3efbe5b1ef | ||
|
|
52cfba06ea | ||
|
|
afba63603e | ||
|
|
3a25d6a44e | ||
|
|
0688895022 | ||
|
|
ce0e5416e6 | ||
|
|
7918a6801e | ||
|
|
6af631e8df | ||
|
|
625ecaa9b5 | ||
|
|
24fe1b9c15 | ||
|
|
ba36d09c70 | ||
|
|
f57be7187e | ||
|
|
6a00338f79 | ||
|
|
16906210e1 | ||
|
|
f9b4540387 | ||
|
|
9755438d0d | ||
|
|
fe9534ed7d | ||
|
|
134c9ada68 | ||
|
|
a3240452ff | ||
|
|
84beb6647d | ||
|
|
ecf7e2d909 | ||
|
|
fcfcf4bbf3 | ||
|
|
c62b6d77b7 | ||
|
|
7e51b9686f | ||
|
|
542ec4cac4 | ||
|
|
64b25d9ec0 | ||
|
|
f91b6fbdf0 | ||
|
|
41445a506e | ||
|
|
798db9d019 | ||
|
|
2e860c32ab | ||
|
|
0e1faed6e5 | ||
|
|
f5de3be977 | ||
|
|
4b0a30eb66 | ||
|
|
7710467571 | ||
|
|
1687794b81 | ||
|
|
158541f05c | ||
|
|
eb28899cd0 | ||
|
|
432046225a | ||
|
|
bf958ce6c1 | ||
|
|
ea8ee4e67d | ||
|
|
edfdbbdc90 | ||
|
|
1b8bfef441 | ||
|
|
514074de8b | ||
|
|
e7aab5c67c | ||
|
|
40484e875e | ||
|
|
fc215ceb63 | ||
|
|
2701c135db | ||
|
|
3a8df3e673 | ||
|
|
fa95b269a5 | ||
|
|
0e9f8c4726 | ||
|
|
a8d4cbd5c1 | ||
|
|
f68a2fc387 | ||
|
|
37503dd3e8 |
@@ -9,3 +9,6 @@
|
||||
# invoking `cargo test` threads are allowed to have a large enough
|
||||
# stack size without needing to use an optimised build.
|
||||
RUST_MIN_STACK = "8388608"
|
||||
|
||||
[build]
|
||||
rustflags = ["--cfg", "tokio_unstable"]
|
||||
25
.github/mergeable.yml
vendored
25
.github/mergeable.yml
vendored
@@ -1,26 +1,15 @@
|
||||
version: 2
|
||||
mergeable:
|
||||
- when: pull_request.*
|
||||
name: "Changelog check"
|
||||
name: "Conventional Commits"
|
||||
validate:
|
||||
- do: or
|
||||
validate:
|
||||
- do: description
|
||||
must_include:
|
||||
regex: "#skip-changelog"
|
||||
- do: and
|
||||
validate:
|
||||
- do: dependent
|
||||
changed:
|
||||
file: "src/**"
|
||||
required: ["CHANGELOG.md"]
|
||||
- do: dependent
|
||||
changed:
|
||||
file: "deltachat-ffi/src/**"
|
||||
required: ["CHANGELOG.md"]
|
||||
- do: title
|
||||
begins_with:
|
||||
match: ['feat', 'fix', 'api', 'refactor', 'perf', 'test', 'style', 'chore', 'cargo', 'build', 'ci', 'docs']
|
||||
|
||||
fail:
|
||||
- do: checks
|
||||
status: "action_required"
|
||||
payload:
|
||||
title: Changelog might need an update
|
||||
summary: "Check if CHANGELOG.md needs an update or add #skip-changelog to the PR description."
|
||||
title: PR title should follow conventional commits
|
||||
summary: "PR title should follow https://conventionalcommits.org. See https://github.com/deltachat/deltachat-core-rust/blob/master/CONTRIBUTING.md for details."
|
||||
|
||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -222,7 +222,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version.
|
||||
- os: ubuntu-latest
|
||||
python: 3.11
|
||||
- os: macos-latest
|
||||
@@ -234,11 +233,12 @@ jobs:
|
||||
- os: macos-latest
|
||||
python: pypy3.9
|
||||
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built. Test it with minimum supported Rust version.
|
||||
# Minimum Supported Python Version = 3.8
|
||||
#
|
||||
# Python 3.7 has at least one known bug related to starting subprocesses
|
||||
# in asyncio programs: <https://bugs.python.org/issue35621>
|
||||
- os: ubuntu-latest
|
||||
python: 3.7
|
||||
python: 3.8
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
||||
23
.github/workflows/deltachat-rpc-server.yml
vendored
23
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -84,9 +84,28 @@ jobs:
|
||||
path: target/${{ matrix.target}}/release/${{ matrix.path }}
|
||||
if-no-files-found: error
|
||||
|
||||
build_macos:
|
||||
name: Build deltachat-rpc-server for macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup rust target
|
||||
run: rustup target add x86_64-apple-darwin
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --package deltachat-rpc-server --target x86_64-apple-darwin --features vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-macos
|
||||
path: target/x86_64-apple-darwin/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Upload binaries to the release
|
||||
needs: ["build_linux", "build_windows"]
|
||||
needs: ["build_linux", "build_windows", "build_macos"]
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: "ubuntu-latest"
|
||||
@@ -97,7 +116,7 @@ jobs:
|
||||
- name: Compose dist/ directory
|
||||
run: |
|
||||
mkdir dist
|
||||
for x in x86_64 i686 aarch64 armv7 win32.exe win64.exe; do
|
||||
for x in x86_64 i686 aarch64 armv7 win32.exe win64.exe x86_64-macos; do
|
||||
mv "deltachat-rpc-server-$x"/* "dist/deltachat-rpc-server-$x"
|
||||
done
|
||||
|
||||
|
||||
79
CHANGELOG.md
79
CHANGELOG.md
@@ -1,5 +1,72 @@
|
||||
# Changelog
|
||||
|
||||
## [1.115.0] - 2023-05-12
|
||||
|
||||
### JSON-RPC API Changes
|
||||
|
||||
- Sort reactions in descending order ([#4388](https://github.com/deltachat/deltachat-core-rust/pull/4388)).
|
||||
- Add API to get reactions outside the message snapshot.
|
||||
- `get_chatlist_items_by_entries` now takes only chatids instead of `ChatListEntries`.
|
||||
- `get_chatlist_entries` now returns `Vec<u32>` of chatids instead of `ChatListEntries`.
|
||||
- `JSONRPCReactions.reactions` is now a `Vec<JSONRPCReaction>` with unique reactions and their count, sorted in descending order.
|
||||
- `Event`: `context_id` property is now called `contextId`.
|
||||
- Expand `MessageSearchResult`:
|
||||
- Always include `chat_name`(not an option anymore).
|
||||
- Add `author_id`, `chat_type`, `chat_color`, `is_chat_protected`, `is_chat_contact_request`, `is_chat_archived`.
|
||||
- `author_name` now contains the overridden sender name.
|
||||
- `ChatListItemFetchResult` gets new properties: `summary_preview_image`, `last_message_type` and `last_message_id`
|
||||
- New `MessageReadReceipt` type and `get_message_read_receipts(account_id, message_id)` jsonrpc method.
|
||||
|
||||
### API Changes
|
||||
|
||||
- New rust API `send_webxdc_status_update_struct` to send a `StatusUpdateItem`.
|
||||
- Add `get_msg_read_receipts(context, msg_id)` - get the contacts that send read receipts for a message.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Build deltachat-rpc-server releases for x86\_64 macOS.
|
||||
- Generate changelogs using git-cliff ([#4393](https://github.com/deltachat/deltachat-core-rust/pull/4393), [#4396](https://github.com/deltachat/deltachat-core-rust/pull/4396)).
|
||||
- Improve SMTP logging.
|
||||
- Do not cut incoming text if "bot" config is set.
|
||||
|
||||
### Fixes
|
||||
|
||||
- JSON-RPC: typescript client: fix types of events in event emitter ([#4373](https://github.com/deltachat/deltachat-core-rust/pull/4373)).
|
||||
- Fetch at most 100 existing messages even if EXISTS was not received ([#4383](https://github.com/deltachat/deltachat-core-rust/pull/4383)).
|
||||
- Don't put a double dot at the end of error messages ([#4398](https://github.com/deltachat/deltachat-core-rust/pull/4398)).
|
||||
- Recreate `smtp` table with AUTOINCREMENT `id` ([#4390](https://github.com/deltachat/deltachat-core-rust/pull/4390)).
|
||||
- Do not return an error from `send_msg_to_smtp` if retry limit is exceeded.
|
||||
- Make the bots automatically accept group chat contact requests ([#4377](https://github.com/deltachat/deltachat-core-rust/pull/4377)).
|
||||
- Delete `smtp` rows when message sending is cancelled ([#4391](https://github.com/deltachat/deltachat-core-rust/pull/4391)).
|
||||
|
||||
### Refactor
|
||||
|
||||
- Iterate over `msg_ids` without .iter().
|
||||
|
||||
## [1.112.9] - 2023-05-12
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fetch at most 100 existing messages even if EXISTS was not received.
|
||||
- Delete `smtp` rows when message sending is cancelled.
|
||||
|
||||
### Changes
|
||||
|
||||
- Improve SMTP logging.
|
||||
|
||||
## [1.114.0] - 2023-04-24
|
||||
|
||||
### Changes
|
||||
- JSON-RPC: Use long polling instead of server-sent notifications to retrieve events.
|
||||
This better corresponds to JSON-RPC 2.0 server-client distinction
|
||||
and is expected to simplify writing new bindings
|
||||
because dispatching events can be done on higher level.
|
||||
- JSON-RPC: TS: Client now has a mandatory argument whether you want to start listening for events.
|
||||
|
||||
### Fixes
|
||||
- JSON-RPC: do not print to stdout on failure to find an account.
|
||||
|
||||
|
||||
## [1.113.0] - 2023-04-18
|
||||
|
||||
### Added
|
||||
@@ -32,6 +99,12 @@
|
||||
- Fix python bindings README documentation on installing the bindings from source.
|
||||
- Remove confusing log line "ignoring unsolicited response Recent(…)". #3934
|
||||
|
||||
## [1.112.8] - 2023-04-20
|
||||
|
||||
### Changes
|
||||
- Add `get_http_response` JSON-RPC API.
|
||||
- Add C API to get HTTP responses.
|
||||
|
||||
## [1.112.7] - 2023-04-17
|
||||
|
||||
### Fixes
|
||||
@@ -2411,4 +2484,8 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.112.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.4...v1.112.5
|
||||
[1.112.6]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.5...v1.112.6
|
||||
[1.112.7]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.6...v1.112.7
|
||||
[1.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.7...v1.113.0
|
||||
[1.112.8]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.7...v1.112.8
|
||||
[1.112.9]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.8...v1.112.9
|
||||
[1.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.113.0
|
||||
[1.114.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.113.0...v1.114.0
|
||||
[1.115.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.114.0...v1.115.0
|
||||
|
||||
70
CONTRIBUTING.md
Normal file
70
CONTRIBUTING.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Contributing guidelines
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
If you found a bug, [report it on GitHub](https://github.com/deltachat/deltachat-core-rust/issues).
|
||||
If the bug you found is specific to
|
||||
[Android](https://github.com/deltachat/deltachat-android/issues),
|
||||
[iOS](https://github.com/deltachat/deltachat-ios/issues) or
|
||||
[Desktop](https://github.com/deltachat/deltachat-desktop/issues),
|
||||
report it to the corresponding repository.
|
||||
|
||||
## Proposing features
|
||||
|
||||
If you have a feature request, create a new topic on the [forum](https://support.delta.chat/).
|
||||
|
||||
## Contributing code
|
||||
|
||||
If you want to contribute a code, [open a pull request](https://github.com/deltachat/deltachat-core-rust/pulls).
|
||||
|
||||
You can find the list of good first issues
|
||||
and a link to this guide
|
||||
on the contributing page: <https://github.com/deltachat/deltachat-core-rust/contribute>
|
||||
|
||||
### Coding conventions
|
||||
|
||||
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
|
||||
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
|
||||
|
||||
Commit messages follow the [Conventional Commits] notation.
|
||||
We use [git-cliff] to generate the changelog from commit messages before the release.
|
||||
|
||||
With **`git cliff --unreleased`**, you can check how the changelog entry for your commit will look.
|
||||
|
||||
The following prefix types are used:
|
||||
- `feat`: Features, e.g. "feat: Pause IO for BackupProvider". If you are unsure what's the category of your commit, you can often just use `feat`.
|
||||
- `fix`: Bug fixes, e.g. "fix: delete `smtp` rows when message sending is cancelled"
|
||||
- `api`: API changes, e.g. "api(rust): add `get_msg_read_receipts(context, msg_id)`"
|
||||
- `refactor`: Refactorings, e.g. "refactor: iterate over `msg_ids` without `.iter()`"
|
||||
- `perf`: Performance improvements, e.g. "perf: improve SQLite performance with `PRAGMA synchronous=normal`"
|
||||
- `test`: Test changes and improvements to the testing framework.
|
||||
- `build`: Build system and tool configuration changes, e.g. "build(git-cliff): put "ci" commits into "CI" section of changelog"
|
||||
- `ci`: CI configuration changes, e.g. "ci: limit artifact retention time for `libdeltachat.a` to 1 day"
|
||||
- `docs`: Documentation changes, e.g. "docs: add contributing guidelines"
|
||||
- `chore`: miscellaneous tasks, e.g. "chore: add `.DS_Store` to `.gitignore`"
|
||||
|
||||
Release preparation commits are marked as "chore(release): prepare for vX.Y.Z".
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
Use a `!` to mark breaking changes, e.g. "api!: Remove `dc_chat_can_send`".
|
||||
|
||||
Alternatively, breaking changes can go into the commit description, e.g.:
|
||||
|
||||
```
|
||||
fix: Fix race condition and db corruption when a message was received during backup
|
||||
|
||||
BREAKING CHANGE: You have to call `dc_stop_io()`/`dc_start_io()` before/after `dc_imex(DC_IMEX_EXPORT_BACKUP)`
|
||||
```
|
||||
|
||||
#### Multiple Changes in one PR
|
||||
|
||||
If you have multiple changes in one PR, create multiple conventional commits, and then do a rebase merge. Otherwise, you should usually do a squash merge.
|
||||
|
||||
[Clippy]: https://doc.rust-lang.org/clippy/
|
||||
[Conventional Commits]: https://www.conventionalcommits.org/
|
||||
[git-cliff]: https://git-cliff.org/
|
||||
|
||||
## Other ways to contribute
|
||||
|
||||
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).
|
||||
344
Cargo.lock
generated
344
Cargo.lock
generated
@@ -60,9 +60,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -114,9 +114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.70"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@@ -270,7 +270,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.13",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -306,9 +306,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.12"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f8ccfd9221ee7d1f3d4b33e1f8319b3a81ed8f61f2ea40b37b859794b4491"
|
||||
checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -341,9 +341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e"
|
||||
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -768,6 +768,42 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console-api"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2895653b4d9f1538a83970077cb01dfc77a4810524e51a110944688e916b18e"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
"tonic",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console-subscriber"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ab2224a0311582eb03adba4caaf18644f7b1f10a760803a803b9b605187fc7"
|
||||
dependencies = [
|
||||
"console-api",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-utils",
|
||||
"futures",
|
||||
"hdrhistogram",
|
||||
"humantime 2.1.0",
|
||||
"prost-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thread_local",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tonic",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.2"
|
||||
@@ -1144,7 +1180,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1175,6 +1211,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"mailparse",
|
||||
"mime",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"num_cpus",
|
||||
@@ -1218,7 +1255,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
@@ -1241,10 +1278,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
"console-subscriber",
|
||||
"deltachat",
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -1256,7 +1294,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1276,12 +1314,12 @@ name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.13",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1294,6 +1332,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"yerpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1421,9 +1460,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.0"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
@@ -1440,13 +1479,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2035,9 +2075,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.12.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
|
||||
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
@@ -2056,7 +2096,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.13",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2205,6 +2245,19 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
version = "7.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"byteorder",
|
||||
"flate2",
|
||||
"nom",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -2306,9 +2359,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "human-panic"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6557b29bbdc9d6c7a5cdbe2962e78eaf48115e8d55b0b62282956981c1f605"
|
||||
checksum = "c16465f6227e18e5a64eba488245d7b2974d4db0c4404ca5a69b550defa18d0a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"os_info",
|
||||
@@ -2366,6 +2419,18 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-timeout"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
|
||||
dependencies = [
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-io-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
@@ -2659,9 +2724,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
version = "0.2.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2806,9 +2871,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
@@ -3152,6 +3217,12 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.6.0"
|
||||
@@ -3602,10 +3673,42 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.6.29",
|
||||
"unarray",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qrcodegen"
|
||||
version = "1.8.0"
|
||||
@@ -3644,9 +3747,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.28.1"
|
||||
version = "0.28.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2"
|
||||
checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -3873,13 +3976,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3888,7 +3991,7 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3898,10 +4001,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.16"
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"bytes",
|
||||
@@ -4279,9 +4388,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.159"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -4306,20 +4415,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.159"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
|
||||
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.13",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.95"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
|
||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -4619,9 +4728,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.13"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4751,7 +4860,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.13",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4829,9 +4938,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.27.0"
|
||||
version = "1.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
|
||||
checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
@@ -4843,7 +4952,8 @@ dependencies = [
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.45.0",
|
||||
"tracing",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4858,13 +4968,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
|
||||
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.13",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4894,9 +5004,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.12"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
|
||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@@ -4932,9 +5042,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
||||
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -4978,6 +5088,34 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64 0.21.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-timeout",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
@@ -4986,9 +5124,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand 0.8.5",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -5274,9 +5416,9 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.0"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||
checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"serde",
|
||||
@@ -5502,12 +5644,12 @@ version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_gnullvm 0.42.1",
|
||||
"windows_aarch64_msvc 0.42.1",
|
||||
"windows_i686_gnu 0.42.1",
|
||||
"windows_i686_msvc 0.42.1",
|
||||
"windows_x86_64_gnu 0.42.1",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_gnullvm 0.42.1",
|
||||
"windows_x86_64_msvc 0.42.1",
|
||||
]
|
||||
|
||||
@@ -5517,7 +5659,16 @@ version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.42.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5526,21 +5677,42 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_gnullvm 0.42.1",
|
||||
"windows_aarch64_msvc 0.42.1",
|
||||
"windows_i686_gnu 0.42.1",
|
||||
"windows_i686_msvc 0.42.1",
|
||||
"windows_x86_64_gnu 0.42.1",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_gnullvm 0.42.1",
|
||||
"windows_x86_64_msvc 0.42.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.0",
|
||||
"windows_aarch64_msvc 0.48.0",
|
||||
"windows_i686_gnu 0.48.0",
|
||||
"windows_i686_msvc 0.48.0",
|
||||
"windows_x86_64_gnu 0.48.0",
|
||||
"windows_x86_64_gnullvm 0.48.0",
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.32.0"
|
||||
@@ -5553,6 +5725,12 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.32.0"
|
||||
@@ -5565,6 +5743,12 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.32.0"
|
||||
@@ -5577,6 +5761,12 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.32.0"
|
||||
@@ -5589,12 +5779,24 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.32.0"
|
||||
@@ -5607,6 +5809,12 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
@@ -5674,9 +5882,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yerpc"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6a0257f42e6bdc187f37074723b6094da3502cee21ae517b3c54d2c37d506e7"
|
||||
checksum = "b2c26a804eaa30c1ff1a296dc6dd1a7d7c622750dafcd0d6b2ed5e3c5c3beb22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.65"
|
||||
@@ -48,7 +48,7 @@ encoded-words = { git = "https://github.com/async-email/encoded-words", branch =
|
||||
escaper = "0.1"
|
||||
fast-socks5 = "0.8"
|
||||
futures = "0.3"
|
||||
futures-lite = "1.12.0"
|
||||
futures-lite = "1.13.0"
|
||||
hex = "0.4.0"
|
||||
humansize = "2"
|
||||
image = { version = "0.24.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
@@ -57,6 +57,7 @@ kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2"
|
||||
mailparse = "0.14"
|
||||
mime = "0.3.17"
|
||||
num_cpus = "1.15"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
@@ -68,8 +69,8 @@ pretty_env_logger = { version = "0.4", optional = true }
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = "0.28"
|
||||
rand = "0.8"
|
||||
regex = "1.7"
|
||||
reqwest = { version = "0.11.16", features = ["json"] }
|
||||
regex = "1.8"
|
||||
reqwest = { version = "0.11.17", features = ["json"] }
|
||||
rusqlite = { version = "0.29", features = ["sqlcipher"] }
|
||||
rust-hsluv = "0.1"
|
||||
sanitize-filename = "0.4"
|
||||
@@ -83,11 +84,11 @@ strum_macros = "0.24"
|
||||
tagger = "4.3.4"
|
||||
textwrap = "0.16.0"
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros", "tracing"] }
|
||||
tokio-io-timeout = "1.2.0"
|
||||
tokio-stream = { version = "0.1.11", features = ["fs"] }
|
||||
tokio-stream = { version = "0.1.14", features = ["fs"] }
|
||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||
tokio-util = "0.7.7"
|
||||
tokio-util = "0.7.8"
|
||||
toml = "0.7"
|
||||
trust-dns-resolver = "0.22"
|
||||
url = "2"
|
||||
@@ -96,7 +97,7 @@ uuid = { version = "1", features = ["serde", "v4"] }
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
criterion = { version = "0.4.0", features = ["async_tokio"] }
|
||||
futures-lite = "1.12"
|
||||
futures-lite = "1.13"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||
|
||||
18
RELEASE.md
Normal file
18
RELEASE.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Releasing a new version of DeltaChat core
|
||||
|
||||
For example, to release version 1.116.0 of the core, do the following steps.
|
||||
|
||||
1. Resolve all [blocker issues](https://github.com/deltachat/deltachat-core-rust/labels/blocker).
|
||||
|
||||
2. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
|
||||
|
||||
3. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||
|
||||
4. Commit the changes as `chore(release): prepare for 1.116.0`.
|
||||
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
|
||||
|
||||
5. Tag the release: `git tag -a v1.116.0`.
|
||||
|
||||
6. Push the release tag: `git push origin v1.116.0`.
|
||||
|
||||
7. Create a GitHub release: `gh release create v1.116.0 -n ''`.
|
||||
77
cliff.toml
Normal file
77
cliff.toml
Normal file
@@ -0,0 +1,77 @@
|
||||
# configuration file for git-cliff
|
||||
# see https://git-cliff.org/docs/configuration/
|
||||
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = false
|
||||
# process each line of a commit as an individual commit
|
||||
split_commits = false
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/deltachat/deltachat-core-rust/pull/${2}))"}, # replace pull request / issue numbers
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features / Changes"},
|
||||
{ message = "^fix", group = "Fixes"},
|
||||
{ message = "^api", group = "API-Changes" },
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^test", group = "Tests"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ message = "^build", group = "Build system"},
|
||||
{ message = "^docs", group = "Documentation"},
|
||||
{ message = "^ci", group = "CI"},
|
||||
{ message = ".*", group = "Other"},
|
||||
# { body = ".*security", group = "Security"},
|
||||
]
|
||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||
protect_breaking_commits = true
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
#skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
# limit the number of commits included in the changelog.
|
||||
# limit_commits = 42
|
||||
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}\
|
||||
{% if commit.scope %}{{ commit.scope }}: {% endif %}\
|
||||
{{ commit.message | upper_first }}.\
|
||||
{% for footer in commit.footers %}{% if 'BREAKING CHANGE' in footer.token %}
|
||||
{% raw %} {% endraw %}- {{ footer.value }}\
|
||||
{% endif %}{% endfor %}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
@@ -25,6 +25,7 @@ anyhow = "1"
|
||||
thiserror = "1"
|
||||
rand = "0.8"
|
||||
once_cell = "1.17.0"
|
||||
yerpc = { version = "0.4.4", features = ["anyhow_expose"] }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
|
||||
@@ -25,6 +25,7 @@ typedef struct _dc_event dc_event_t;
|
||||
typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t;
|
||||
typedef struct _dc_backup_provider dc_backup_provider_t;
|
||||
typedef struct _dc_http_response dc_http_response_t;
|
||||
|
||||
// Alias for backwards compatibility, use dc_event_emitter_t instead.
|
||||
typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
@@ -460,8 +461,9 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* If no type is prefixed, the videochat is handled completely in a browser.
|
||||
* - `bot` = Set to "1" if this is a bot.
|
||||
* Prevents adding the "Device messages" and "Saved messages" chats,
|
||||
* adds Auto-Submitted header to outgoing messages
|
||||
* and accepts contact requests automatically (calling dc_accept_chat() is not needed for bots).
|
||||
* adds Auto-Submitted header to outgoing messages,
|
||||
* accepts contact requests automatically (calling dc_accept_chat() is not needed for bots)
|
||||
* and does not cut large incoming text messages.
|
||||
* - `last_msg_id` = database ID of the last message processed by the bot.
|
||||
* This ID and IDs below it are guaranteed not to be returned
|
||||
* by dc_get_next_msgs() and dc_wait_next_msgs().
|
||||
@@ -5127,6 +5129,72 @@ int dc_provider_get_status (const dc_provider_t* prov
|
||||
void dc_provider_unref (dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Return an HTTP(S) GET response.
|
||||
* This function can be used to download remote content for HTML emails.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object to take proxy settings from.
|
||||
* @param url HTTP or HTTPS URL.
|
||||
* @return The response must be released using dc_http_response_unref() after usage.
|
||||
* NULL is returned on errors.
|
||||
*/
|
||||
dc_http_response_t* dc_get_http_response (const dc_context_t* context, const char* url);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_http_response_t
|
||||
*
|
||||
* An object containing an HTTP(S) GET response.
|
||||
* Created by dc_get_http_response().
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns HTTP response MIME type as a string, e.g. "text/plain" or "text/html".
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
|
||||
*/
|
||||
char* dc_http_response_get_mimetype (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Returns HTTP response encoding, e.g. "utf-8".
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
|
||||
*/
|
||||
char* dc_http_response_get_encoding (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Returns HTTP response contents.
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The blob which must be released using dc_str_unref() after usage. NULL is never returned.
|
||||
*/
|
||||
uint8_t* dc_http_response_get_blob (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Returns HTTP response content size.
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The blob size.
|
||||
*/
|
||||
size_t dc_http_response_get_size (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Free an HTTP response object.
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
*/
|
||||
void dc_http_response_unref (const dc_http_response_t* response);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_lot_t
|
||||
*
|
||||
@@ -5604,7 +5672,6 @@ void dc_reactions_unref (dc_reactions_t* reactions);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_jsonrpc_instance_t
|
||||
*
|
||||
@@ -5653,6 +5720,18 @@ void dc_jsonrpc_request(dc_jsonrpc_instance_t* jsonrpc_instance, const char* req
|
||||
*/
|
||||
char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||
|
||||
/**
|
||||
* Make a JSON-RPC call and return a response.
|
||||
*
|
||||
* @memberof dc_jsonrpc_instance_t
|
||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||
* @param method JSON-RPC method name, e.g. `check_email_validity`.
|
||||
* @param params JSON-RPC method parameters, e.g. `["alice@example.org"]`.
|
||||
* @return JSON-RPC response as string, must be freed using dc_str_unref() after usage.
|
||||
* On error, NULL is returned.
|
||||
*/
|
||||
char* dc_jsonrpc_blocking_call(dc_jsonrpc_instance_t* jsonrpc_instance, const char *method, const char *params);
|
||||
|
||||
/**
|
||||
* @class dc_event_emitter_t
|
||||
*
|
||||
|
||||
@@ -31,6 +31,7 @@ use deltachat::ephemeral::Timer as EphemeralTimer;
|
||||
use deltachat::imex::BackupProvider;
|
||||
use deltachat::key::DcKey;
|
||||
use deltachat::message::MsgId;
|
||||
use deltachat::net::read_url_blob;
|
||||
use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
|
||||
use deltachat::reaction::{get_msg_reactions, send_reaction, Reactions};
|
||||
use deltachat::stock_str::StockMessage;
|
||||
@@ -4572,6 +4573,96 @@ pub unsafe extern "C" fn dc_provider_unref(provider: *mut dc_provider_t) {
|
||||
// this may change once we start localizing string.
|
||||
}
|
||||
|
||||
// dc_http_response_t
|
||||
|
||||
pub type dc_http_response_t = net::HttpResponse;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_http_response(
|
||||
context: *const dc_context_t,
|
||||
url: *const libc::c_char,
|
||||
) -> *mut dc_http_response_t {
|
||||
if context.is_null() || url.is_null() {
|
||||
eprintln!("ignoring careless call to dc_get_http_response()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let context = &*context;
|
||||
let url = to_string_lossy(url);
|
||||
if let Ok(response) = block_on(read_url_blob(context, &url))
|
||||
.context("read_url_blob")
|
||||
.log_err(context)
|
||||
{
|
||||
Box::into_raw(Box::new(response))
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_http_response_get_mimetype(
|
||||
response: *const dc_http_response_t,
|
||||
) -> *mut libc::c_char {
|
||||
if response.is_null() {
|
||||
eprintln!("ignoring careless call to dc_http_response_get_mimetype()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let response = &*response;
|
||||
response.mimetype.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_http_response_get_encoding(
|
||||
response: *const dc_http_response_t,
|
||||
) -> *mut libc::c_char {
|
||||
if response.is_null() {
|
||||
eprintln!("ignoring careless call to dc_http_response_get_encoding()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let response = &*response;
|
||||
response.encoding.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_http_response_get_blob(
|
||||
response: *const dc_http_response_t,
|
||||
) -> *mut libc::c_char {
|
||||
if response.is_null() {
|
||||
eprintln!("ignoring careless call to dc_http_response_get_blob()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let response = &*response;
|
||||
let blob_len = response.blob.len();
|
||||
let ptr = libc::malloc(blob_len);
|
||||
libc::memcpy(ptr, response.blob.as_ptr() as *mut libc::c_void, blob_len);
|
||||
ptr as *mut libc::c_char
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_http_response_get_size(
|
||||
response: *const dc_http_response_t,
|
||||
) -> libc::size_t {
|
||||
if response.is_null() {
|
||||
eprintln!("ignoring careless call to dc_http_response_get_size()");
|
||||
return 0;
|
||||
}
|
||||
|
||||
let response = &*response;
|
||||
response.blob.len()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_http_response_unref(response: *mut dc_http_response_t) {
|
||||
if response.is_null() {
|
||||
eprintln!("ignoring careless call to dc_http_response_unref()");
|
||||
return;
|
||||
}
|
||||
drop(Box::from_raw(response));
|
||||
}
|
||||
|
||||
// -- Accounts
|
||||
|
||||
/// Reader-writer lock wrapper for accounts manager to guarantee thread safety when using
|
||||
@@ -4876,15 +4967,13 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
|
||||
#[cfg(feature = "jsonrpc")]
|
||||
mod jsonrpc {
|
||||
use deltachat_jsonrpc::api::CommandApi;
|
||||
use deltachat_jsonrpc::events::event_to_json_rpc_notification;
|
||||
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcSession};
|
||||
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcServer, RpcSession};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct dc_jsonrpc_instance_t {
|
||||
receiver: OutReceiver,
|
||||
handle: RpcSession<CommandApi>,
|
||||
event_thread: JoinHandle<Result<(), anyhow::Error>>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -4897,28 +4986,12 @@ mod jsonrpc {
|
||||
}
|
||||
|
||||
let account_manager = &*account_manager;
|
||||
let events = block_on(account_manager.read()).get_event_emitter();
|
||||
let cmd_api = deltachat_jsonrpc::api::CommandApi::from_arc(account_manager.inner.clone());
|
||||
|
||||
let (request_handle, receiver) = RpcClient::new();
|
||||
let handle = RpcSession::new(request_handle.clone(), cmd_api);
|
||||
let handle = RpcSession::new(request_handle, cmd_api);
|
||||
|
||||
let event_thread = spawn(async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
let event = event_to_json_rpc_notification(event);
|
||||
request_handle
|
||||
.send_notification("event", Some(event))
|
||||
.await?;
|
||||
}
|
||||
let res: Result<(), anyhow::Error> = Ok(());
|
||||
res
|
||||
});
|
||||
|
||||
let instance = dc_jsonrpc_instance_t {
|
||||
receiver,
|
||||
handle,
|
||||
event_thread,
|
||||
};
|
||||
let instance = dc_jsonrpc_instance_t { receiver, handle };
|
||||
|
||||
Box::into_raw(Box::new(instance))
|
||||
}
|
||||
@@ -4929,7 +5002,6 @@ mod jsonrpc {
|
||||
eprintln!("ignoring careless call to dc_jsonrpc_unref()");
|
||||
return;
|
||||
}
|
||||
(*jsonrpc_instance).event_thread.abort();
|
||||
drop(Box::from_raw(jsonrpc_instance));
|
||||
}
|
||||
|
||||
@@ -4967,4 +5039,29 @@ mod jsonrpc {
|
||||
.map(|result| serde_json::to_string(&result).unwrap_or_default().strdup())
|
||||
.unwrap_or(ptr::null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_jsonrpc_blocking_call(
|
||||
jsonrpc_instance: *mut dc_jsonrpc_instance_t,
|
||||
method: *const libc::c_char,
|
||||
params: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
if jsonrpc_instance.is_null() {
|
||||
eprintln!("ignoring careless call to dc_jsonrpc_blocking_call()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let api = &*jsonrpc_instance;
|
||||
let method = to_string_lossy(method);
|
||||
let params = to_string_lossy(params);
|
||||
let params: Option<yerpc::Params> = match serde_json::from_str(¶ms) {
|
||||
Ok(params) => Some(params),
|
||||
Err(_) => None,
|
||||
};
|
||||
let params = params.map(yerpc::Params::into_value).unwrap_or_default();
|
||||
let res = block_on(api.handle.server().handle_request(method, params));
|
||||
match res {
|
||||
Ok(res) => res.to_string().strdup(),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
@@ -20,20 +20,20 @@ tempfile = "3.3.0"
|
||||
log = "0.4"
|
||||
async-channel = { version = "1.8.0" }
|
||||
futures = { version = "0.3.28" }
|
||||
serde_json = "1.0.95"
|
||||
yerpc = { version = "0.4.3", features = ["anyhow_expose"] }
|
||||
serde_json = "1.0.96"
|
||||
yerpc = { version = "0.4.4", features = ["anyhow_expose"] }
|
||||
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
|
||||
tokio = { version = "1.27.0" }
|
||||
tokio = { version = "1.28.0" }
|
||||
sanitize-filename = "0.4"
|
||||
walkdir = "2.3.3"
|
||||
base64 = "0.21"
|
||||
|
||||
# optional dependencies
|
||||
axum = { version = "0.6.12", optional = true, features = ["ws"] }
|
||||
axum = { version = "0.6.18", optional = true, features = ["ws"] }
|
||||
env_logger = { version = "0.10.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.27.0", features = ["full", "rt-multi-thread"] }
|
||||
tokio = { version = "1.28.0", features = ["full", "rt-multi-thread"] }
|
||||
|
||||
|
||||
[features]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
pub use deltachat::accounts::Accounts;
|
||||
use deltachat::message::get_msg_read_receipts;
|
||||
use deltachat::qr::Qr;
|
||||
use deltachat::{
|
||||
chat::{
|
||||
@@ -24,7 +25,7 @@ use deltachat::{
|
||||
provider::get_provider_info,
|
||||
qr,
|
||||
qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg},
|
||||
reaction::send_reaction,
|
||||
reaction::{get_msg_reactions, send_reaction},
|
||||
securejoin,
|
||||
stock_str::StockMessage,
|
||||
webxdc::StatusUpdateSerial,
|
||||
@@ -35,17 +36,17 @@ use tokio::sync::{watch, Mutex, RwLock};
|
||||
use walkdir::WalkDir;
|
||||
use yerpc::rpc;
|
||||
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
use types::account::Account;
|
||||
use types::chat::FullChat;
|
||||
use types::chat_list::ChatListEntry;
|
||||
use types::contact::ContactObject;
|
||||
use types::message::MessageData;
|
||||
use types::message::MessageObject;
|
||||
use types::events::Event;
|
||||
use types::http::HttpResponse;
|
||||
use types::message::{MessageData, MessageObject, MessageReadReceipt};
|
||||
use types::provider_info::ProviderInfo;
|
||||
use types::reactions::JSONRPCReactions;
|
||||
use types::webxdc::WebxdcMessageInfo;
|
||||
|
||||
use self::types::message::MessageLoadResult;
|
||||
@@ -164,6 +165,16 @@ impl CommandApi {
|
||||
get_info()
|
||||
}
|
||||
|
||||
/// Get the next event.
|
||||
async fn get_next_event(&self) -> Result<Event> {
|
||||
let event_emitter = self.accounts.read().await.get_event_emitter();
|
||||
event_emitter
|
||||
.recv()
|
||||
.await
|
||||
.map(|event| event.into())
|
||||
.context("event channel is closed")
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// Account Management
|
||||
// ---------------------------------------------
|
||||
@@ -205,8 +216,6 @@ impl CommandApi {
|
||||
let context_option = self.accounts.read().await.get_account(id);
|
||||
if let Some(ctx) = context_option {
|
||||
accounts.push(Account::from_context(&ctx, id).await?)
|
||||
} else {
|
||||
println!("account with id {id} doesn't exist anymore");
|
||||
}
|
||||
}
|
||||
Ok(accounts)
|
||||
@@ -539,7 +548,7 @@ impl CommandApi {
|
||||
list_flags: Option<u32>,
|
||||
query_string: Option<String>,
|
||||
query_contact_id: Option<u32>,
|
||||
) -> Result<Vec<ChatListEntry>> {
|
||||
) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let list = Chatlist::try_load(
|
||||
&ctx,
|
||||
@@ -548,12 +557,9 @@ impl CommandApi {
|
||||
query_contact_id.map(ContactId::new),
|
||||
)
|
||||
.await?;
|
||||
let mut l: Vec<ChatListEntry> = Vec::with_capacity(list.len());
|
||||
let mut l: Vec<u32> = Vec::with_capacity(list.len());
|
||||
for i in 0..list.len() {
|
||||
l.push(ChatListEntry(
|
||||
list.get_chat_id(i)?.to_u32(),
|
||||
list.get_msg_id(i)?.unwrap_or_default().to_u32(),
|
||||
));
|
||||
l.push(list.get_chat_id(i)?.to_u32());
|
||||
}
|
||||
Ok(l)
|
||||
}
|
||||
@@ -561,19 +567,18 @@ impl CommandApi {
|
||||
async fn get_chatlist_items_by_entries(
|
||||
&self,
|
||||
account_id: u32,
|
||||
entries: Vec<ChatListEntry>,
|
||||
entries: Vec<u32>,
|
||||
) -> Result<HashMap<u32, ChatListItemFetchResult>> {
|
||||
// todo custom json deserializer for ChatListEntry?
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut result: HashMap<u32, ChatListItemFetchResult> =
|
||||
HashMap::with_capacity(entries.len());
|
||||
for entry in entries.iter() {
|
||||
for &entry in entries.iter() {
|
||||
result.insert(
|
||||
entry.0,
|
||||
entry,
|
||||
match get_chat_list_item_by_id(&ctx, entry).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => ChatListItemFetchResult::Error {
|
||||
id: entry.0,
|
||||
id: entry,
|
||||
error: format!("{err:#}"),
|
||||
},
|
||||
},
|
||||
@@ -1113,6 +1118,24 @@ impl CommandApi {
|
||||
get_msg_info(&ctx, MsgId::new(message_id)).await
|
||||
}
|
||||
|
||||
/// Returns contacts that sent read receipts and the time of reading.
|
||||
async fn get_message_read_receipts(
|
||||
&self,
|
||||
account_id: u32,
|
||||
message_id: u32,
|
||||
) -> Result<Vec<MessageReadReceipt>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let receipts = get_msg_read_receipts(&ctx, MsgId::new(message_id))
|
||||
.await?
|
||||
.iter()
|
||||
.map(|(contact_id, ts)| MessageReadReceipt {
|
||||
contact_id: contact_id.to_u32(),
|
||||
timestamp: *ts,
|
||||
})
|
||||
.collect();
|
||||
Ok(receipts)
|
||||
}
|
||||
|
||||
/// Asks the core to start downloading a message fully.
|
||||
/// This function is typically called when the user hits the "Download" button
|
||||
/// that is shown by the UI in case `download_state` is `'Available'` or `'Failure'`
|
||||
@@ -1658,6 +1681,15 @@ impl CommandApi {
|
||||
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
|
||||
}
|
||||
|
||||
/// Makes an HTTP GET request and returns a response.
|
||||
///
|
||||
/// `url` is the HTTP or HTTPS URL.
|
||||
async fn get_http_response(&self, account_id: u32, url: String) -> Result<HttpResponse> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let response = deltachat::net::read_url_blob(&ctx, &url).await?.into();
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Forward messages to another chat.
|
||||
///
|
||||
/// All types of messages can be forwarded,
|
||||
@@ -1707,6 +1739,21 @@ impl CommandApi {
|
||||
Ok(message_id.to_u32())
|
||||
}
|
||||
|
||||
/// Returns reactions to the message.
|
||||
async fn get_message_reactions(
|
||||
&self,
|
||||
account_id: u32,
|
||||
message_id: u32,
|
||||
) -> Result<Option<JSONRPCReactions>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let reactions = get_msg_reactions(&ctx, MsgId::new(message_id)).await?;
|
||||
if reactions.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(reactions.into()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut message = Message::new(if let Some(viewtype) = data.viewtype {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
@@ -53,7 +53,9 @@ impl FullChat {
|
||||
contacts.push(
|
||||
ContactObject::try_from_dc_contact(
|
||||
context,
|
||||
Contact::load_from_db(context, *contact_id).await?,
|
||||
Contact::load_from_db(context, *contact_id)
|
||||
.await
|
||||
.context("failed to load contact")?,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -73,7 +75,8 @@ impl FullChat {
|
||||
let was_seen_recently = if chat.get_type() == Chattype::Single {
|
||||
match contact_ids.get(0) {
|
||||
Some(contact) => Contact::load_from_db(context, *contact)
|
||||
.await?
|
||||
.await
|
||||
.context("failed to load contact for was_seen_recently")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
}
|
||||
@@ -89,10 +92,7 @@ impl FullChat {
|
||||
is_protected: chat.is_protected(),
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
chat_type: chat
|
||||
.get_type()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
is_unpromoted: chat.is_unpromoted(),
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
contacts,
|
||||
@@ -155,10 +155,7 @@ impl BasicChat {
|
||||
is_protected: chat.is_protected(),
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
chat_type: chat
|
||||
.get_type()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
is_unpromoted: chat.is_unpromoted(),
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
color,
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::chatlist::get_last_message_for_chat;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use deltachat::{
|
||||
chat::{get_chat_contacts, ChatVisibility},
|
||||
chatlist::Chatlist,
|
||||
};
|
||||
use deltachat::{
|
||||
chat::{Chat, ChatId},
|
||||
message::MsgId,
|
||||
};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Deserialize, Serialize, TypeDef)]
|
||||
pub struct ChatListEntry(pub u32, pub u32);
|
||||
use super::message::MessageViewtype;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(tag = "type")]
|
||||
@@ -31,6 +27,8 @@ pub enum ChatListItemFetchResult {
|
||||
summary_text1: String,
|
||||
summary_text2: String,
|
||||
summary_status: u32,
|
||||
/// showing preview if last chat message is image
|
||||
summary_preview_image: Option<String>,
|
||||
is_protected: bool,
|
||||
is_group: bool,
|
||||
fresh_message_counter: usize,
|
||||
@@ -47,6 +45,8 @@ pub enum ChatListItemFetchResult {
|
||||
/// contact id if this is a dm chat (for view profile entry in context menu)
|
||||
dm_chat_contact: Option<u32>,
|
||||
was_seen_recently: bool,
|
||||
last_message_type: Option<MessageViewtype>,
|
||||
last_message_id: Option<u32>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ArchiveLink { fresh_message_counter: usize },
|
||||
@@ -56,14 +56,9 @@ pub enum ChatListItemFetchResult {
|
||||
|
||||
pub(crate) async fn get_chat_list_item_by_id(
|
||||
ctx: &deltachat::context::Context,
|
||||
entry: &ChatListEntry,
|
||||
entry: u32,
|
||||
) -> Result<ChatListItemFetchResult> {
|
||||
let chat_id = ChatId::new(entry.0);
|
||||
let last_msgid = match entry.1 {
|
||||
0 => None,
|
||||
_ => Some(MsgId::new(entry.1)),
|
||||
};
|
||||
|
||||
let chat_id = ChatId::new(entry);
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
|
||||
if chat_id.is_archived_link() {
|
||||
@@ -72,12 +67,18 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
});
|
||||
}
|
||||
|
||||
let chat = Chat::load_from_db(ctx, chat_id).await?;
|
||||
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat)).await?;
|
||||
let last_msgid = get_last_message_for_chat(ctx, chat_id).await?;
|
||||
|
||||
let chat = Chat::load_from_db(ctx, chat_id).await.context("chat")?;
|
||||
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat))
|
||||
.await
|
||||
.context("summary")?;
|
||||
|
||||
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
|
||||
let summary_text2 = summary.text.to_owned();
|
||||
|
||||
let summary_preview_image = summary.thumbnail_path;
|
||||
|
||||
let visibility = chat.get_visibility();
|
||||
|
||||
let avatar_path = chat
|
||||
@@ -85,12 +86,15 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
.await?
|
||||
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
|
||||
|
||||
let last_updated = match last_msgid {
|
||||
let (last_updated, message_type) = match last_msgid {
|
||||
Some(id) => {
|
||||
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
|
||||
Some(last_message.get_timestamp() * 1000)
|
||||
(
|
||||
Some(last_message.get_timestamp() * 1000),
|
||||
Some(last_message.get_viewtype().into()),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
|
||||
@@ -101,7 +105,8 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
let contact = chat_contacts.get(0);
|
||||
let was_seen_recently = match contact {
|
||||
Some(contact) => Contact::load_from_db(ctx, *contact)
|
||||
.await?
|
||||
.await
|
||||
.context("contact")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
};
|
||||
@@ -124,6 +129,7 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
summary_text1,
|
||||
summary_text2,
|
||||
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
|
||||
summary_preview_image,
|
||||
is_protected: chat.is_protected(),
|
||||
is_group: chat.get_type() == Chattype::Group,
|
||||
fresh_message_counter,
|
||||
@@ -138,5 +144,7 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
is_broadcast: chat.get_type() == Chattype::Broadcast,
|
||||
dm_chat_contact,
|
||||
was_seen_recently,
|
||||
last_message_type: message_type,
|
||||
last_message_id: last_msgid.map(|id| id.to_u32()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
use deltachat::{Event, EventType};
|
||||
use deltachat::{Event as CoreEvent, EventType as CoreEventType};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
pub fn event_to_json_rpc_notification(event: Event) -> Value {
|
||||
let id: JSONRPCEventType = event.typ.into();
|
||||
json!({
|
||||
"event": id,
|
||||
"contextId": event.id,
|
||||
})
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Event {
|
||||
/// Event payload.
|
||||
event: EventType,
|
||||
|
||||
/// Account ID.
|
||||
context_id: u32,
|
||||
}
|
||||
|
||||
impl From<CoreEvent> for Event {
|
||||
fn from(event: CoreEvent) -> Self {
|
||||
Event {
|
||||
event: event.typ.into(),
|
||||
context_id: event.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(tag = "type", rename = "Event")]
|
||||
pub enum JSONRPCEventType {
|
||||
#[serde(tag = "type")]
|
||||
pub enum EventType {
|
||||
/// The library-user may write an informational string to the log.
|
||||
///
|
||||
/// This event should *not* be reported to the end-user using a popup or something like
|
||||
@@ -286,27 +296,27 @@ pub enum JSONRPCEventType {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<EventType> for JSONRPCEventType {
|
||||
fn from(event: EventType) -> Self {
|
||||
use JSONRPCEventType::*;
|
||||
impl From<CoreEventType> for EventType {
|
||||
fn from(event: CoreEventType) -> Self {
|
||||
use EventType::*;
|
||||
match event {
|
||||
EventType::Info(msg) => Info { msg },
|
||||
EventType::SmtpConnected(msg) => SmtpConnected { msg },
|
||||
EventType::ImapConnected(msg) => ImapConnected { msg },
|
||||
EventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
|
||||
EventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
|
||||
EventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
|
||||
EventType::ImapInboxIdle => ImapInboxIdle,
|
||||
EventType::NewBlobFile(file) => NewBlobFile { file },
|
||||
EventType::DeletedBlobFile(file) => DeletedBlobFile { file },
|
||||
EventType::Warning(msg) => Warning { msg },
|
||||
EventType::Error(msg) => Error { msg },
|
||||
EventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
|
||||
EventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
|
||||
CoreEventType::Info(msg) => Info { msg },
|
||||
CoreEventType::SmtpConnected(msg) => SmtpConnected { msg },
|
||||
CoreEventType::ImapConnected(msg) => ImapConnected { msg },
|
||||
CoreEventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
|
||||
CoreEventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
|
||||
CoreEventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
|
||||
CoreEventType::ImapInboxIdle => ImapInboxIdle,
|
||||
CoreEventType::NewBlobFile(file) => NewBlobFile { file },
|
||||
CoreEventType::DeletedBlobFile(file) => DeletedBlobFile { file },
|
||||
CoreEventType::Warning(msg) => Warning { msg },
|
||||
CoreEventType::Error(msg) => Error { msg },
|
||||
CoreEventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
|
||||
CoreEventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::ReactionsChanged {
|
||||
CoreEventType::ReactionsChanged {
|
||||
chat_id,
|
||||
msg_id,
|
||||
contact_id,
|
||||
@@ -315,92 +325,76 @@ impl From<EventType> for JSONRPCEventType {
|
||||
msg_id: msg_id.to_u32(),
|
||||
contact_id: contact_id.to_u32(),
|
||||
},
|
||||
EventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
|
||||
CoreEventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
|
||||
msg_ids: msg_ids.into_iter().map(|id| id.to_u32()).collect(),
|
||||
},
|
||||
EventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||
CoreEventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
EventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
||||
CoreEventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::MsgFailed { chat_id, msg_id } => MsgFailed {
|
||||
CoreEventType::MsgFailed { chat_id, msg_id } => MsgFailed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::MsgRead { chat_id, msg_id } => MsgRead {
|
||||
CoreEventType::MsgRead { chat_id, msg_id } => MsgRead {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::ChatModified(chat_id) => ChatModified {
|
||||
CoreEventType::ChatModified(chat_id) => ChatModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
EventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||
CoreEventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||
ChatEphemeralTimerModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
timer: timer.to_u32(),
|
||||
}
|
||||
}
|
||||
EventType::ContactsChanged(contact) => ContactsChanged {
|
||||
CoreEventType::ContactsChanged(contact) => ContactsChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
EventType::LocationChanged(contact) => LocationChanged {
|
||||
CoreEventType::LocationChanged(contact) => LocationChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
EventType::ConfigureProgress { progress, comment } => {
|
||||
CoreEventType::ConfigureProgress { progress, comment } => {
|
||||
ConfigureProgress { progress, comment }
|
||||
}
|
||||
EventType::ImexProgress(progress) => ImexProgress { progress },
|
||||
EventType::ImexFileWritten(path) => ImexFileWritten {
|
||||
CoreEventType::ImexProgress(progress) => ImexProgress { progress },
|
||||
CoreEventType::ImexFileWritten(path) => ImexFileWritten {
|
||||
path: path.to_str().unwrap_or_default().to_owned(),
|
||||
},
|
||||
EventType::SecurejoinInviterProgress {
|
||||
CoreEventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinInviterProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
EventType::SecurejoinJoinerProgress {
|
||||
CoreEventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinJoinerProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
EventType::ConnectivityChanged => ConnectivityChanged,
|
||||
EventType::SelfavatarChanged => SelfavatarChanged,
|
||||
EventType::WebxdcStatusUpdate {
|
||||
CoreEventType::ConnectivityChanged => ConnectivityChanged,
|
||||
CoreEventType::SelfavatarChanged => SelfavatarChanged,
|
||||
CoreEventType::WebxdcStatusUpdate {
|
||||
msg_id,
|
||||
status_update_serial,
|
||||
} => WebxdcStatusUpdate {
|
||||
msg_id: msg_id.to_u32(),
|
||||
status_update_serial: status_update_serial.to_u32(),
|
||||
},
|
||||
EventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn generate_events_ts_types_definition() {
|
||||
let events = {
|
||||
let mut buf = Vec::new();
|
||||
let options = typescript_type_def::DefinitionFileOptions {
|
||||
root_namespace: None,
|
||||
..typescript_type_def::DefinitionFileOptions::default()
|
||||
};
|
||||
typescript_type_def::write_definition_file::<_, JSONRPCEventType>(&mut buf, options)
|
||||
.unwrap();
|
||||
String::from_utf8(buf).unwrap()
|
||||
};
|
||||
std::fs::write("typescript/generated/events.ts", events).unwrap();
|
||||
}
|
||||
29
deltachat-jsonrpc/src/api/types/http.rs
Normal file
29
deltachat-jsonrpc/src/api/types/http.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use deltachat::net::HttpResponse as CoreHttpResponse;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
pub struct HttpResponse {
|
||||
/// base64-encoded response body.
|
||||
blob: String,
|
||||
|
||||
/// MIME type, e.g. "text/plain" or "text/html".
|
||||
mimetype: Option<String>,
|
||||
|
||||
/// Encoding, e.g. "utf-8".
|
||||
encoding: Option<String>,
|
||||
}
|
||||
|
||||
impl From<CoreHttpResponse> for HttpResponse {
|
||||
fn from(response: CoreHttpResponse) -> Self {
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
let blob = general_purpose::STANDARD_NO_PAD.encode(response.blob);
|
||||
let mimetype = response.mimetype;
|
||||
let encoding = response.encoding;
|
||||
HttpResponse {
|
||||
blob,
|
||||
mimetype,
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Context as _, Result};
|
||||
use deltachat::chat::Chat;
|
||||
use deltachat::chat::ChatItem;
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::chat::ChatVisibility;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::download;
|
||||
@@ -114,8 +114,12 @@ impl MessageObject {
|
||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
||||
let message = Message::load_from_db(context, msg_id).await?;
|
||||
|
||||
let sender_contact = Contact::load_from_db(context, message.get_from_id()).await?;
|
||||
let sender = ContactObject::try_from_dc_contact(context, sender_contact).await?;
|
||||
let sender_contact = Contact::load_from_db(context, message.get_from_id())
|
||||
.await
|
||||
.context("failed to load sender contact")?;
|
||||
let sender = ContactObject::try_from_dc_contact(context, sender_contact)
|
||||
.await
|
||||
.context("failed to load sender contact object")?;
|
||||
let file_bytes = message.get_filebytes(context).await?.unwrap_or_default();
|
||||
let override_sender_name = message.get_override_sender_name();
|
||||
|
||||
@@ -132,7 +136,9 @@ impl MessageObject {
|
||||
let quote = if let Some(quoted_text) = message.quoted_text() {
|
||||
match message.quoted_message(context).await? {
|
||||
Some(quote) => {
|
||||
let quote_author = Contact::load_from_db(context, quote.get_from_id()).await?;
|
||||
let quote_author = Contact::load_from_db(context, quote.get_from_id())
|
||||
.await
|
||||
.context("failed to load quote author contact")?;
|
||||
Some(MessageQuote::WithMessage {
|
||||
text: quoted_text,
|
||||
message_id: quote.get_id().to_u32(),
|
||||
@@ -160,7 +166,9 @@ impl MessageObject {
|
||||
None
|
||||
};
|
||||
|
||||
let reactions = get_msg_reactions(context, msg_id).await?;
|
||||
let reactions = get_msg_reactions(context, msg_id)
|
||||
.await
|
||||
.context("failed to load message reactions")?;
|
||||
let reactions = if reactions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -180,7 +188,7 @@ impl MessageObject {
|
||||
state: message
|
||||
.get_state()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
||||
.context("state conversion to number failed")?,
|
||||
error: message.error(),
|
||||
|
||||
timestamp: message.get_timestamp(),
|
||||
@@ -203,7 +211,7 @@ impl MessageObject {
|
||||
videochat_type: match message.get_videochat_type() {
|
||||
Some(vct) => Some(
|
||||
vct.to_u32()
|
||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
||||
.context("videochat type conversion to number failed")?,
|
||||
),
|
||||
None => None,
|
||||
},
|
||||
@@ -443,9 +451,17 @@ impl MessageNotificationInfo {
|
||||
pub struct MessageSearchResult {
|
||||
id: u32,
|
||||
author_profile_image: Option<String>,
|
||||
/// if sender name if overridden it will show it as ~alias
|
||||
author_name: String,
|
||||
author_color: String,
|
||||
chat_name: Option<String>,
|
||||
author_id: u32,
|
||||
chat_profile_image: Option<String>,
|
||||
chat_color: String,
|
||||
chat_name: String,
|
||||
chat_type: u32,
|
||||
is_chat_protected: bool,
|
||||
is_chat_contact_request: bool,
|
||||
is_chat_archived: bool,
|
||||
message: String,
|
||||
timestamp: i64,
|
||||
}
|
||||
@@ -460,17 +476,31 @@ impl MessageSearchResult {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let chat_profile_image = match chat.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let author_name = if let Some(name) = message.get_override_sender_name() {
|
||||
format!("~{name}")
|
||||
} else {
|
||||
sender.get_display_name().to_owned()
|
||||
};
|
||||
let chat_color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||
|
||||
Ok(Self {
|
||||
id: msg_id.to_u32(),
|
||||
author_profile_image: profile_image,
|
||||
author_name: sender.get_display_name().to_owned(),
|
||||
author_name,
|
||||
author_color: color_int_to_hex_string(sender.get_color()),
|
||||
chat_name: if chat.get_type() == Chattype::Single {
|
||||
Some(chat.get_name().to_owned())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
author_id: sender.id.to_u32(),
|
||||
chat_name: chat.get_name().to_owned(),
|
||||
chat_color,
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
chat_profile_image,
|
||||
is_chat_protected: chat.is_protected(),
|
||||
is_chat_contact_request: chat.is_contact_request(),
|
||||
is_chat_archived: chat.get_visibility() == ChatVisibility::Archived,
|
||||
message: message.get_text().unwrap_or_default(),
|
||||
timestamp: message.get_timestamp(),
|
||||
})
|
||||
@@ -514,3 +544,10 @@ pub struct MessageData {
|
||||
pub override_sender_name: Option<String>,
|
||||
pub quoted_message_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageReadReceipt {
|
||||
pub contact_id: u32,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ pub mod account;
|
||||
pub mod chat;
|
||||
pub mod chat_list;
|
||||
pub mod contact;
|
||||
pub mod events;
|
||||
pub mod http;
|
||||
pub mod location;
|
||||
pub mod message;
|
||||
pub mod provider_info;
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use deltachat::contact::ContactId;
|
||||
use deltachat::reaction::Reactions;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
/// A single reaction emoji.
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Reaction", rename_all = "camelCase")]
|
||||
pub struct JSONRPCReaction {
|
||||
/// Emoji.
|
||||
emoji: String,
|
||||
|
||||
/// Emoji frequency.
|
||||
count: usize,
|
||||
|
||||
/// True if we reacted with this emoji.
|
||||
is_from_self: bool,
|
||||
}
|
||||
|
||||
/// Structure representing all reactions to a particular message.
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Reactions", rename_all = "camelCase")]
|
||||
pub struct JSONRPCReactions {
|
||||
/// Map from a contact to it's reaction to message.
|
||||
reactions_by_contact: BTreeMap<u32, Vec<String>>,
|
||||
/// Unique reactions and their count
|
||||
reactions: BTreeMap<String, u32>,
|
||||
/// Unique reactions and their count, sorted in descending order.
|
||||
reactions: Vec<JSONRPCReaction>,
|
||||
}
|
||||
|
||||
impl From<Reactions> for JSONRPCReactions {
|
||||
fn from(reactions: Reactions) -> Self {
|
||||
let mut reactions_by_contact: BTreeMap<u32, Vec<String>> = BTreeMap::new();
|
||||
let mut unique_reactions: BTreeMap<String, u32> = BTreeMap::new();
|
||||
|
||||
for contact_id in reactions.contacts() {
|
||||
let reaction = reactions.get(contact_id);
|
||||
@@ -30,18 +44,29 @@ impl From<Reactions> for JSONRPCReactions {
|
||||
.map(|emoji| emoji.to_owned())
|
||||
.collect();
|
||||
reactions_by_contact.insert(contact_id.to_u32(), emojis.clone());
|
||||
for emoji in emojis {
|
||||
if let Some(x) = unique_reactions.get_mut(&emoji) {
|
||||
*x += 1;
|
||||
} else {
|
||||
unique_reactions.insert(emoji, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let self_reactions = reactions_by_contact.get(&ContactId::SELF.to_u32());
|
||||
|
||||
let mut reactions_v = Vec::new();
|
||||
for (emoji, count) in reactions.emoji_sorted_by_frequency() {
|
||||
let is_from_self = if let Some(self_reactions) = self_reactions {
|
||||
self_reactions.contains(&emoji)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let reaction = JSONRPCReaction {
|
||||
emoji,
|
||||
count,
|
||||
is_from_self,
|
||||
};
|
||||
reactions_v.push(reaction)
|
||||
}
|
||||
|
||||
JSONRPCReactions {
|
||||
reactions_by_contact,
|
||||
reactions: unique_reactions,
|
||||
reactions: reactions_v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod api;
|
||||
pub use api::events;
|
||||
pub use yerpc;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -6,7 +6,6 @@ use yerpc::axum::handle_ws_rpc;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
mod api;
|
||||
use api::events::event_to_json_rpc_notification;
|
||||
use api::{Accounts, CommandApi};
|
||||
|
||||
const DEFAULT_PORT: u16 = 20808;
|
||||
@@ -44,12 +43,5 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
async fn handler(ws: WebSocketUpgrade, Extension(api): Extension<CommandApi>) -> Response {
|
||||
let (client, out_receiver) = RpcClient::new();
|
||||
let session = RpcSession::new(client.clone(), api.clone());
|
||||
tokio::spawn(async move {
|
||||
let events = api.accounts.read().await.get_event_emitter();
|
||||
while let Some(event) = events.recv().await {
|
||||
let event = event_to_json_rpc_notification(event);
|
||||
client.send_notification("event", Some(event)).await.ok();
|
||||
}
|
||||
});
|
||||
handle_ws_rpc(ws, out_receiver, session).await
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ async function run() {
|
||||
null,
|
||||
null
|
||||
);
|
||||
for (const [chatId, _messageId] of chats) {
|
||||
for (const chatId of chats) {
|
||||
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
|
||||
write($main, `<h3>${chat.name}</h3>`);
|
||||
const messageIds = await client.rpc.getMessageIds(
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.113.0"
|
||||
"version": "1.115.0"
|
||||
}
|
||||
|
||||
@@ -1,34 +1,28 @@
|
||||
import * as T from "../generated/types.js";
|
||||
import { EventType } from "../generated/types.js";
|
||||
import * as RPC from "../generated/jsonrpc.js";
|
||||
import { RawClient } from "../generated/client.js";
|
||||
import { Event } from "../generated/events.js";
|
||||
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
||||
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
||||
|
||||
type DCWireEvent<T extends Event> = {
|
||||
event: T;
|
||||
contextId: number;
|
||||
};
|
||||
// export type Events = Record<
|
||||
// Event["type"] | "ALL",
|
||||
// (event: DeltaChatEvent<Event>) => void
|
||||
// >;
|
||||
|
||||
type Events = { ALL: (accountId: number, event: Event) => void } & {
|
||||
[Property in Event["type"]]: (
|
||||
type Events = { ALL: (accountId: number, event: EventType) => void } & {
|
||||
[Property in EventType["type"]]: (
|
||||
accountId: number,
|
||||
event: Extract<Event, { type: Property }>
|
||||
event: Extract<EventType, { type: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
type ContextEvents = { ALL: (event: Event) => void } & {
|
||||
[Property in Event["type"]]: (
|
||||
event: Extract<Event, { type: Property }>
|
||||
type ContextEvents = { ALL: (event: EventType) => void } & {
|
||||
[Property in EventType["type"]]: (
|
||||
event: Extract<EventType, { type: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type DcEvent = Event;
|
||||
export type DcEventType<T extends Event["type"]> = Extract<Event, { type: T }>;
|
||||
export type DcEvent = EventType;
|
||||
export type DcEventType<T extends EventType["type"]> = Extract<
|
||||
EventType,
|
||||
{ type: T }
|
||||
>;
|
||||
|
||||
export class BaseDeltaChat<
|
||||
Transport extends BaseTransport<any>
|
||||
@@ -36,27 +30,34 @@ export class BaseDeltaChat<
|
||||
rpc: RawClient;
|
||||
account?: T.Account;
|
||||
private contextEmitters: { [key: number]: TinyEmitter<ContextEvents> } = {};
|
||||
constructor(public transport: Transport) {
|
||||
|
||||
//@ts-ignore
|
||||
private eventTask: Promise<void>;
|
||||
|
||||
constructor(public transport: Transport, startEventLoop: boolean) {
|
||||
super();
|
||||
this.rpc = new RawClient(this.transport);
|
||||
this.transport.on("request", (request: Request) => {
|
||||
const method = request.method;
|
||||
if (method === "event") {
|
||||
const event = request.params! as DCWireEvent<Event>;
|
||||
//@ts-ignore
|
||||
this.emit(event.event.type, event.contextId, event.event as any);
|
||||
this.emit("ALL", event.contextId, event.event as any);
|
||||
if (startEventLoop) {
|
||||
this.eventTask = this.eventLoop();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.contextEmitters[event.contextId]) {
|
||||
this.contextEmitters[event.contextId].emit(
|
||||
event.event.type,
|
||||
//@ts-ignore
|
||||
event.event as any
|
||||
);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event.event);
|
||||
}
|
||||
async eventLoop(): Promise<void> {
|
||||
while (true) {
|
||||
const event = await this.rpc.getNextEvent();
|
||||
//@ts-ignore
|
||||
this.emit(event.event.type, event.contextId, event.event);
|
||||
this.emit("ALL", event.contextId, event.event);
|
||||
|
||||
if (this.contextEmitters[event.contextId]) {
|
||||
this.contextEmitters[event.contextId].emit(
|
||||
event.event.type,
|
||||
//@ts-ignore
|
||||
event.event as any
|
||||
);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event.event as any);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async listAccounts(): Promise<T.Account[]> {
|
||||
@@ -75,10 +76,12 @@ export class BaseDeltaChat<
|
||||
|
||||
export type Opts = {
|
||||
url: string;
|
||||
startEventLoop: boolean;
|
||||
};
|
||||
|
||||
export const DEFAULT_OPTS: Opts = {
|
||||
url: "ws://localhost:20808/ws",
|
||||
startEventLoop: true,
|
||||
};
|
||||
export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
||||
opts: Opts;
|
||||
@@ -86,20 +89,24 @@ export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
||||
this.transport.close();
|
||||
}
|
||||
constructor(opts?: Opts | string) {
|
||||
if (typeof opts === "string") opts = { url: opts };
|
||||
if (opts) opts = { ...DEFAULT_OPTS, ...opts };
|
||||
else opts = { ...DEFAULT_OPTS };
|
||||
if (typeof opts === "string") {
|
||||
opts = { ...DEFAULT_OPTS, url: opts };
|
||||
} else if (opts) {
|
||||
opts = { ...DEFAULT_OPTS, ...opts };
|
||||
} else {
|
||||
opts = { ...DEFAULT_OPTS };
|
||||
}
|
||||
const transport = new WebsocketTransport(opts.url);
|
||||
super(transport);
|
||||
super(transport, opts.startEventLoop);
|
||||
this.opts = opts;
|
||||
}
|
||||
}
|
||||
|
||||
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
||||
close() {}
|
||||
constructor(input: any, output: any) {
|
||||
constructor(input: any, output: any, startEventLoop: boolean) {
|
||||
const transport = new StdioTransport(input, output);
|
||||
super(transport);
|
||||
super(transport, startEventLoop);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export * as RPC from "../generated/jsonrpc.js";
|
||||
export * as T from "../generated/types.js";
|
||||
export * from "../generated/events.js";
|
||||
export { RawClient } from "../generated/client.js";
|
||||
export * from "./client.js";
|
||||
export * as yerpc from "yerpc";
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("basic tests", () => {
|
||||
|
||||
before(async () => {
|
||||
serverHandle = await startServer();
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||
// dc.on("ALL", (event) => {
|
||||
//console.log("event", event);
|
||||
// });
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("online tests", function () {
|
||||
this.skip();
|
||||
}
|
||||
serverHandle = await startServer();
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||
|
||||
dc.on("ALL", (contextId, { type }) => {
|
||||
if (type !== "Info") console.log(contextId, type);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
anyhow = "1"
|
||||
console-subscriber = "0.1.9"
|
||||
deltachat = { path = "..", features = ["internals"]}
|
||||
dirs = "5"
|
||||
log = "0.4.16"
|
||||
|
||||
@@ -315,11 +315,13 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
let context = Context::new(Path::new(&args[1]), 0, Events::new(), StockStrings::new()).await?;
|
||||
|
||||
let events = context.get_event_emitter();
|
||||
tokio::task::spawn(async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
receive_event(event.typ);
|
||||
}
|
||||
});
|
||||
tokio::task::Builder::new()
|
||||
.name("repl:receive_event")
|
||||
.spawn(async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
receive_event(event.typ);
|
||||
}
|
||||
})?;
|
||||
|
||||
println!("Delta Chat Core is awaiting your commands.");
|
||||
|
||||
@@ -331,61 +333,63 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
let mut selected_chat = ChatId::default();
|
||||
|
||||
let ctx = context.clone();
|
||||
let input_loop = tokio::task::spawn_blocking(move || {
|
||||
let h = DcHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
highlighter: MatchingBracketHighlighter::new(),
|
||||
hinter: HistoryHinter {},
|
||||
};
|
||||
let mut rl = Editor::with_config(config)?;
|
||||
rl.set_helper(Some(h));
|
||||
rl.bind_sequence(KeyEvent::alt('N'), Cmd::HistorySearchForward);
|
||||
rl.bind_sequence(KeyEvent::alt('P'), Cmd::HistorySearchBackward);
|
||||
if rl.load_history(".dc-history.txt").is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
let input_loop = tokio::task::Builder::new()
|
||||
.name("repl:input_loop")
|
||||
.spawn_blocking(move || {
|
||||
let h = DcHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
highlighter: MatchingBracketHighlighter::new(),
|
||||
hinter: HistoryHinter {},
|
||||
};
|
||||
let mut rl = Editor::with_config(config)?;
|
||||
rl.set_helper(Some(h));
|
||||
rl.bind_sequence(KeyEvent::alt('N'), Cmd::HistorySearchForward);
|
||||
rl.bind_sequence(KeyEvent::alt('P'), Cmd::HistorySearchBackward);
|
||||
if rl.load_history(".dc-history.txt").is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
|
||||
loop {
|
||||
let p = "> ";
|
||||
let readline = rl.readline(p);
|
||||
loop {
|
||||
let p = "> ";
|
||||
let readline = rl.readline(p);
|
||||
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str())?;
|
||||
let should_continue = Handle::current().block_on(async {
|
||||
match handle_cmd(line.trim(), ctx.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => true,
|
||||
Ok(ExitResult::Exit) => {
|
||||
println!("Exiting ...");
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {err:#}");
|
||||
true
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str())?;
|
||||
let should_continue = Handle::current().block_on(async {
|
||||
match handle_cmd(line.trim(), ctx.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => true,
|
||||
Ok(ExitResult::Exit) => {
|
||||
println!("Exiting ...");
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {err:#}");
|
||||
true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !should_continue {
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if !should_continue {
|
||||
}
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
println!("Exiting...");
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {err:#}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
println!("Exiting...");
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {err:#}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rl.save_history(".dc-history.txt")?;
|
||||
println!("history saved");
|
||||
Ok::<_, Error>(())
|
||||
});
|
||||
rl.save_history(".dc-history.txt")?;
|
||||
println!("history saved");
|
||||
Ok::<_, Error>(())
|
||||
})?;
|
||||
|
||||
context.stop_io().await;
|
||||
input_loop.await??;
|
||||
@@ -481,6 +485,7 @@ async fn handle_cmd(
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
console_subscriber::init();
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
let args = std::env::args().collect();
|
||||
|
||||
@@ -5,9 +5,23 @@ and provides asynchronous interface to it.
|
||||
|
||||
## Getting started
|
||||
|
||||
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`
|
||||
or download a prebuilt release.
|
||||
Install it anywhere in your `PATH`.
|
||||
|
||||
[Create a virtual environment](https://docs.python.org/3/library/venv.html)
|
||||
if you don't have one already and activate it.
|
||||
```
|
||||
$ python -m venv env
|
||||
$ . env/bin/activate
|
||||
```
|
||||
|
||||
Install `deltachat-rpc-client` from source:
|
||||
```
|
||||
$ cd deltachat-rpc-client
|
||||
$ pip install .
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
|
||||
@@ -177,7 +177,7 @@ class Account:
|
||||
|
||||
entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id)
|
||||
if not snapshot:
|
||||
return [Chat(self, entry[0]) for entry in entries]
|
||||
return [Chat(self, entry) for entry in entries]
|
||||
|
||||
items = await self._rpc.get_chatlist_items_by_entries(self.id, entries)
|
||||
chats = []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .contact import Contact
|
||||
@@ -35,6 +35,13 @@ class Message:
|
||||
snapshot["message"] = self
|
||||
return snapshot
|
||||
|
||||
async def get_reactions(self) -> Optional[AttrDict]:
|
||||
"""Get message reactions."""
|
||||
reactions = await self._rpc.get_message_reactions(self.account.id, self.id)
|
||||
if reactions:
|
||||
return AttrDict(reactions)
|
||||
return None
|
||||
|
||||
async def mark_seen(self) -> None:
|
||||
"""Mark the message as seen."""
|
||||
await self._rpc.markseen_msgs(self.account.id, [self.id])
|
||||
|
||||
@@ -23,7 +23,9 @@ class Rpc:
|
||||
self.event_queues: Dict[int, asyncio.Queue]
|
||||
# Map from request ID to `asyncio.Future` returning the response.
|
||||
self.request_events: Dict[int, asyncio.Future]
|
||||
self.closing: bool
|
||||
self.reader_task: asyncio.Task
|
||||
self.events_task: asyncio.Task
|
||||
|
||||
async def start(self) -> None:
|
||||
self.process = await asyncio.create_subprocess_exec(
|
||||
@@ -35,10 +37,15 @@ class Rpc:
|
||||
self.id = 0
|
||||
self.event_queues = {}
|
||||
self.request_events = {}
|
||||
self.closing = False
|
||||
self.reader_task = asyncio.create_task(self.reader_loop())
|
||||
self.events_task = asyncio.create_task(self.events_loop())
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Terminate RPC server process and wait until the reader loop finishes."""
|
||||
self.closing = True
|
||||
await self.stop_io_for_all_accounts()
|
||||
await self.events_task
|
||||
self.process.terminate()
|
||||
await self.reader_task
|
||||
|
||||
@@ -58,21 +65,28 @@ class Rpc:
|
||||
if "id" in response:
|
||||
fut = self.request_events.pop(response["id"])
|
||||
fut.set_result(response)
|
||||
elif response["method"] == "event":
|
||||
# An event notification.
|
||||
params = response["params"]
|
||||
account_id = params["contextId"]
|
||||
if account_id not in self.event_queues:
|
||||
self.event_queues[account_id] = asyncio.Queue()
|
||||
await self.event_queues[account_id].put(params["event"])
|
||||
else:
|
||||
print(response)
|
||||
|
||||
async def get_queue(self, account_id: int) -> asyncio.Queue:
|
||||
if account_id not in self.event_queues:
|
||||
self.event_queues[account_id] = asyncio.Queue()
|
||||
return self.event_queues[account_id]
|
||||
|
||||
async def events_loop(self) -> None:
|
||||
"""Requests new events and distributes them between queues."""
|
||||
while True:
|
||||
if self.closing:
|
||||
return
|
||||
event = await self.get_next_event()
|
||||
account_id = event["contextId"]
|
||||
queue = await self.get_queue(account_id)
|
||||
await queue.put(event["event"])
|
||||
|
||||
async def wait_for_event(self, account_id: int) -> Optional[dict]:
|
||||
"""Waits for the next event from the given account and returns it."""
|
||||
if account_id in self.event_queues:
|
||||
return await self.event_queues[account_id].get()
|
||||
return None
|
||||
queue = await self.get_queue(account_id)
|
||||
return await queue.get()
|
||||
|
||||
def __getattr__(self, attr: str):
|
||||
async def method(*args, **kwargs) -> Any:
|
||||
|
||||
@@ -239,6 +239,10 @@ async def test_message(acfactory) -> None:
|
||||
|
||||
await message.mark_seen()
|
||||
await message.send_reaction("😎")
|
||||
reactions = await message.get_reactions()
|
||||
assert reactions
|
||||
snapshot = await message.get_snapshot()
|
||||
assert reactions == snapshot.reactions
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.113.0"
|
||||
version = "1.115.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
@@ -15,12 +15,12 @@ deltachat = { path = "..", default-features = false }
|
||||
|
||||
anyhow = "1"
|
||||
env_logger = { version = "0.10.0" }
|
||||
futures-lite = "1.12.0"
|
||||
futures-lite = "1.13.0"
|
||||
log = "0.4"
|
||||
serde_json = "1.0.95"
|
||||
serde_json = "1.0.96"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.27.0", features = ["io-std"] }
|
||||
tokio-util = "0.7.7"
|
||||
tokio = { version = "1.28.0", features = ["io-std"] }
|
||||
tokio-util = "0.7.8"
|
||||
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use deltachat::constants::DC_VERSION_STR;
|
||||
use deltachat_jsonrpc::api::events::event_to_json_rpc_notification;
|
||||
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
||||
use futures_lite::stream::StreamExt;
|
||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||
@@ -58,7 +57,6 @@ async fn main_impl() -> Result<()> {
|
||||
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
|
||||
log::info!("Starting with accounts directory `{}`.", path);
|
||||
let accounts = Accounts::new(PathBuf::from(&path)).await?;
|
||||
let events = accounts.get_event_emitter();
|
||||
|
||||
log::info!("Creating JSON-RPC API.");
|
||||
let accounts = Arc::new(RwLock::new(accounts));
|
||||
@@ -68,21 +66,6 @@ async fn main_impl() -> Result<()> {
|
||||
let session = RpcSession::new(client.clone(), state.clone());
|
||||
let main_cancel = CancellationToken::new();
|
||||
|
||||
// Events task converts core events to JSON-RPC notifications.
|
||||
let cancel = main_cancel.clone();
|
||||
let events_task: JoinHandle<Result<()>> = tokio::spawn(async move {
|
||||
let _cancel_guard = cancel.clone().drop_guard();
|
||||
let mut r = Ok(());
|
||||
while let Some(event) = events.recv().await {
|
||||
if r.is_err() {
|
||||
continue;
|
||||
}
|
||||
let event = event_to_json_rpc_notification(event);
|
||||
r = client.send_notification("event", Some(event)).await;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Send task prints JSON responses to stdout.
|
||||
let cancel = main_cancel.clone();
|
||||
let send_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||
@@ -148,16 +131,13 @@ async fn main_impl() -> Result<()> {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// See "Thread safety" section in deltachat-ffi/deltachat.h for explanation.
|
||||
// NB: Events are drained by events_task.
|
||||
main_cancel.cancelled().await;
|
||||
accounts.read().await.stop_io().await;
|
||||
drop(accounts);
|
||||
drop(state);
|
||||
let (r0, r1, r2, r3) = tokio::join!(events_task, send_task, sigterm_task, recv_task);
|
||||
for r in [r0, r1, r2, r3] {
|
||||
r??;
|
||||
}
|
||||
send_task.await??;
|
||||
sigterm_task.await??;
|
||||
recv_task.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
16
deny.toml
16
deny.toml
@@ -41,6 +41,7 @@ skip = [
|
||||
{ name = "rand_core", version = "<0.6" },
|
||||
{ name = "rand", version = "<0.8" },
|
||||
{ name = "redox_syscall", version = "0.2.16" },
|
||||
{ name = "regex-syntax", version = "0.6.29" },
|
||||
{ name = "sec1", version = "0.3.0" },
|
||||
{ name = "sha2", version = "<0.10" },
|
||||
{ name = "signature", version = "1.6.4" },
|
||||
@@ -49,12 +50,15 @@ skip = [
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "time", version = "<0.3" },
|
||||
{ name = "wasi", version = "<0.11" },
|
||||
{ name = "windows_aarch64_msvc", version = "<0.42" },
|
||||
{ name = "windows_i686_gnu", version = "<0.42" },
|
||||
{ name = "windows_i686_msvc", version = "<0.42" },
|
||||
{ name = "windows-sys", version = "<0.45" },
|
||||
{ name = "windows_x86_64_gnu", version = "<0.42" },
|
||||
{ name = "windows_x86_64_msvc", version = "<0.42" },
|
||||
{ name = "windows_aarch64_gnullvm", version = "<0.48" },
|
||||
{ name = "windows_aarch64_msvc", version = "<0.48" },
|
||||
{ name = "windows_i686_gnu", version = "<0.48" },
|
||||
{ name = "windows_i686_msvc", version = "<0.48" },
|
||||
{ name = "windows-sys", version = "<0.48" },
|
||||
{ name = "windows-targets", version = "<0.48" },
|
||||
{ name = "windows_x86_64_gnullvm", version = "<0.48" },
|
||||
{ name = "windows_x86_64_gnu", version = "<0.48" },
|
||||
{ name = "windows_x86_64_msvc", version = "<0.48" },
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -60,5 +60,5 @@
|
||||
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
|
||||
},
|
||||
"types": "node/dist/index.d.ts",
|
||||
"version": "1.113.0"
|
||||
"version": "1.115.0"
|
||||
}
|
||||
|
||||
@@ -74,7 +74,9 @@ Developing the bindings
|
||||
If you want to develop or debug the bindings,
|
||||
you can create a testing development environment using `tox`::
|
||||
|
||||
tox -c python --devenv env
|
||||
export DCC_RS_DEV="$PWD"
|
||||
export DCC_RS_TARGET=debug
|
||||
tox -c python --devenv env -e py
|
||||
. env/bin/activate
|
||||
|
||||
Inside this environment the bindings are installed
|
||||
|
||||
@@ -703,7 +703,7 @@ def test_mdn_asymmetric(acfactory, lp):
|
||||
assert len(chat.get_messages()) == 1
|
||||
|
||||
# Wait for the message to be marked as seen on IMAP.
|
||||
ac1._evtracker.get_info_contains("Marked messages 1 in folder DeltaChat as seen.")
|
||||
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
|
||||
|
||||
# MDN is received even though MDNs are already disabled
|
||||
assert msg_out.is_out_mdn_received()
|
||||
|
||||
@@ -9,6 +9,7 @@ from deltachat.testplugin import (
|
||||
create_dict_from_files_in_path,
|
||||
write_dict_to_dir,
|
||||
)
|
||||
from deltachat.cutil import from_optional_dc_charpointer
|
||||
|
||||
# from deltachat.account import EventLogger
|
||||
|
||||
@@ -215,3 +216,19 @@ def test_logged_ac_process_ffi_failure(acfactory):
|
||||
assert "ac_process_ffi_event" in res
|
||||
assert "ZeroDivisionError" in res
|
||||
assert "Traceback" in res
|
||||
|
||||
|
||||
def test_jsonrpc_blocking_call(tmpdir):
|
||||
accounts_fname = tmpdir.join("accounts")
|
||||
accounts = ffi.gc(
|
||||
lib.dc_accounts_new(ffi.NULL, accounts_fname.strpath.encode("ascii")),
|
||||
lib.dc_accounts_unref,
|
||||
)
|
||||
jsonrpc = ffi.gc(lib.dc_jsonrpc_init(accounts), lib.dc_jsonrpc_unref)
|
||||
res = from_optional_dc_charpointer(
|
||||
lib.dc_jsonrpc_blocking_call(jsonrpc, b"check_email_validity", b'["alice@example.org"]'),
|
||||
)
|
||||
assert res == "true"
|
||||
|
||||
res = from_optional_dc_charpointer(lib.dc_jsonrpc_blocking_call(jsonrpc, b"check_email_validity", b'["alice"]'))
|
||||
assert res == "false"
|
||||
|
||||
@@ -1 +1 @@
|
||||
2023-04-18
|
||||
2023-05-12
|
||||
@@ -1,3 +0,0 @@
|
||||
pre-release-commit-message = "chore({{crate_name}}): release {{version}}"
|
||||
pro-release-commit-message = "chore({{crate_name}}): starting development cycle for {{next_version}}"
|
||||
no-dev-version = true
|
||||
@@ -1,2 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Update package cache without changing the lockfile.
|
||||
cargo update --dry-run
|
||||
|
||||
cargo deny --workspace --all-features check -D warnings
|
||||
|
||||
@@ -7,6 +7,7 @@ import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
|
||||
rex = re.compile(r'version = "(\S+)"')
|
||||
|
||||
@@ -95,22 +96,14 @@ def main():
|
||||
today = datetime.date.today().isoformat()
|
||||
|
||||
if "alpha" not in newversion:
|
||||
changelog_name = "CHANGELOG.md"
|
||||
changelog_tmpname = changelog_name + ".tmp"
|
||||
changelog_tmp = open(changelog_tmpname, "w")
|
||||
found = False
|
||||
for line in open(changelog_name):
|
||||
## 1.25.0
|
||||
if line == f"## [{newversion}]\n":
|
||||
line = f"## [{newversion}] - {today}\n"
|
||||
for line in Path("CHANGELOG.md").open():
|
||||
if line == f"## [{newversion}] - {today}\n":
|
||||
found = True
|
||||
changelog_tmp.write(line)
|
||||
if not found:
|
||||
raise SystemExit(
|
||||
f"{changelog_name} contains no entry for version: {newversion}"
|
||||
)
|
||||
changelog_tmp.close()
|
||||
os.rename(changelog_tmpname, changelog_name)
|
||||
|
||||
for toml_filename in toml_list:
|
||||
replace_toml_version(toml_filename, newversion)
|
||||
@@ -128,11 +121,15 @@ def main():
|
||||
subprocess.call(["git", "add", "-u"])
|
||||
# subprocess.call(["cargo", "update", "-p", "deltachat"])
|
||||
|
||||
print("after commit, on master make sure to: ")
|
||||
print("")
|
||||
print("After commit, make sure to:")
|
||||
print()
|
||||
print(f" git tag -a v{newversion}")
|
||||
print(f" git push origin v{newversion}")
|
||||
print("")
|
||||
print(f" gh release create v{newversion} -n ''")
|
||||
print()
|
||||
print("Merge release branch into `master` if the release")
|
||||
print("is made on a stable branch.")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5147,9 +5147,11 @@ mod tests {
|
||||
// Alice has an SMTP-server replacing the `Message-ID:`-header (as done eg. by outlook.com).
|
||||
let sent_msg = alice.pop_sent_msg().await;
|
||||
let msg = sent_msg.payload();
|
||||
assert_eq!(msg.match_indices("Gr.").count(), 2);
|
||||
assert_eq!(msg.match_indices("Message-ID: <Gr.").count(), 1);
|
||||
assert_eq!(msg.match_indices("References: <Gr.").count(), 1);
|
||||
let msg = msg.replace("Message-ID: <Gr.", "Message-ID: <XXX");
|
||||
assert_eq!(msg.match_indices("Gr.").count(), 1);
|
||||
assert_eq!(msg.match_indices("Message-ID: <Gr.").count(), 0);
|
||||
assert_eq!(msg.match_indices("References: <Gr.").count(), 1);
|
||||
|
||||
// Bob receives this message, he may detect group by `References:`- or `Chat-Group:`-header
|
||||
receive_imf(&bob, msg.as_bytes(), false).await.unwrap();
|
||||
|
||||
@@ -311,13 +311,17 @@ impl Chatlist {
|
||||
};
|
||||
|
||||
let (lastmsg, lastcontact) = if let Some(lastmsg_id) = lastmsg_id {
|
||||
let lastmsg = Message::load_from_db(context, lastmsg_id).await?;
|
||||
let lastmsg = Message::load_from_db(context, lastmsg_id)
|
||||
.await
|
||||
.context("loading message failed")?;
|
||||
if lastmsg.from_id == ContactId::SELF {
|
||||
(Some(lastmsg), None)
|
||||
} else {
|
||||
match chat.typ {
|
||||
Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
|
||||
let lastcontact = Contact::load_from_db(context, lastmsg.from_id).await?;
|
||||
let lastcontact = Contact::load_from_db(context, lastmsg.from_id)
|
||||
.await
|
||||
.context("loading contact failed")?;
|
||||
(Some(lastmsg), Some(lastcontact))
|
||||
}
|
||||
Chattype::Single | Chattype::Undefined => (Some(lastmsg), None),
|
||||
@@ -362,6 +366,25 @@ pub async fn get_archived_cnt(context: &Context) -> Result<usize> {
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Gets the last message of a chat, the message that would also be displayed in the ChatList
|
||||
/// Used for passing to `deltachat::chatlist::Chatlist::get_summary2`
|
||||
pub async fn get_last_message_for_chat(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
) -> Result<Option<MsgId>> {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT id
|
||||
FROM msgs
|
||||
WHERE chat_id=?2
|
||||
AND (hidden=0 OR state=?1)
|
||||
ORDER BY timestamp DESC, id DESC LIMIT 1",
|
||||
(MessageState::OutDraft, chat_id),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
mod auto_mozilla;
|
||||
mod auto_outlook;
|
||||
mod read_url;
|
||||
mod server_params;
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
@@ -182,7 +181,9 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
let socks5_enabled = socks5_config.is_some();
|
||||
|
||||
let ctx2 = ctx.clone();
|
||||
let update_device_chats_handle = task::spawn(async move { ctx2.update_device_chats().await });
|
||||
let update_device_chats_handle = tokio::task::Builder::new()
|
||||
.name("update_device_chats")
|
||||
.spawn(async move { ctx2.update_device_chats().await })?;
|
||||
|
||||
// Step 1: Load the parameters and check email-address and password
|
||||
|
||||
@@ -351,44 +352,46 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
.provider
|
||||
.map_or(socks5_config.is_some(), |provider| provider.opt.strict_tls);
|
||||
|
||||
let smtp_config_task = task::spawn(async move {
|
||||
let mut smtp_configured = false;
|
||||
let mut errors = Vec::new();
|
||||
for smtp_server in smtp_servers {
|
||||
smtp_param.user = smtp_server.username.clone();
|
||||
smtp_param.server = smtp_server.hostname.clone();
|
||||
smtp_param.port = smtp_server.port;
|
||||
smtp_param.security = smtp_server.socket;
|
||||
smtp_param.certificate_checks = match smtp_server.strict_tls {
|
||||
Some(true) => CertificateChecks::Strict,
|
||||
Some(false) => CertificateChecks::AcceptInvalidCertificates,
|
||||
None => CertificateChecks::Automatic,
|
||||
};
|
||||
let smtp_config_task = tokio::task::Builder::new()
|
||||
.name("smtp_config")
|
||||
.spawn(async move {
|
||||
let mut smtp_configured = false;
|
||||
let mut errors = Vec::new();
|
||||
for smtp_server in smtp_servers {
|
||||
smtp_param.user = smtp_server.username.clone();
|
||||
smtp_param.server = smtp_server.hostname.clone();
|
||||
smtp_param.port = smtp_server.port;
|
||||
smtp_param.security = smtp_server.socket;
|
||||
smtp_param.certificate_checks = match smtp_server.strict_tls {
|
||||
Some(true) => CertificateChecks::Strict,
|
||||
Some(false) => CertificateChecks::AcceptInvalidCertificates,
|
||||
None => CertificateChecks::Automatic,
|
||||
};
|
||||
|
||||
match try_smtp_one_param(
|
||||
&context_smtp,
|
||||
&smtp_param,
|
||||
&socks5_config,
|
||||
&smtp_addr,
|
||||
provider_strict_tls,
|
||||
&mut smtp,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
smtp_configured = true;
|
||||
break;
|
||||
match try_smtp_one_param(
|
||||
&context_smtp,
|
||||
&smtp_param,
|
||||
&socks5_config,
|
||||
&smtp_addr,
|
||||
provider_strict_tls,
|
||||
&mut smtp,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
smtp_configured = true;
|
||||
break;
|
||||
}
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
}
|
||||
|
||||
if smtp_configured {
|
||||
Ok(smtp_param)
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
});
|
||||
if smtp_configured {
|
||||
Ok(smtp_param)
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
})?;
|
||||
|
||||
progress!(ctx, 600);
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ use std::str::FromStr;
|
||||
|
||||
use quick_xml::events::{BytesStart, Event};
|
||||
|
||||
use super::read_url::read_url;
|
||||
use super::{Error, ServerParams};
|
||||
use crate::context::Context;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::net::read_url;
|
||||
use crate::provider::{Protocol, Socket};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -7,9 +7,9 @@ use std::io::BufRead;
|
||||
|
||||
use quick_xml::events::Event;
|
||||
|
||||
use super::read_url::read_url;
|
||||
use super::{Error, ServerParams};
|
||||
use crate::context::Context;
|
||||
use crate::net::read_url;
|
||||
use crate::provider::{Protocol, Socket};
|
||||
|
||||
/// Result of parsing a single `Protocol` tag.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use anyhow::{anyhow, format_err};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::socks::Socks5Config;
|
||||
|
||||
pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||
match read_url_inner(context, url).await {
|
||||
Ok(s) => {
|
||||
info!(context, "Successfully read url {}", url);
|
||||
Ok(s)
|
||||
}
|
||||
Err(e) => {
|
||||
info!(context, "Can't read URL {}: {:#}", url, e);
|
||||
Err(format_err!("Can't read URL {}: {:#}", url, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
let client = crate::http::get_client(socks5_config)?;
|
||||
let mut url = url.to_string();
|
||||
|
||||
// Follow up to 10 http-redirects
|
||||
for _i in 0..10 {
|
||||
let response = client.get(&url).send().await?;
|
||||
if response.status().is_redirection() {
|
||||
let headers = response.headers();
|
||||
let header = headers
|
||||
.get_all("location")
|
||||
.iter()
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("Redirection doesn't have a target location"))?
|
||||
.to_str()?;
|
||||
info!(context, "Following redirect to {}", header);
|
||||
url = header.to_string();
|
||||
continue;
|
||||
}
|
||||
|
||||
return response.text().await.map_err(Into::into);
|
||||
}
|
||||
|
||||
Err(format_err!("Followed 10 redirections"))
|
||||
}
|
||||
@@ -1555,7 +1555,10 @@ impl RecentlySeenLoop {
|
||||
pub(crate) fn new(context: Context) -> Self {
|
||||
let (interrupt_send, interrupt_recv) = channel::bounded(1);
|
||||
|
||||
let handle = task::spawn(Self::run(context, interrupt_recv));
|
||||
let handle = tokio::task::Builder::new()
|
||||
.name("recently_seen")
|
||||
.spawn(Self::run(context, interrupt_recv))
|
||||
.expect("failed to spawn recently_seen task");
|
||||
Self {
|
||||
handle,
|
||||
interrupt_send,
|
||||
|
||||
@@ -143,9 +143,11 @@ pub(crate) async fn set_debug_logging_xdc(ctx: &Context, id: Option<MsgId>) -> a
|
||||
let (sender, debug_logging_recv) = channel::bounded(1000);
|
||||
let loop_handle = {
|
||||
let ctx = ctx.clone();
|
||||
task::spawn(async move {
|
||||
debug_logging_loop(&ctx, debug_logging_recv).await
|
||||
})
|
||||
tokio::task::Builder::new()
|
||||
.name("debug_logging")
|
||||
.spawn(async move {
|
||||
debug_logging_loop(&ctx, debug_logging_recv).await
|
||||
})?
|
||||
};
|
||||
*debug_logging = Some(DebugLogging {
|
||||
msg_id,
|
||||
|
||||
24
src/http.rs
24
src/http.rs
@@ -1,24 +0,0 @@
|
||||
//! # HTTP module.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::socks::Socks5Config;
|
||||
|
||||
const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
pub(crate) fn get_client(socks5_config: Option<Socks5Config>) -> Result<reqwest::Client> {
|
||||
let builder = reqwest::ClientBuilder::new().timeout(HTTP_TIMEOUT);
|
||||
let builder = if let Some(socks5_config) = socks5_config {
|
||||
let proxy = reqwest::Proxy::all(socks5_config.to_url())?;
|
||||
builder.proxy(proxy)
|
||||
} else {
|
||||
// Disable usage of "system" proxy configured via environment variables.
|
||||
// It is enabled by default in `reqwest`, see
|
||||
// <https://docs.rs/reqwest/0.11.14/reqwest/struct.ClientBuilder.html#method.no_proxy>
|
||||
// for documentation.
|
||||
builder.no_proxy()
|
||||
};
|
||||
Ok(builder.build()?)
|
||||
}
|
||||
@@ -1324,8 +1324,8 @@ impl Imap {
|
||||
// Fetch last DC_FETCH_EXISTING_MSGS_COUNT (100) messages.
|
||||
// Sequence numbers are sequential. If there are 1000 messages in the inbox,
|
||||
// we can fetch the sequence numbers 900-1000 and get the last 100 messages.
|
||||
let first = cmp::max(1, exists - DC_FETCH_EXISTING_MSGS_COUNT);
|
||||
let set = format!("{first}:*");
|
||||
let first = cmp::max(1, exists - DC_FETCH_EXISTING_MSGS_COUNT + 1);
|
||||
let set = format!("{first}:{exists}");
|
||||
let mut list = session
|
||||
.fetch(&set, PREFETCH_FLAGS)
|
||||
.await
|
||||
|
||||
@@ -63,7 +63,6 @@ mod decrypt;
|
||||
pub mod download;
|
||||
mod e2ee;
|
||||
pub mod ephemeral;
|
||||
mod http;
|
||||
mod imap;
|
||||
pub mod imex;
|
||||
pub mod release;
|
||||
@@ -100,7 +99,7 @@ mod dehtml;
|
||||
mod authres;
|
||||
mod color;
|
||||
pub mod html;
|
||||
mod net;
|
||||
pub mod net;
|
||||
pub mod plaintext;
|
||||
pub mod summary;
|
||||
|
||||
|
||||
@@ -537,7 +537,9 @@ impl Message {
|
||||
/// Returns the size of the file in bytes, if applicable.
|
||||
pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
|
||||
if let Some(path) = self.param.get_path(Param::File, context)? {
|
||||
Ok(Some(get_filebytes(context, &path).await?))
|
||||
Ok(Some(get_filebytes(context, &path).await.with_context(
|
||||
|| format!("failed to get {} size in bytes", path.display()),
|
||||
)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -1086,6 +1088,26 @@ impl MessageState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns contacts that sent read receipts and the time of reading.
|
||||
pub async fn get_msg_read_receipts(
|
||||
context: &Context,
|
||||
msg_id: MsgId,
|
||||
) -> Result<Vec<(ContactId, i64)>> {
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
|
||||
(msg_id,),
|
||||
|row| {
|
||||
let contact_id: ContactId = row.get(0)?;
|
||||
let ts: i64 = row.get(1)?;
|
||||
Ok((contact_id, ts))
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns detailed message information in a multi-line text form.
|
||||
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String> {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
@@ -1399,8 +1421,8 @@ pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8
|
||||
/// by moving them to the trash chat
|
||||
/// and scheduling for deletion on IMAP.
|
||||
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
for msg_id in msg_ids.iter() {
|
||||
let msg = Message::load_from_db(context, *msg_id).await?;
|
||||
for &msg_id in msg_ids {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
if msg.location_id > 0 {
|
||||
delete_poi_location(context, msg.location_id).await?;
|
||||
}
|
||||
@@ -1410,7 +1432,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
.with_context(|| format!("Unable to trash message {msg_id}"))?;
|
||||
|
||||
if msg.viewtype == Viewtype::Webxdc {
|
||||
context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: *msg_id });
|
||||
context.emit_event(EventType::WebxdcInstanceDeleted { msg_id });
|
||||
}
|
||||
|
||||
let target = context.get_delete_msgs_target().await?;
|
||||
@@ -1430,7 +1452,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
.map(|dl| dl.msg_id);
|
||||
|
||||
if let Some(id) = logging_xdc_id {
|
||||
if id == *msg_id {
|
||||
if id == msg_id {
|
||||
set_debug_logging_xdc(context, None).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use crate::aheader::{Aheader, EncryptPreference};
|
||||
use crate::blob::BlobObject;
|
||||
use crate::config::Config;
|
||||
use crate::constants::{DC_DESIRED_TEXT_LINES, DC_DESIRED_TEXT_LINE_LEN};
|
||||
use crate::contact::{addr_cmp, addr_normalize, ContactId};
|
||||
use crate::context::Context;
|
||||
@@ -1114,15 +1115,22 @@ impl MimeMessage {
|
||||
(simplified_txt, top_quote)
|
||||
};
|
||||
|
||||
// Truncate text if it has too many lines
|
||||
let (simplified_txt, was_truncated) = truncate_by_lines(
|
||||
simplified_txt,
|
||||
DC_DESIRED_TEXT_LINES,
|
||||
DC_DESIRED_TEXT_LINE_LEN,
|
||||
);
|
||||
if was_truncated {
|
||||
self.is_mime_modified = was_truncated;
|
||||
}
|
||||
let is_bot = context.get_config_bool(Config::Bot).await?;
|
||||
|
||||
let simplified_txt = if is_bot {
|
||||
simplified_txt
|
||||
} else {
|
||||
// Truncate text if it has too many lines
|
||||
let (simplified_txt, was_truncated) = truncate_by_lines(
|
||||
simplified_txt,
|
||||
DC_DESIRED_TEXT_LINES,
|
||||
DC_DESIRED_TEXT_LINE_LEN,
|
||||
);
|
||||
if was_truncated {
|
||||
self.is_mime_modified = was_truncated;
|
||||
}
|
||||
simplified_txt
|
||||
};
|
||||
|
||||
if !simplified_txt.is_empty() || simplified_quote.is_some() {
|
||||
let mut part = Part {
|
||||
@@ -3290,24 +3298,37 @@ On 2020-10-25, Bob wrote:
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mime_modified_large_plain() {
|
||||
async fn test_mime_modified_large_plain() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
static REPEAT_TXT: &str = "this text with 42 chars is just repeated.\n";
|
||||
static REPEAT_CNT: usize = 2000; // results in a text of 84k, should be more than DC_DESIRED_TEXT_LEN
|
||||
let long_txt = format!("From: alice@c.de\n\n{}", REPEAT_TXT.repeat(REPEAT_CNT));
|
||||
|
||||
let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(long_txt.matches("just repeated").count(), REPEAT_CNT);
|
||||
assert!(long_txt.len() > DC_DESIRED_TEXT_LEN);
|
||||
assert!(mimemsg.is_mime_modified);
|
||||
assert!(
|
||||
mimemsg.parts[0].msg.matches("just repeated").count()
|
||||
<= DC_DESIRED_TEXT_LEN / REPEAT_TXT.len()
|
||||
);
|
||||
assert!(mimemsg.parts[0].msg.len() <= DC_DESIRED_TEXT_LEN + DC_ELLIPSIS.len());
|
||||
|
||||
{
|
||||
let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref(), None).await?;
|
||||
assert!(mimemsg.is_mime_modified);
|
||||
assert!(
|
||||
mimemsg.parts[0].msg.matches("just repeated").count()
|
||||
<= DC_DESIRED_TEXT_LEN / REPEAT_TXT.len()
|
||||
);
|
||||
assert!(mimemsg.parts[0].msg.len() <= DC_DESIRED_TEXT_LEN + DC_ELLIPSIS.len());
|
||||
}
|
||||
|
||||
t.set_config(Config::Bot, Some("1")).await?;
|
||||
|
||||
{
|
||||
let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref(), None).await?;
|
||||
assert!(!mimemsg.is_mime_modified);
|
||||
assert_eq!(
|
||||
format!("{}\n", mimemsg.parts[0].msg),
|
||||
REPEAT_TXT.repeat(REPEAT_CNT)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
///! # Common network utilities.
|
||||
//! # Common network utilities.
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
@@ -12,9 +12,12 @@ use tokio_io_timeout::TimeoutStream;
|
||||
use crate::context::Context;
|
||||
use crate::tools::time;
|
||||
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod session;
|
||||
pub(crate) mod tls;
|
||||
|
||||
pub use http::{read_url, read_url_blob, Response as HttpResponse};
|
||||
|
||||
async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result<TcpStream> {
|
||||
let tcp_stream = timeout(timeout_val, TcpStream::connect(addr))
|
||||
.await
|
||||
|
||||
94
src/net/http.rs
Normal file
94
src/net/http.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
//! # HTTP module.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use mime::Mime;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::socks::Socks5Config;
|
||||
|
||||
const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
/// HTTP(S) GET response.
|
||||
#[derive(Debug)]
|
||||
pub struct Response {
|
||||
/// Response body.
|
||||
pub blob: Vec<u8>,
|
||||
|
||||
/// MIME type exntracted from the `Content-Type` header, if any.
|
||||
pub mimetype: Option<String>,
|
||||
|
||||
/// Encoding extracted from the `Content-Type` header, if any.
|
||||
pub encoding: Option<String>,
|
||||
}
|
||||
|
||||
/// Retrieves the text contents of URL using HTTP GET request.
|
||||
pub async fn read_url(context: &Context, url: &str) -> Result<String> {
|
||||
Ok(read_url_inner(context, url).await?.text().await?)
|
||||
}
|
||||
|
||||
/// Retrieves the binary contents of URL using HTTP GET request.
|
||||
pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
|
||||
let response = read_url_inner(context, url).await?;
|
||||
let content_type = response
|
||||
.headers()
|
||||
.get(reqwest::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
let mimetype = content_type
|
||||
.as_ref()
|
||||
.map(|mime| mime.essence_str().to_string());
|
||||
let encoding = content_type.as_ref().and_then(|mime| {
|
||||
mime.get_param(mime::CHARSET)
|
||||
.map(|charset| charset.as_str().to_string())
|
||||
});
|
||||
let blob: Vec<u8> = response.bytes().await?.into();
|
||||
Ok(Response {
|
||||
blob,
|
||||
mimetype,
|
||||
encoding,
|
||||
})
|
||||
}
|
||||
|
||||
async fn read_url_inner(context: &Context, url: &str) -> Result<reqwest::Response> {
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
let client = get_client(socks5_config)?;
|
||||
let mut url = url.to_string();
|
||||
|
||||
// Follow up to 10 http-redirects
|
||||
for _i in 0..10 {
|
||||
let response = client.get(&url).send().await?;
|
||||
if response.status().is_redirection() {
|
||||
let headers = response.headers();
|
||||
let header = headers
|
||||
.get_all("location")
|
||||
.iter()
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("Redirection doesn't have a target location"))?
|
||||
.to_str()?;
|
||||
info!(context, "Following redirect to {}", header);
|
||||
url = header.to_string();
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
Err(anyhow!("Followed 10 redirections"))
|
||||
}
|
||||
|
||||
pub(crate) fn get_client(socks5_config: Option<Socks5Config>) -> Result<reqwest::Client> {
|
||||
let builder = reqwest::ClientBuilder::new().timeout(HTTP_TIMEOUT);
|
||||
let builder = if let Some(socks5_config) = socks5_config {
|
||||
let proxy = reqwest::Proxy::all(socks5_config.to_url())?;
|
||||
builder.proxy(proxy)
|
||||
} else {
|
||||
// Disable usage of "system" proxy configured via environment variables.
|
||||
// It is enabled by default in `reqwest`, see
|
||||
// <https://docs.rs/reqwest/0.11.14/reqwest/struct.ClientBuilder.html#method.no_proxy>
|
||||
// for documentation.
|
||||
builder.no_proxy()
|
||||
};
|
||||
Ok(builder.build()?)
|
||||
}
|
||||
@@ -160,7 +160,7 @@ pub(crate) async fn get_oauth2_access_token(
|
||||
|
||||
// ... and POST
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
let client = crate::http::get_client(socks5_config)?;
|
||||
let client = crate::net::http::get_client(socks5_config)?;
|
||||
|
||||
let response: Response = match client.post(post_url).form(&post_param).send().await {
|
||||
Ok(resp) => match resp.json().await {
|
||||
@@ -291,7 +291,7 @@ impl Oauth2 {
|
||||
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
|
||||
// }
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await.ok()?;
|
||||
let client = match crate::http::get_client(socks5_config) {
|
||||
let client = match crate::net::http::get_client(socks5_config) {
|
||||
Ok(cl) => cl,
|
||||
Err(err) => {
|
||||
warn!(context, "failed to get HTTP client: {}", err);
|
||||
|
||||
46
src/pgp.rs
46
src/pgp.rs
@@ -364,17 +364,19 @@ pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
let passphrase = passphrase.to_string();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut rng = thread_rng();
|
||||
let s2k = StringToKey::new_default(&mut rng);
|
||||
let msg =
|
||||
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase)?;
|
||||
tokio::task::Builder::new()
|
||||
.name("symm_encrypt")
|
||||
.spawn_blocking(move || {
|
||||
let mut rng = thread_rng();
|
||||
let s2k = StringToKey::new_default(&mut rng);
|
||||
let msg =
|
||||
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase)?;
|
||||
|
||||
let encoded_msg = msg.to_armored_string(None)?;
|
||||
let encoded_msg = msg.to_armored_string(None)?;
|
||||
|
||||
Ok(encoded_msg)
|
||||
})
|
||||
.await?
|
||||
Ok(encoded_msg)
|
||||
})?
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Symmetric decryption.
|
||||
@@ -385,20 +387,22 @@ pub async fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
||||
|
||||
let passphrase = passphrase.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let decryptor = enc_msg.decrypt_with_password(|| passphrase)?;
|
||||
tokio::task::Builder::new()
|
||||
.name("symm_decrypt")
|
||||
.spawn_blocking(move || {
|
||||
let decryptor = enc_msg.decrypt_with_password(|| passphrase)?;
|
||||
|
||||
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
||||
if let Some(msg) = msgs.first() {
|
||||
match msg.get_content()? {
|
||||
Some(content) => Ok(content),
|
||||
None => bail!("Decrypted message is empty"),
|
||||
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
||||
if let Some(msg) = msgs.first() {
|
||||
match msg.get_content()? {
|
||||
Some(content) => Ok(content),
|
||||
None => bail!("Decrypted message is empty"),
|
||||
}
|
||||
} else {
|
||||
bail!("No valid messages found")
|
||||
}
|
||||
} else {
|
||||
bail!("No valid messages found")
|
||||
}
|
||||
})
|
||||
.await?
|
||||
})?
|
||||
.await?
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -526,7 +526,7 @@ struct CreateAccountErrorResponse {
|
||||
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
let response = crate::http::get_client(socks5_config)?
|
||||
let response = crate::net::http::get_client(socks5_config)?
|
||||
.post(url_str)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
//! possible to remove all reactions by sending an empty string as a reaction,
|
||||
//! even though RFC 9078 requires at least one emoji to be sent.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
@@ -116,10 +117,9 @@ impl Reactions {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.reactions.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Reactions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// Returns a map from emojis to their frequencies.
|
||||
pub fn emoji_frequencies(&self) -> BTreeMap<String, usize> {
|
||||
let mut emoji_frequencies: BTreeMap<String, usize> = BTreeMap::new();
|
||||
for reaction in self.reactions.values() {
|
||||
for emoji in reaction.emojis() {
|
||||
@@ -129,6 +129,30 @@ impl fmt::Display for Reactions {
|
||||
.or_insert(1);
|
||||
}
|
||||
}
|
||||
emoji_frequencies
|
||||
}
|
||||
|
||||
/// Returns a vector of emojis
|
||||
/// sorted in descending order of frequencies.
|
||||
///
|
||||
/// This function can be used to display the reactions in
|
||||
/// the message bubble in the UIs.
|
||||
pub fn emoji_sorted_by_frequency(&self) -> Vec<(String, usize)> {
|
||||
let mut emoji_frequencies: Vec<(String, usize)> =
|
||||
self.emoji_frequencies().into_iter().collect();
|
||||
emoji_frequencies.sort_by(|(a, a_count), (b, b_count)| {
|
||||
match a_count.cmp(b_count).reverse() {
|
||||
Ordering::Equal => a.cmp(b),
|
||||
other => other,
|
||||
}
|
||||
});
|
||||
emoji_frequencies
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Reactions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let emoji_frequencies = self.emoji_sorted_by_frequency();
|
||||
let mut first = true;
|
||||
for (emoji, frequency) in emoji_frequencies {
|
||||
if !first {
|
||||
@@ -481,6 +505,11 @@ Content-Disposition: reaction\n\
|
||||
let reactions = get_msg_reactions(&alice, alice_msg.sender_msg_id).await?;
|
||||
assert_eq!(reactions.to_string(), "👍2 😀1");
|
||||
|
||||
assert_eq!(
|
||||
reactions.emoji_sorted_by_frequency(),
|
||||
vec![("👍".to_string(), 2), ("😀".to_string(), 1)]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -557,7 +557,13 @@ async fn add_parts(
|
||||
blocked: Blocked::Request,
|
||||
}) if is_bot => Blocked::Not,
|
||||
Some(ChatIdBlocked { id: _, blocked }) => blocked,
|
||||
None => Blocked::Request,
|
||||
None => {
|
||||
if is_bot {
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Request
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if chat_id.is_none() {
|
||||
@@ -2078,7 +2084,7 @@ async fn check_verified_properties(
|
||||
) -> Result<()> {
|
||||
let contact = Contact::load_from_db(context, from_id).await?;
|
||||
|
||||
ensure!(mimeparser.was_encrypted(), "This message is not encrypted.");
|
||||
ensure!(mimeparser.was_encrypted(), "This message is not encrypted");
|
||||
|
||||
if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
|
||||
// we do not fail here currently, this would exclude (a) non-deltas
|
||||
@@ -2111,7 +2117,7 @@ async fn check_verified_properties(
|
||||
if let Some(peerstate) = peerstate {
|
||||
ensure!(
|
||||
peerstate.has_verified_key(&mimeparser.signatures),
|
||||
"The message was sent with non-verified encryption."
|
||||
"The message was sent with non-verified encryption"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2980,6 +2980,17 @@ async fn test_auto_accept_for_bots() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_auto_accept_group_for_bots() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::Bot, Some("1")).await.unwrap();
|
||||
receive_imf(&t, GRP_MAIL, false).await?;
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat = chat::Chat::load_from_db(&t, msg.chat_id).await?;
|
||||
assert!(!chat.is_contact_request());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_private_reply_to_blocked_account() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
|
||||
@@ -740,7 +740,9 @@ impl Scheduler {
|
||||
let (inbox_start_send, inbox_start_recv) = oneshot::channel();
|
||||
let handle = {
|
||||
let ctx = ctx.clone();
|
||||
task::spawn(inbox_loop(ctx, inbox_start_send, inbox_handlers))
|
||||
tokio::task::Builder::new()
|
||||
.name("inbox_loop")
|
||||
.spawn(inbox_loop(ctx, inbox_start_send, inbox_handlers))?
|
||||
};
|
||||
let inbox = SchedBox {
|
||||
meaning: FolderMeaning::Inbox,
|
||||
@@ -760,7 +762,9 @@ impl Scheduler {
|
||||
let (conn_state, handlers) = ImapConnectionState::new(&ctx).await?;
|
||||
let (start_send, start_recv) = oneshot::channel();
|
||||
let ctx = ctx.clone();
|
||||
let handle = task::spawn(simple_imap_loop(ctx, start_send, handlers, meaning));
|
||||
let handle = tokio::task::Builder::new()
|
||||
.name("simple_imap_loop")
|
||||
.spawn(simple_imap_loop(ctx, start_send, handlers, meaning))?;
|
||||
oboxes.push(SchedBox {
|
||||
meaning,
|
||||
conn_state,
|
||||
@@ -772,22 +776,28 @@ impl Scheduler {
|
||||
|
||||
let smtp_handle = {
|
||||
let ctx = ctx.clone();
|
||||
task::spawn(smtp_loop(ctx, smtp_start_send, smtp_handlers))
|
||||
tokio::task::Builder::new()
|
||||
.name("smtp_loop")
|
||||
.spawn(smtp_loop(ctx, smtp_start_send, smtp_handlers))?
|
||||
};
|
||||
start_recvs.push(smtp_start_recv);
|
||||
|
||||
let ephemeral_handle = {
|
||||
let ctx = ctx.clone();
|
||||
task::spawn(async move {
|
||||
ephemeral::ephemeral_loop(&ctx, ephemeral_interrupt_recv).await;
|
||||
})
|
||||
tokio::task::Builder::new()
|
||||
.name("ephemeral")
|
||||
.spawn(async move {
|
||||
ephemeral::ephemeral_loop(&ctx, ephemeral_interrupt_recv).await;
|
||||
})?
|
||||
};
|
||||
|
||||
let location_handle = {
|
||||
let ctx = ctx.clone();
|
||||
task::spawn(async move {
|
||||
location::location_loop(&ctx, location_interrupt_recv).await;
|
||||
})
|
||||
tokio::task::Builder::new()
|
||||
.name("location")
|
||||
.spawn(async move {
|
||||
location::location_loop(&ctx, location_interrupt_recv).await;
|
||||
})?
|
||||
};
|
||||
|
||||
let recently_seen_loop = RecentlySeenLoop::new(ctx.clone());
|
||||
|
||||
19
src/smtp.rs
19
src/smtp.rs
@@ -8,7 +8,6 @@ use anyhow::{bail, format_err, Context as _, Error, Result};
|
||||
use async_smtp::response::{Category, Code, Detail};
|
||||
use async_smtp::{self as smtp, EmailAddress, SmtpTransport};
|
||||
use tokio::io::BufStream;
|
||||
use tokio::task;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::contact::{Contact, ContactId};
|
||||
@@ -60,7 +59,10 @@ impl Smtp {
|
||||
// Closing connection with a QUIT command may take some time, especially if it's a
|
||||
// stale connection and an attempt to send the command times out. Send a command in a
|
||||
// separate task to avoid waiting for reply or timeout.
|
||||
task::spawn(async move { transport.quit().await });
|
||||
tokio::task::Builder::new()
|
||||
.name("smtp_disconnect")
|
||||
.spawn(async move { transport.quit().await })
|
||||
.expect("failed to spawn smtp_disconnect task");
|
||||
}
|
||||
self.last_success = None;
|
||||
}
|
||||
@@ -545,11 +547,11 @@ pub(crate) async fn send_msg_to_smtp(
|
||||
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
|
||||
.await
|
||||
.context("failed to remove message with exceeded retry limit from smtp table")?;
|
||||
bail!("Number of retries exceeded the limit");
|
||||
return Ok(());
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
"Try number {} to send message {} over SMTP", retries, msg_id
|
||||
"Try number {retries} to send message {msg_id} (entry {rowid}) over SMTP"
|
||||
);
|
||||
|
||||
let recipients_list = recipients
|
||||
@@ -573,8 +575,13 @@ pub(crate) async fn send_msg_to_smtp(
|
||||
{
|
||||
info!(
|
||||
context,
|
||||
"Sending of message {} was cancelled by the user.", msg_id
|
||||
"Sending of message {msg_id} (entry {rowid}) was cancelled by the user."
|
||||
);
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
|
||||
.await
|
||||
.context("failed to remove cancelled message from smtp table")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -646,6 +653,8 @@ pub(crate) async fn send_smtp_messages(context: &Context, connection: &mut Smtp)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!(context, "Selected rows from SMTP queue: {rowids:?}.");
|
||||
for rowid in rowids {
|
||||
send_msg_to_smtp(context, connection, rowid)
|
||||
.await
|
||||
|
||||
@@ -64,9 +64,11 @@ impl Smtp {
|
||||
if let Some(ref mut transport) = self.transport {
|
||||
transport.send(mail).await.map_err(Error::SmtpSend)?;
|
||||
|
||||
context.emit_event(EventType::SmtpMessageSent(format!(
|
||||
"Message len={message_len_bytes} was smtp-sent to {recipients_display}"
|
||||
)));
|
||||
let info_msg = format!(
|
||||
"Message len={message_len_bytes} was SMTP-sent to {recipients_display}"
|
||||
);
|
||||
info!(context, "{info_msg}.");
|
||||
context.emit_event(EventType::SmtpMessageSent(info_msg));
|
||||
self.last_success = Some(std::time::SystemTime::now());
|
||||
} else {
|
||||
warn!(
|
||||
|
||||
@@ -711,6 +711,24 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if dbversion < 101 {
|
||||
// Recreate `smtp` table with autoincrement.
|
||||
// rfc724_mid index is not recreated, because it is not used.
|
||||
sql.execute_migration(
|
||||
"DROP TABLE smtp;
|
||||
CREATE TABLE smtp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
rfc724_mid TEXT NOT NULL, -- Message-ID
|
||||
mime TEXT NOT NULL, -- SMTP payload
|
||||
msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
|
||||
recipients TEXT NOT NULL, -- List of recipients separated by space
|
||||
retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
|
||||
);
|
||||
",
|
||||
101,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
|
||||
@@ -50,6 +50,9 @@ pub struct Summary {
|
||||
|
||||
/// Message state.
|
||||
pub state: MessageState,
|
||||
|
||||
/// Message preview image path
|
||||
pub thumbnail_path: Option<String>,
|
||||
}
|
||||
|
||||
impl Summary {
|
||||
@@ -90,11 +93,22 @@ impl Summary {
|
||||
text = stock_str::reply_noun(context).await
|
||||
}
|
||||
|
||||
let thumbnail_path = if msg.viewtype == Viewtype::Image
|
||||
|| msg.viewtype == Viewtype::Gif
|
||||
|| msg.viewtype == Viewtype::Sticker
|
||||
{
|
||||
msg.get_file(context)
|
||||
.and_then(|path| path.to_str().map(|p| p.to_owned()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
prefix,
|
||||
text,
|
||||
timestamp: msg.get_timestamp(),
|
||||
state: msg.state,
|
||||
thumbnail_path,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
113
src/webxdc.rs
113
src/webxdc.rs
@@ -4,6 +4,7 @@ use std::convert::TryFrom;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, format_err, Result};
|
||||
|
||||
use deltachat_derive::FromSql;
|
||||
use lettre_email::mime;
|
||||
use lettre_email::PartBuilder;
|
||||
@@ -143,17 +144,25 @@ struct StatusUpdates {
|
||||
|
||||
/// Update items as sent on the wire and as stored in the database.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct StatusUpdateItem {
|
||||
pub(crate) payload: Value,
|
||||
pub struct StatusUpdateItem {
|
||||
/// The playload of the status update.
|
||||
pub payload: Value,
|
||||
|
||||
/// Optional short info message that will be displayed in the chat.
|
||||
/// For example "Alice added an item" or "Bob voted for option x".
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) info: Option<String>,
|
||||
pub info: Option<String>,
|
||||
|
||||
/// The new name of the editing document.
|
||||
/// This is not needed if the webxdc doesn't edit documents.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) document: Option<String>,
|
||||
pub document: Option<String>,
|
||||
|
||||
/// Optional summary of the status update which will be shown next to the
|
||||
/// app icon. This should be short and can be something like "8 votes"
|
||||
/// for a voting app.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) summary: Option<String>,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
/// Update items as passed to the UIs.
|
||||
@@ -230,14 +239,14 @@ impl Context {
|
||||
let valid = match async_zip::read::fs::ZipFileReader::new(path).await {
|
||||
Ok(archive) => {
|
||||
if find_zip_entry(archive.file(), "index.html").is_none() {
|
||||
info!(self, "{} misses index.html", filename);
|
||||
warn!(self, "{} misses index.html", filename);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
info!(self, "{} cannot be opened as zip-file", filename);
|
||||
warn!(self, "{} cannot be opened as zip-file", filename);
|
||||
false
|
||||
}
|
||||
};
|
||||
@@ -289,23 +298,11 @@ impl Context {
|
||||
async fn create_status_update_record(
|
||||
&self,
|
||||
instance: &mut Message,
|
||||
update_str: &str,
|
||||
status_update_item: StatusUpdateItem,
|
||||
timestamp: i64,
|
||||
can_info_msg: bool,
|
||||
from_id: ContactId,
|
||||
) -> Result<StatusUpdateSerial> {
|
||||
let update_str = strip_rtlo_characters(update_str.trim());
|
||||
if update_str.is_empty() {
|
||||
bail!("create_status_update_record: empty update.");
|
||||
}
|
||||
|
||||
let status_update_item: StatusUpdateItem =
|
||||
if let Ok(item) = serde_json::from_str::<StatusUpdateItem>(&update_str) {
|
||||
item
|
||||
} else {
|
||||
bail!("create_status_update_record: no valid update item.");
|
||||
};
|
||||
|
||||
if can_info_msg {
|
||||
if let Some(ref info) = status_update_item.info {
|
||||
if let Some(info_msg_id) =
|
||||
@@ -404,6 +401,26 @@ impl Context {
|
||||
instance_msg_id: MsgId,
|
||||
update_str: &str,
|
||||
descr: &str,
|
||||
) -> Result<()> {
|
||||
let status_update_item: StatusUpdateItem =
|
||||
if let Ok(item) = serde_json::from_str::<StatusUpdateItem>(update_str) {
|
||||
item
|
||||
} else {
|
||||
bail!("create_status_update_record: no valid update item.");
|
||||
};
|
||||
|
||||
self.send_webxdc_status_update_struct(instance_msg_id, status_update_item, descr)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a status update for an webxdc instance.
|
||||
/// Also see [Self::send_webxdc_status_update]
|
||||
pub async fn send_webxdc_status_update_struct(
|
||||
&self,
|
||||
instance_msg_id: MsgId,
|
||||
status_update: StatusUpdateItem,
|
||||
descr: &str,
|
||||
) -> Result<()> {
|
||||
let mut instance = Message::load_from_db(self, instance_msg_id).await?;
|
||||
if instance.viewtype != Viewtype::Webxdc {
|
||||
@@ -420,10 +437,10 @@ impl Context {
|
||||
MessageState::Undefined | MessageState::OutPreparing | MessageState::OutDraft
|
||||
);
|
||||
|
||||
let status_update_serial = self
|
||||
let status_update_serial: StatusUpdateSerial = self
|
||||
.create_status_update_record(
|
||||
&mut instance,
|
||||
update_str,
|
||||
status_update,
|
||||
create_smeared_timestamp(self),
|
||||
send_now,
|
||||
ContactId::SELF,
|
||||
@@ -552,7 +569,7 @@ impl Context {
|
||||
for update_item in updates.updates {
|
||||
self.create_status_update_record(
|
||||
&mut instance,
|
||||
&serde_json::to_string(&update_item)?,
|
||||
update_item,
|
||||
timestamp,
|
||||
can_info_msg,
|
||||
from_id,
|
||||
@@ -801,6 +818,8 @@ impl Message {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{
|
||||
add_contact_to_chat, create_broadcast_list, create_group_chat, forward_msgs,
|
||||
@@ -1235,7 +1254,12 @@ mod tests {
|
||||
let update_id1 = t
|
||||
.create_status_update_record(
|
||||
&mut instance,
|
||||
"\n\n{\"payload\": {\"foo\":\"bar\"}}\n",
|
||||
StatusUpdateItem {
|
||||
payload: json!({"foo": "bar"}),
|
||||
info: None,
|
||||
document: None,
|
||||
summary: None,
|
||||
},
|
||||
1640178619,
|
||||
true,
|
||||
ContactId::SELF,
|
||||
@@ -1248,19 +1272,15 @@ mod tests {
|
||||
);
|
||||
|
||||
assert!(t
|
||||
.create_status_update_record(&mut instance, "\n\n\n", 1640178619, true, ContactId::SELF)
|
||||
.send_webxdc_status_update(instance.id, "\n\n\n", "")
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
assert!(t
|
||||
.create_status_update_record(
|
||||
&mut instance,
|
||||
"bad json",
|
||||
1640178619,
|
||||
true,
|
||||
ContactId::SELF
|
||||
)
|
||||
.send_webxdc_status_update(instance.id, "bad json", "")
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
assert_eq!(
|
||||
t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
|
||||
.await?,
|
||||
@@ -1270,7 +1290,12 @@ mod tests {
|
||||
let update_id2 = t
|
||||
.create_status_update_record(
|
||||
&mut instance,
|
||||
r#"{"payload" : { "foo2":"bar2"}}"#,
|
||||
StatusUpdateItem {
|
||||
payload: json!({"foo2": "bar2"}),
|
||||
info: None,
|
||||
document: None,
|
||||
summary: None,
|
||||
},
|
||||
1640178619,
|
||||
true,
|
||||
ContactId::SELF,
|
||||
@@ -1282,7 +1307,12 @@ mod tests {
|
||||
);
|
||||
t.create_status_update_record(
|
||||
&mut instance,
|
||||
r#"{"payload":true}"#,
|
||||
StatusUpdateItem {
|
||||
payload: Value::Bool(true),
|
||||
info: None,
|
||||
document: None,
|
||||
summary: None,
|
||||
},
|
||||
1640178619,
|
||||
true,
|
||||
ContactId::SELF,
|
||||
@@ -1296,15 +1326,12 @@ mod tests {
|
||||
{"payload":true,"serial":3,"max_serial":3}]"#
|
||||
);
|
||||
|
||||
let _update_id3 = t
|
||||
.create_status_update_record(
|
||||
&mut instance,
|
||||
r#"{"payload" : 1, "sender": "that is not used"}"#,
|
||||
1640178619,
|
||||
true,
|
||||
ContactId::SELF,
|
||||
)
|
||||
.await?;
|
||||
t.send_webxdc_status_update(
|
||||
instance.id,
|
||||
r#"{"payload" : 1, "sender": "that is not used"}"#,
|
||||
"",
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
t.get_webxdc_status_updates(instance.id, update_id2).await?,
|
||||
r#"[{"payload":true,"serial":3,"max_serial":4},
|
||||
|
||||
Reference in New Issue
Block a user