Compare commits

..

133 Commits

Author SHA1 Message Date
dignifiedquire
c2e275cb96 array backed pool 2023-02-18 16:26:58 +01:00
link2xt
b2c5560e30 Remove try_many_times r2d2 bug workaround 2023-02-18 01:57:19 +00:00
link2xt
bfd3c1763d Replace r2d2 with an own connection pool
New connection pool does not use threads
and does not remove idle connections
or create new connections at runtime.
2023-02-17 21:12:17 +00:00
link2xt
7586bcf45e Update rusqlite to 0.28 2023-02-17 14:48:33 +00:00
link2xt
870527de1e Remove r2d2_sqlite dependency 2023-02-17 11:06:17 +00:00
link2xt
a34aeb240a Increase database timeout to 60 seconds 2023-02-16 18:37:47 +00:00
link2xt
6ee165fddc python: type annotations for testplugin.py 2023-02-16 17:48:29 +00:00
link2xt
c9db41a7f6 python: build Python 3.11 wheels 2023-02-16 14:36:23 +00:00
link2xt
7a6bfae93b Fix typo 2023-02-16 11:42:59 +00:00
link2xt
ae19c9b331 Add more documentation 2023-02-15 21:56:33 +00:00
link2xt
7d2cca8633 sql: enable auto_vacuum on all connections 2023-02-15 18:59:47 +00:00
link2xt
c52b48b0f5 sql: update version first in migration transactions
In execute_migration transaction first update the version, and only
then execute the rest of the migration. This ensures that transaction
is immediately recognized as write transaction and there is no need
to promote it from read transaction to write transaction later, e.g.
in the case of "DROP TABLE IF EXISTS" that is a read only operation if
the table does not exist. Promoting a read transaction to write
transaction may result in an error if database is locked.
2023-02-15 18:58:44 +00:00
link2xt
893794f4e7 accounts: retry filesystem operations in migrate_account() 2023-02-15 18:57:37 +00:00
link2xt
032da4fb1a python: add py.typed file
It marks the package as supporting typing according to PEP 561 <https://peps.python.org/pep-0561/>

Also remove zip-safe option from setuptools configuration for deltachat-rpc-client.
It is deprecated in setuptools and not used for wheel package format according to <https://setuptools.pypa.io/en/latest/deprecated/zip_safe.html>
2023-02-15 18:56:34 +00:00
Simon Laux
0de5125de8 jsonrpc: get_messages now returns a map with MessageLoadResult instead of failing completely if one of the requested messages could not be loaded. (#4038)
* jsonrpc: `get_messages` now returns a map with `MessageLoadResult`
instead of failing completely if one of the requested messages could not be loaded.

* add pr number to changelog

* format errors with causes instead of debug output

also for chatlistitemfetchresult
2023-02-15 18:46:34 +00:00
link2xt
cd3f1fe874 python: autoformat with black 2023-02-15 17:15:43 +00:00
link2xt
4f68e94fb3 python: use f-strings instead of percent-encoding 2023-02-15 16:33:05 +00:00
link2xt
b3ecbbc8b3 python: do not import print function in tests 2023-02-15 16:32:19 +00:00
link2xt
01653a881a python: do not import print function 2023-02-15 16:28:16 +00:00
link2xt
e021a59b87 python: do not inherit from object 2023-02-15 16:27:34 +00:00
link2xt
243f28f8a5 python: use dataclasses to reduce boilerplate 2023-02-15 12:35:52 +00:00
link2xt
78577594d0 deltachat-rpc-server: spawn request handlers 2023-02-15 12:19:03 +00:00
link2xt
d3e2f38da0 deltachat-jsonrpc: fix "prettier" arguments
**.ts finds .ts files only in the current directory and no *.js files.
2023-02-15 11:18:58 +00:00
link2xt
05f0fe0a88 Derive Default where possible 2023-02-14 21:33:02 +00:00
link2xt
e11d7c0444 Remove unnecessary .into_iter() 2023-02-14 21:04:29 +00:00
link2xt
a203cde400 Remove TestHeader from non-test builds 2023-02-14 11:00:04 +00:00
link2xt
00c14dd9f6 Move changelog entry to unreleased section 2023-02-14 10:58:43 +00:00
link2xt
71d9716117 Remove MimeMessage::from_bytes()
It was not used anywhere except the tests.
2023-02-14 10:57:57 +00:00
link2xt
2e4f63a290 Add Unreleased changelog section 2023-02-14 01:54:22 +00:00
link2xt
9dee725895 Fix header levels for 1.108.0 changelog 2023-02-14 01:53:57 +00:00
link2xt
267263dab7 Release 1.108.0 2023-02-13 21:15:34 +00:00
link2xt
1d81457f38 Remove unused ConfiguredE2EEEnabled key
`e2ee_enabled` is always used without `configured_` prefix.
2023-02-13 21:14:47 +00:00
link2xt
0885cad089 Add deltachat-repl to scripts/set_core_version.py 2023-02-13 20:32:25 +00:00
iequidoo
19d7632be0 Show non-deltachat emails by default for new installations 2023-02-13 14:48:53 -03:00
link2xt
2a5fa9a0d3 Documentation improvements 2023-02-13 14:01:24 +00:00
link2xt
bb702a9342 Remove outdated comment 2023-02-13 10:20:41 +00:00
link2xt
6c8368fa23 jsonrpc: add API to check if the message is sent by a bot
Co-Authored-By: Asiel Díaz Benítez <asieldbenitez@gmail.com>
2023-02-12 18:53:26 +00:00
link2xt
1c875209f7 python: create test accounts in parallel 2023-02-12 18:50:01 +00:00
link2xt
82da09760c ci: use --workspace instead of deprecated --all flag 2023-02-12 16:38:09 +00:00
link2xt
ef44aa0bd0 ci: there are no staging and trying branches 2023-02-12 16:14:10 +00:00
link2xt
af5a3235fd python: save references to asyncio tasks to avoid GC
Otherwise the task may be garbage collected
and cancelled.

See <https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task> for reference.
2023-02-12 15:13:54 +00:00
link2xt
07c510c178 Remove bitflags from get_chat_msgs() interface
get_chat_msgs() function is split into new get_chat_msgs() without flags
and get_chat_msgs_ex() which accepts booleans instead of bitflags.

FFI call dc_get_chat_msgs() is still using bitflags for compatibility.

JSON-RPC calls get_message_ids() and get_message_list_items()
accept booleans instead of bitflags now.
2023-02-12 15:11:04 +00:00
link2xt
c367bb63d1 ci: name run_clippy job 2023-02-12 12:19:15 +00:00
link2xt
819d658531 ci: don't use unmaintained actions-rs/toolchain
Also fix clippy version to prevent new clippy releases from breaking CI.
clippy version has to be updated manually now.
2023-02-12 11:45:27 +00:00
Asiel Díaz Benítez
48f098482e Merge pull request #4023 from deltachat/adb/issue-2266
capture unexpected exceptions in EventThread
2023-02-11 22:00:02 -05:00
link2xt
1a49bc85b8 Add scripts/clippy.sh
The script makes it easier to manually check REPL and benchmark code
with clippy without the need to copy-paste all the flags.
2023-02-11 16:07:52 +00:00
link2xt
51ee564246 ci: consistently capitalize job names 2023-02-11 16:06:32 +00:00
link2xt
7f313c803e ci: remove unnecessary Rust installation step
windows-latest image already contains Rust 1.67.0.
2023-02-11 15:25:20 +00:00
link2xt
0d7c33b1a9 Add missing "vendored" feature on deltachat-repl 2023-02-11 14:22:14 +00:00
link2xt
14f3abb51e ci: Windows repl.exe action fixup 2023-02-11 14:11:30 +00:00
link2xt
46143ac54f Move deltachat-repl into a separate crate 2023-02-11 13:54:49 +00:00
link2xt
6a30c0a997 Fix code style with black 2023-02-11 09:21:25 +00:00
link2xt
d1702e3081 python: display the diff on black failures 2023-02-11 09:13:42 +00:00
link2xt
fa198c3b5e Use SOCKS5 configuration for HTTP requests 2023-02-10 23:20:11 +00:00
link2xt
151b34ea79 python: handle NULL value returned from dc_get_msg
Returning None in this case and checked with mypy that callers can handle it.
2023-02-10 23:17:42 +00:00
adbenitez
3e8687e464 capture unexpected exceptions in EventThread 2023-02-10 14:43:32 -05:00
link2xt
c9b2ad4ffa Prefer TLS over STARTTLS during autoconfiguration 2023-02-10 11:28:11 +00:00
link2xt
386b5bb848 Update flate2 dependency
Get rid of minize_oxide@0.5.3
2023-02-09 10:26:00 +00:00
link2xt
d8bd189175 Bump openssl-src from 111.24.0+1.1.1s to 111.25.0+1.1.1t 2023-02-09 10:18:47 +00:00
link2xt
817760a6ef python: upgrade from .format() to f-strings
They are supported since Python 3.5.
2023-02-08 15:44:34 +00:00
link2xt
315e944b69 python: cut text in Message representation to 100 characters 2023-02-08 12:49:18 +00:00
dependabot[bot]
48722a22c9 Merge pull request #4012 from deltachat/dependabot/cargo/fuzz/tokio-1.25.0 2023-02-08 12:24:35 +00:00
dependabot[bot]
a8f018a208 Bump tokio from 1.24.1 to 1.25.0 in /fuzz
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.24.1 to 1.25.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.24.1...tokio-1.25.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-08 00:12:52 +00:00
Hocuri
fa70d8da09 Re-enable DKIM-checks (#3935)
Re-enable keychange-denying when the From address is wrong

Reverts #3728
Closes #3735
Reopens #3700
2023-02-07 17:07:43 +01:00
link2xt
cd293e6f49 Update async-smtp to 0.8 2023-02-03 11:36:58 +00:00
dependabot[bot]
d178c4a91a Merge pull request #4008 from deltachat/dependabot/cargo/toml-0.7.1 2023-02-02 08:30:53 +00:00
dependabot[bot]
ff63ce0630 cargo: bump toml from 0.7.0 to 0.7.1
Bumps [toml](https://github.com/toml-rs/toml) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/toml-rs/toml/releases)
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.7.0...toml-v0.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 23:13:38 +00:00
dependabot[bot]
757b77786a Merge pull request #4009 from deltachat/dependabot/cargo/human-panic-1.1.0 2023-02-01 23:12:20 +00:00
dependabot[bot]
7a78449950 Merge pull request #4010 from deltachat/dependabot/cargo/uuid-1.3.0 2023-02-01 23:11:57 +00:00
dependabot[bot]
7b44b26e9e cargo: bump uuid from 1.2.2 to 1.3.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.2.2...1.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 21:02:05 +00:00
dependabot[bot]
3f5da7357f cargo: bump human-panic from 1.0.3 to 1.1.0
Bumps [human-panic](https://github.com/rust-cli/human-panic) from 1.0.3 to 1.1.0.
- [Release notes](https://github.com/rust-cli/human-panic/releases)
- [Changelog](https://github.com/rust-cli/human-panic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/human-panic/compare/v1.0.3...v1.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 21:01:40 +00:00
dependabot[bot]
8ac972856c Merge pull request #4001 from deltachat/dependabot/cargo/rustyline-10.1.1 2023-02-01 16:50:01 +00:00
dependabot[bot]
1f49fcc777 cargo: bump rustyline from 10.0.0 to 10.1.1
Bumps [rustyline](https://github.com/kkawakam/rustyline) from 10.0.0 to 10.1.1.
- [Release notes](https://github.com/kkawakam/rustyline/releases)
- [Changelog](https://github.com/kkawakam/rustyline/blob/master/History.md)
- [Commits](https://github.com/kkawakam/rustyline/compare/v10.0.0...v10.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 16:16:27 +00:00
link2xt
f2f5bfd17c Fix python file code style
New version of `black` complains otherwise.
2023-02-01 16:10:08 +00:00
link2xt
59f5cbe6f1 Merge from stable branch 2023-02-01 15:36:26 +00:00
link2xt
72e004c12b Release 1.107.1 2023-02-01 14:52:06 +00:00
link2xt
52a4b0c2b8 Revert to async-smtp 0.5 to disable SMTP pipelining 2023-02-01 14:43:22 +00:00
link2xt
74abb82de2 Log server security (TLS/STARTTLS/plain) type 2023-02-01 00:01:47 +00:00
link2xt
e318f5c697 Simplify unset_empty() 2023-01-31 23:58:38 +00:00
link2xt
b62c61329a Update to base64 0.21 2023-01-31 19:24:20 +00:00
dependabot[bot]
1e71d8dcc8 Merge pull request #3997 from deltachat/dependabot/cargo/toml-0.7.0 2023-01-31 14:40:19 +00:00
link2xt
f342dc8196 Update for new toml API 2023-01-31 13:12:54 +00:00
link2xt
87c333ff7a Always optimize dependencies
They are only built once, but the time of
`cargo nextest run` is reduced by around 40% as a result.
2023-01-31 12:20:58 +01:00
Floris Bruynooghe
76893df5bd Remove the nightly PGP feature
This was to test pgp early on, but that's not deltachat's business.
If needed the PGP project can always do this with patching.
2023-01-31 11:54:48 +01:00
Asiel Díaz Benítez
c8453e2c81 Merge pull request #4002 from deltachat/adb/rpc-server-add-readme
add deltachat-rpc-server/README.md
2023-01-31 05:39:48 -05:00
dependabot[bot]
8ab4a4b82d Merge pull request #3993 from deltachat/dependabot/cargo/yerpc-0.4.0 2023-01-31 10:30:16 +00:00
adbenitez
e6fe814ada add deltachat-rpc-server/README.md 2023-01-31 05:08:59 -05:00
link2xt
e171a69240 Do not specify _send message type 2023-01-31 09:35:41 +00:00
dependabot[bot]
55cb11da07 cargo: bump yerpc from 0.3.1 to 0.4.0
Bumps [yerpc](https://github.com/Frando/yerpc) from 0.3.1 to 0.4.0.
- [Release notes](https://github.com/Frando/yerpc/releases)
- [Commits](https://github.com/Frando/yerpc/commits/v0.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-31 00:43:21 +00:00
dependabot[bot]
3104edba0f Merge pull request #3996 from deltachat/dependabot/cargo/axum-0.6.4 2023-01-31 00:42:32 +00:00
dependabot[bot]
3878389f2b Merge pull request #3999 from deltachat/dependabot/cargo/futures-0.3.26 2023-01-31 00:42:05 +00:00
dependabot[bot]
356a064dd1 cargo: bump axum from 0.6.1 to 0.6.4
Bumps [axum](https://github.com/tokio-rs/axum) from 0.6.1 to 0.6.4.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.6.1...axum-v0.6.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 22:48:33 +00:00
dependabot[bot]
a0ba866d5b cargo: bump futures from 0.3.25 to 0.3.26
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.25 to 0.3.26.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.25...0.3.26)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 22:48:32 +00:00
dependabot[bot]
ede63cd6be Merge pull request #3994 from deltachat/dependabot/cargo/reqwest-0.11.14 2023-01-30 22:47:04 +00:00
dependabot[bot]
9311d1fe44 Merge pull request #3998 from deltachat/dependabot/cargo/tokio-1.25.0 2023-01-30 22:46:26 +00:00
dependabot[bot]
11bfa2a813 Merge pull request #3995 from deltachat/dependabot/cargo/regex-1.7.1 2023-01-30 22:44:56 +00:00
dependabot[bot]
05d6bde362 cargo: bump tokio from 1.24.1 to 1.25.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.24.1 to 1.25.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.24.1...tokio-1.25.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 18:55:10 +00:00
dependabot[bot]
afcbbb3538 cargo: bump toml from 0.5.10 to 0.7.0
Bumps [toml](https://github.com/toml-rs/toml) from 0.5.10 to 0.7.0.
- [Release notes](https://github.com/toml-rs/toml/releases)
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.5.10...toml-v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 18:55:01 +00:00
dependabot[bot]
8fd117ee8c cargo: bump regex from 1.7.0 to 1.7.1
Bumps [regex](https://github.com/rust-lang/regex) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.7.0...1.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 18:54:40 +00:00
dependabot[bot]
d031e1a7e9 cargo: bump reqwest from 0.11.13 to 0.11.14
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.13 to 0.11.14.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.13...v0.11.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 18:54:31 +00:00
missytake
e83fa8840b python bindings account setup: remove deviation from default config 2023-01-30 17:46:13 +01:00
link2xt
fcf73165ed Inline format arguments
This feature has been stable since Rust 1.58.0.
2023-01-30 11:50:11 +03:00
link2xt
c911f1262a Remove ContextError type
Using anyhow instead.
2023-01-29 00:36:07 +00:00
link2xt
ba3e4c5dff Resultify tools::delete_file() 2023-01-29 00:35:15 +00:00
link2xt
ae564ef702 Add documentation 2023-01-28 21:30:43 +00:00
link2xt
7d3a591139 Make chunk_size variable immutable 2023-01-28 12:54:56 +00:00
link2xt
9b3f76ab5a Add remove_subject_prefix() test 2023-01-28 11:22:30 +00:00
link2xt
dab8acc7d8 Replace Result<_, Error> with Result<_> 2023-01-28 11:14:30 +00:00
link2xt
4eda53d5a1 Move SessionStream from imap to net 2023-01-27 15:51:04 +00:00
dependabot[bot]
8c0296ca67 Merge pull request #3971 from deltachat/dependabot/cargo/bumpalo-3.12.0 2023-01-27 14:59:33 +00:00
dependabot[bot]
7ef094325d cargo: bump bumpalo from 3.10.0 to 3.12.0
Bumps [bumpalo](https://github.com/fitzgen/bumpalo) from 3.10.0 to 3.12.0.
- [Release notes](https://github.com/fitzgen/bumpalo/releases)
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/3.10.0...3.12.0)

---
updated-dependencies:
- dependency-name: bumpalo
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-27 14:28:32 +00:00
link2xt
2365862615 Allow clippy::uninlined_format_args in examples 2023-01-27 09:57:00 +00:00
link2xt
e30749e94c Allow clippy::uninlined_format_args in deltachat-ffi 2023-01-27 09:32:41 +00:00
link2xt
c20dea64cf Allow clippy::uninlined_format_args in deltachat-rpc-server 2023-01-27 02:24:48 +00:00
link2xt
faef8b679e Allow clippy::uninlined_format_args in deltachat-jsonrpc
It started failing since release of Rust 1.67
2023-01-27 01:53:12 +00:00
link2xt
9b3e21225c ci: run clippy without actions-rs/clippy-check
actions-rs/clippy-check runs clippy with --message-format=json option
and converts the output into annotations.

This makes clippy output unreadable, it is JSON
and you cannot quickly find the line number to fix.

Annotations in the code review view look nice,
but on large PRs they are less usable because you need
to scroll the whole page to find all the annotations.
2023-01-26 18:13:13 +00:00
link2xt
7640e3255f ci: remove unnecessary rustfmt check steps
`cargo fmt` is already pre-installed in ubuntu-latest
2023-01-26 12:38:38 +00:00
iequidoo
4c5ed3df2c Add a test for verified groups with multiple devices on a joining side (#3836)
There was the following bug:
- Bob has two devices, the second is offline.
- Alice creates a verified group and sends a QR invitation to Bob.
- Bob joins the group.
- Bob's second devices goes online, but sees a contact request instead of the verified group.
- The "member added" message is not a system message but a plain text message.
- Sending a message fails as the key is missing -- message info says "proper enc-key for <Alice>
  missing, cannot encrypt".
2023-01-25 20:28:49 -03:00
iequidoo
39601be452 observe_securejoin_on_other_device(): Add handling of "v*-request-with-auth" messages (#3836)
This fixes the case with multiple devices on the joining side: if we observe such a message from
another device, we mark an inviter as verified and an accepted contact thus causing a subsequent
"vg-member-added" message -- in case of a verified group -- to create it properly.
2023-01-25 20:28:49 -03:00
iequidoo
76aaecb8f2 Add gossips to all Securejoin messages (#3836)
It worked before only because of the gossip-by-timeout logic in the branch above. And that is why
not always.
2023-01-25 20:28:49 -03:00
iequidoo
4b2faeedab ChatId::create_multiuser_record(): Log blocked of created chat 2023-01-25 20:28:49 -03:00
link2xt
0087e3748c Fix comment typo 2023-01-25 21:38:08 +00:00
link2xt
754ec33e4b Correct IMAP_TIMEOUT comment 2023-01-25 21:38:08 +00:00
link2xt
9f8b74adf9 Add missing ephemeral.rs documentation 2023-01-25 19:11:38 +00:00
link2xt
8916841e83 Make maybe_do_aeap_transition() private 2023-01-25 16:25:40 +00:00
link2xt
e7f21f41ee Do not pass Result as a function argument 2023-01-25 13:57:21 +00:00
Sebastian Klähn
ba860a2b61 Debug logging v2 (#3958)
debug logging
2023-01-25 13:22:15 +00:00
link2xt
c349a5c75b Add missing chatlist documentation 2023-01-24 10:11:38 +00:00
link2xt
d522b7ef1e mimefactory: do not check if the key exists before rendering Autocrypt-Gossip
`render_gossip_header()` returns `None` in this case anyway.
2023-01-24 10:06:52 +00:00
link2xt
800125c7a9 Add missing documentation to peerstate.rs 2023-01-24 10:06:52 +00:00
link2xt
37f20c6889 Prepare 1.107.0 2023-01-23 16:20:07 +00:00
link2xt
5dfe7bea8e Move rate limiter into its own crate 2023-01-23 14:53:50 +00:00
Sebastian Klähn
6067622c19 fix node readme (#3974)
fix node readme
2023-01-23 12:36:41 +01:00
149 changed files with 3530 additions and 2281 deletions

View File

@@ -5,8 +5,6 @@ on:
push:
branches:
- master
- staging
- trying
env:
RUSTFLAGS: -Dwarnings
@@ -18,31 +16,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- run: cargo fmt --all -- --check
run_clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: clippy
override: true
- name: Install clippy
run: rustup toolchain install 1.67.1 --component clippy
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --tests --examples --benches --features repl -- -D warnings
- name: Run clippy
env:
RUSTUP_TOOLCHAIN: 1.67.1
run: scripts/clippy.sh
docs:
name: Rust doc comments
@@ -52,13 +40,6 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install rust stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rust-docs
override: true
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- name: Rustdoc
@@ -90,39 +71,37 @@ jobs:
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.rust }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Install Rust ${{ matrix.rust }}
run: rustup toolchain install ${{ matrix.rust }}
- run: rustup override set ${{ matrix.rust }}
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- name: check
run: cargo check --all --bins --examples --tests --features repl --benches
- name: Check
run: cargo check --workspace --bins --examples --tests --benches
- name: tests
run: cargo test --all
- name: Tests
run: cargo test --workspace
- name: test cargo vendor
- name: Test cargo vendor
run: cargo vendor
- name: install python
- name: Install python
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: install tox
- name: Install tox
if: ${{ matrix.python }}
run: pip install tox
- name: build C library
- name: Build C library
if: ${{ matrix.python }}
run: cargo build -p deltachat_ffi --features jsonrpc
- name: run python tests
- name: Run python tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
@@ -131,28 +110,28 @@ jobs:
working-directory: python
run: tox -e lint,mypy,doc,py3
- name: build deltachat-rpc-server
- name: Build deltachat-rpc-server
if: ${{ matrix.python }}
run: cargo build -p deltachat-rpc-server
- name: add deltachat-rpc-server to path
- name: Add deltachat-rpc-server to path
if: ${{ matrix.python }}
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
- name: run deltachat-rpc-client tests
- name: Run deltachat-rpc-client tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
working-directory: deltachat-rpc-client
run: tox -e py3,lint
- name: install pypy
- name: Install pypy
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: 'pypy${{ matrix.python }}'
- name: run pypy tests
- name: Run pypy tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}

View File

@@ -12,14 +12,14 @@ jobs:
name: 'Package @deltachat/jsonrpc-client and upload to download.delta.chat'
runs-on: ubuntu-18.04
steps:
- name: install tree
- name: Install tree
run: sudo apt install tree
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- name: get tag
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
continue-on-error: true
@@ -38,11 +38,11 @@ jobs:
npm --version
node --version
echo $DELTACHAT_JSONRPC_TAR_GZ
- name: install dependencies without running scripts
- name: Install dependencies without running scripts
run: |
cd deltachat-jsonrpc/typescript
npm install --ignore-scripts
- name: package
- name: Package
shell: bash
run: |
cd deltachat-jsonrpc/typescript
@@ -65,7 +65,7 @@ jobs:
chmod 600 __TEMP_INPUT_KEY_FILE
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r deltachat-jsonrpc/typescript/$DELTACHAT_JSONRPC_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
continue-on-error: true
- name: "Post links to details"
- name: Post links to details
if: steps.upload-preview.outcome == 'success'
run: node ./node/scripts/postLinksToDetails.js
env:

View File

@@ -9,7 +9,7 @@ on:
jobs:
prebuild:
name: 'prebuild'
name: Prebuild
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -65,17 +65,17 @@ jobs:
pack-module:
needs: prebuild
name: 'Package deltachat-node and upload to download.delta.chat'
name: Package deltachat-node and upload to download.delta.chat
runs-on: ubuntu-18.04
steps:
- name: install tree
- name: Install tree
run: sudo apt install tree
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: get tag
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
continue-on-error: true
@@ -97,15 +97,15 @@ jobs:
npm --version
node --version
echo $DELTACHAT_NODE_TAR_GZ
- name: Download ubuntu prebuild
- name: Download Ubuntu prebuild
uses: actions/download-artifact@v1
with:
name: ubuntu-18.04
- name: Download macos prebuild
- name: Download macOS prebuild
uses: actions/download-artifact@v1
with:
name: macos-latest
- name: Download windows prebuild
- name: Download Windows prebuild
uses: actions/download-artifact@v1
with:
name: windows-latest
@@ -117,23 +117,23 @@ jobs:
tar -xvzf windows-latest/windows-latest.tar.gz -C node/prebuilds
tree node/prebuilds
rm -rf ubuntu-18.04 macos-latest windows-latest
- name: install dependencies without running scripts
- name: Install dependencies without running scripts
run: |
npm install --ignore-scripts
- name: build constants
- name: Build constants
run: |
npm run build:core:constants
- name: build typescript part
- name: Build TypeScript part
run: |
npm run build:bindings:ts
- name: package
- name: Package
shell: bash
run: |
mv node/README.md README.md
npm pack .
ls -lah
mv $(find deltachat-node-*) $DELTACHAT_NODE_TAR_GZ
- name: Upload Prebuild
- name: Upload prebuild
uses: actions/upload-artifact@v3
with:
name: deltachat-node.tgz
@@ -148,7 +148,7 @@ jobs:
chmod 600 __TEMP_INPUT_KEY_FILE
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
continue-on-error: true
- name: "Post links to details"
- name: Post links to details
if: steps.upload-preview.outcome == 'success'
run: node ./node/scripts/postLinksToDetails.js
env:

View File

@@ -9,7 +9,7 @@ on:
jobs:
tests:
name: 'tests'
name: Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:

View File

@@ -13,17 +13,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.66.0
override: true
- name: build
run: cargo build --example repl --features repl,vendored
- name: Build
run: cargo build -p deltachat-repl --features vendored
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: repl.exe
path: 'target/debug/examples/repl.exe'
path: 'target/debug/deltachat-repl.exe'

View File

@@ -2,9 +2,59 @@
## Unreleased
### Changes
- deltachat-rpc-client: use `dataclass` for `Account`, `Chat`, `Contact` and `Message` #4042
- python: mark bindings as supporting typing according to PEP 561 #4045
- retry filesystem operations during account migration #4043
- replace `r2d2` and `r2d2_sqlite` dependencies with an own connection pool #4050 #4053
### Fixes
- deltachat-rpc-server: do not block stdin while processing the request. #4041
deltachat-rpc-server now reads the next request as soon as previous request handler is spawned.
- enable `auto_vacuum` on all SQL connections #2955
### API-Changes
- Remove `MimeMessage::from_bytes()` public interface. #4033
- BREAKING Types: jsonrpc: `get_messages` now returns a map with `MessageLoadResult` instead of failing completely if one of the requested messages could not be loaded. #4038
## 1.108.0
### Changes
- Use read/write timeouts instead of per-command timeouts for SMTP #3985
- Cache DNS results for SMTP connections #3985
- Prefer TLS over STARTTLS during autoconfiguration #4021
- Use SOCKS5 configuration for HTTP requests #4017
- Show non-deltachat emails by default for new installations #4019
### Fixes
- Fix Securejoin for multiple devices on a joining side #3982
- python: handle NULL value returned from `dc_get_msg()` #4020
Account.`get_message_by_id` may return `None` in this case.
### API-Changes
- Remove bitflags from `get_chat_msgs()` interface #4022
C interface is not changed.
Rust and JSON-RPC API have `flags` integer argument
replaced with two boolean flags `info_only` and `add_daymarker`.
- jsonrpc: add API to check if the message is sent by a bot #3877
## 1.107.1
### Changes
- Log server security (TLS/STARTTLS/plain) type #4005
### Fixes
- Disable SMTP pipelining #4006
## 1.107.0
### Changes
- Pipeline SMTP commands #3924
- Cache DNS results #3970
- Cache DNS results for IMAP connections #3970
### Fixes
- Securejoin: Fix adding and handling Autocrypt-Gossip headers #3914
@@ -20,6 +70,8 @@
- jsonrpc: add verified-by information to `Contact`-Object
- Remove `attach_selfavatar` config #3951
### Changes
- add debug logging support for webxdcs #3296
## 1.106.0
@@ -57,13 +109,13 @@
### Fixes
- Do not add an error if the message is encrypted but not signed #3860
- Do not strip leading spaces from message lines #3867
- Don't always rebuild group member lists #3872
- Fix uncaught exception in JSON-RPC tests #3884
- Fix STARTTLS connection and add a test for it #3907
- Trigger reconnection when failing to fetch existing messages #3911
- Do not retry fetching existing messages after failure, prevents infinite reconnection loop #3913
- Ensure format=flowed formatting is always reversible on the receiver side #3880
## 1.104.0
### Changes

365
Cargo.lock generated
View File

@@ -165,21 +165,18 @@ dependencies = [
[[package]]
name = "async-smtp"
version = "0.6.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ade89127f9e0d44f9e83cf574d499060005cd45b7dc76be89c0167487fe8edd"
checksum = "7384febcabdd07a498c9f4fbaa7e488ff4eb60d0ade14b47b09ec44b8f645301"
dependencies = [
"async-native-tls",
"async-trait",
"anyhow",
"base64 0.13.1",
"bufstream",
"fast-socks5",
"futures",
"hostname",
"log",
"nom 7.1.1",
"pin-project",
"pin-utils",
"thiserror",
"tokio",
]
@@ -262,7 +259,7 @@ dependencies = [
"sha-1",
"sync_wrapper",
"tokio",
"tokio-tungstenite",
"tokio-tungstenite 0.17.2",
"tower",
"tower-http",
"tower-layer",
@@ -271,13 +268,13 @@ dependencies = [
[[package]]
name = "axum"
version = "0.6.1"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48"
checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc"
dependencies = [
"async-trait",
"axum-core 0.3.0",
"base64 0.13.1",
"axum-core 0.3.2",
"base64 0.20.0",
"bitflags",
"bytes",
"futures-util",
@@ -295,10 +292,10 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sha-1",
"sha1",
"sync_wrapper",
"tokio",
"tokio-tungstenite",
"tokio-tungstenite 0.18.0",
"tower",
"tower-http",
"tower-layer",
@@ -323,9 +320,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.3.0"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92"
checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
dependencies = [
"async-trait",
"bytes",
@@ -348,7 +345,7 @@ dependencies = [
"cc",
"cfg-if",
"libc",
"miniz_oxide 0.6.2",
"miniz_oxide",
"object",
"rustc-demangle",
]
@@ -377,6 +374,12 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
version = "1.5.1"
@@ -450,9 +453,9 @@ checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]]
name = "bumpalo"
version = "3.10.0"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byte-pool"
@@ -618,6 +621,23 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "concolor"
version = "0.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "318d6c16e73b3a900eb212ad6a82fc7d298c5ab8184c7a9998646455bc474a16"
dependencies = [
"bitflags",
"concolor-query",
"is-terminal",
]
[[package]]
name = "concolor-query"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317"
[[package]]
name = "concurrent-queue"
version = "2.0.0"
@@ -873,7 +893,7 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "deltachat"
version = "1.106.0"
version = "1.108.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -883,12 +903,11 @@ dependencies = [
"async-smtp",
"async_zip",
"backtrace",
"base64 0.20.0",
"base64 0.21.0",
"bitflags",
"chrono",
"criterion",
"deltachat_derive",
"dirs",
"email",
"encoded-words",
"escaper",
@@ -909,20 +928,19 @@ dependencies = [
"num-traits",
"num_cpus",
"once_cell",
"parking_lot",
"percent-encoding",
"pgp",
"pretty_env_logger",
"proptest",
"qrcodegen",
"quick-xml",
"r2d2",
"r2d2_sqlite",
"rand 0.8.5",
"ratelimit",
"regex",
"reqwest",
"rusqlite",
"rust-hsluv",
"rustyline",
"sanitize-filename",
"serde",
"serde_json",
@@ -939,19 +957,19 @@ dependencies = [
"tokio-io-timeout",
"tokio-stream",
"tokio-tar",
"toml",
"toml 0.7.1",
"trust-dns-resolver",
"url",
"uuid 1.2.2",
"uuid 1.3.0",
]
[[package]]
name = "deltachat-jsonrpc"
version = "1.106.0"
version = "1.108.0"
dependencies = [
"anyhow",
"async-channel",
"axum 0.6.1",
"axum 0.6.4",
"deltachat",
"env_logger 0.10.0",
"futures",
@@ -967,9 +985,24 @@ dependencies = [
"yerpc",
]
[[package]]
name = "deltachat-repl"
version = "1.108.0"
dependencies = [
"ansi_term",
"anyhow",
"deltachat",
"dirs",
"log",
"pretty_env_logger",
"rusqlite",
"rustyline",
"tokio",
]
[[package]]
name = "deltachat-rpc-server"
version = "1.106.0"
version = "1.108.0"
dependencies = [
"anyhow",
"deltachat-jsonrpc",
@@ -992,7 +1025,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.106.0"
version = "1.108.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1414,12 +1447,12 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide 0.5.3",
"miniz_oxide",
]
[[package]]
@@ -1458,9 +1491,9 @@ version = "1.0.0"
[[package]]
name = "futures"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@@ -1473,9 +1506,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@@ -1483,15 +1516,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@@ -1500,9 +1533,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-lite"
@@ -1521,9 +1554,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
@@ -1532,21 +1565,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@@ -1633,15 +1666,6 @@ version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1653,11 +1677,11 @@ dependencies = [
[[package]]
name = "hashlink"
version = "0.7.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
dependencies = [
"hashbrown 0.11.2",
"hashbrown",
]
[[package]]
@@ -1743,16 +1767,17 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "human-panic"
version = "1.0.3"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39f357a500abcbd7c5f967c1d45c8838585b36743823b9d43488f24850534e36"
checksum = "87eb03e654582b31967d414b86711a7bbd7c6b28a6b7d32857b7d1d45c0926f9"
dependencies = [
"backtrace",
"os_type",
"concolor",
"os_info",
"serde",
"serde_derive",
"termcolor",
"toml",
"toml 0.5.11",
"uuid 0.8.2",
]
@@ -1889,7 +1914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"hashbrown",
]
[[package]]
@@ -2048,9 +2073,9 @@ checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565"
[[package]]
name = "libsqlite3-sys"
version = "0.24.2"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa"
dependencies = [
"cc",
"openssl-sys",
@@ -2175,15 +2200,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
@@ -2240,10 +2256,11 @@ dependencies = [
[[package]]
name = "nix"
version = "0.24.2"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"libc",
@@ -2269,6 +2286,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.1"
@@ -2411,9 +2437,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.22.0+1.1.1q"
version = "111.25.0+1.1.1t"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853"
checksum = "3173cd3626c43e3854b1b727422a276e568d9ec5fe8cec197822cf52cfb743d6"
dependencies = [
"cc",
]
@@ -2432,21 +2458,23 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "os_info"
version = "2.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc1b4330bb29087e791ae2a5cf56be64fb8946a4ff5aec2ba11c6ca51f5d60"
dependencies = [
"log",
"serde",
"winapi",
]
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "os_type"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3df761f6470298359f84fcfb60d86db02acc22c251c37265c07a3d1057d2389"
dependencies = [
"regex",
]
[[package]]
name = "ouroboros"
version = "0.15.2"
@@ -2656,7 +2684,7 @@ dependencies = [
"bitflags",
"crc32fast",
"flate2",
"miniz_oxide 0.6.2",
"miniz_oxide",
]
[[package]]
@@ -2767,27 +2795,6 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20f14e071918cbeefc5edc986a7aa92c425dae244e003a35e1cdddb5ca39b5cb"
[[package]]
name = "r2d2"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
dependencies = [
"log",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "r2d2_sqlite"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fdc8e4da70586127893be32b7adf21326a4c6b1aba907611edf467d13ffe895"
dependencies = [
"r2d2",
"rusqlite",
]
[[package]]
name = "radix_trie"
version = "0.2.1"
@@ -2878,6 +2885,10 @@ dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "ratelimit"
version = "1.0.0"
[[package]]
name = "rayon"
version = "1.5.3"
@@ -2924,9 +2935,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.0"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
@@ -2950,11 +2961,11 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.13"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
dependencies = [
"base64 0.13.1",
"base64 0.21.0",
"bytes",
"encoding_rs",
"futures-core",
@@ -3027,16 +3038,15 @@ dependencies = [
[[package]]
name = "rusqlite"
version = "0.27.0"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"memchr",
"smallvec",
]
@@ -3088,9 +3098,9 @@ checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
[[package]]
name = "rustyline"
version = "10.0.0"
version = "10.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1cd5ae51d3f7bf65d7969d579d502168ef578f289452bd8ccc91de28fda20e"
checksum = "c1e83c32c3f3c33b08496e0d1df9ea8c64d39adb8eb36a1ebb1440c690697aef"
dependencies = [
"bitflags",
"cfg-if",
@@ -3150,15 +3160,6 @@ dependencies = [
"windows-sys 0.36.1",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf"
dependencies = [
"parking_lot",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@@ -3228,6 +3229,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -3544,9 +3554,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.24.1"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg",
"bytes",
@@ -3628,7 +3638,19 @@ dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
"tungstenite 0.17.3",
]
[[package]]
name = "tokio-tungstenite"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite 0.18.0",
]
[[package]]
@@ -3647,13 +3669,47 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.10"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90a238ee2e6ede22fb95350acc78e21dc40da00bb66c0334bde83de4ed89424e"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
]
[[package]]
name = "tower"
version = "0.4.13"
@@ -3804,6 +3860,25 @@ dependencies = [
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788"
dependencies = [
"base64 0.13.1",
"byteorder",
"bytes",
"http",
"httparse",
"log",
"rand 0.8.5",
"sha1",
"thiserror",
"url",
"utf-8",
]
[[package]]
name = "twofish"
version = "0.7.1"
@@ -3861,7 +3936,7 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
dependencies = [
"hashbrown 0.12.3",
"hashbrown",
"regex",
]
@@ -3926,9 +4001,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.2.2"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
dependencies = [
"getrandom 0.2.7",
"serde",
@@ -4256,9 +4331,9 @@ dependencies = [
[[package]]
name = "yerpc"
version = "0.3.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1baa6fce4cf16d1cff91b557baceac3e363106f66e555fff906a7f82dce8153"
checksum = "3d383dfbc1842f5b915a01deeaea3523eef8653fcd734f79f6c696d51521c70f"
dependencies = [
"anyhow",
"async-channel",
@@ -4278,9 +4353,9 @@ dependencies = [
[[package]]
name = "yerpc_derive"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f944ca6789bc55ddc86839478f6d49c9d2a66e130f69fd1f8d171b3108990"
checksum = "cd5e0da8e7a58236986d9032bad52f30995999f94cd39142aa1144b5875e8bce"
dependencies = [
"convert_case",
"darling 0.14.1",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.106.0"
version = "1.108.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.63"
@@ -13,6 +13,12 @@ opt-level = 1
[profile.test]
opt-level = 0
# Always optimize dependencies.
# This does not apply to crates in the workspace.
# <https://doc.rust-lang.org/cargo/reference/profiles.html#overrides>
[profile.dev.package."*"]
opt-level = 3
[profile.release]
lto = true
panic = 'abort'
@@ -20,20 +26,19 @@ panic = 'abort'
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
format-flowed = { path = "./format-flowed" }
ratelimit = { path = "./deltachat-ratelimit" }
ansi_term = { version = "0.12.1", optional = true }
anyhow = "1"
async-imap = { git = "https://github.com/async-email/async-imap", branch = "master", default-features = false, features = ["runtime-tokio"] }
async-native-tls = { version = "0.4", default-features = false, features = ["runtime-tokio"] }
async-smtp = { version = "0.6", default-features = false, features = ["smtp-transport", "socks5", "runtime-tokio"] }
async-smtp = { version = "0.8", default-features = false, features = ["runtime-tokio"] }
trust-dns-resolver = "0.22"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
backtrace = "0.3"
base64 = "0.20"
base64 = "0.21"
bitflags = "1.3"
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
dirs = { version = "4", optional=true }
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
escaper = "0.1"
@@ -43,24 +48,21 @@ image = { version = "0.24.5", default-features=false, features = ["gif", "jpeg",
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"
log = {version = "0.4.16", optional = true }
mailparse = "0.14"
native-tls = "0.2"
num_cpus = "1.15"
num-derive = "0.3"
num-traits = "0.2"
once_cell = "1.17.0"
parking_lot = "0.12"
percent-encoding = "2.2"
pgp = { version = "0.9", default-features = false }
pretty_env_logger = { version = "0.4", optional = true }
quick-xml = "0.27"
r2d2 = "0.8"
r2d2_sqlite = "0.20"
rand = "0.8"
regex = "1.7"
rusqlite = { version = "0.27", features = ["sqlcipher"] }
rusqlite = { version = "0.28", features = ["sqlcipher"] }
rust-hsluv = "0.1"
rustyline = { version = "10", optional = true }
sanitize-filename = "0.4"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
@@ -70,7 +72,7 @@ smallvec = "1"
strum = "0.24"
strum_macros = "0.24"
thiserror = "1"
toml = "0.5"
toml = "0.7"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
fast-socks5 = "0.8"
@@ -82,7 +84,7 @@ async-channel = "1.8.0"
futures-lite = "1.12.0"
tokio-stream = { version = "0.1.11", features = ["fs"] }
tokio-io-timeout = "1.2.0"
reqwest = { version = "0.11.13", features = ["json"] }
reqwest = { version = "0.11.14", features = ["json"] }
async_zip = { version = "0.0.9", default-features = false, features = ["deflate"] }
[dev-dependencies]
@@ -101,18 +103,14 @@ members = [
"deltachat_derive",
"deltachat-jsonrpc",
"deltachat-rpc-server",
"deltachat-ratelimit",
"deltachat-repl",
"format-flowed",
]
[[example]]
name = "simple"
path = "examples/simple.rs"
required-features = ["repl"]
[[example]]
name = "repl"
path = "examples/repl/main.rs"
required-features = ["repl"]
[[bench]]
@@ -139,14 +137,15 @@ harness = false
name = "get_chatlist"
harness = false
[[bench]]
name = "send_events"
harness = false
[features]
default = ["vendored"]
internals = []
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term", "dirs"]
vendored = [
"async-native-tls/vendored",
"async-smtp/native-tls-vendored",
"rusqlite/bundled-sqlcipher-vendored-openssl",
"reqwest/native-tls-vendored"
]
nightly = ["pgp/nightly"]

View File

@@ -19,10 +19,19 @@ $ curl https://sh.rustup.rs -sSf | sh
Compile and run Delta Chat Core command line utility, using `cargo`:
```
$ RUST_LOG=repl=info cargo run --example repl --features repl -- ~/deltachat-db
$ RUST_LOG=repl=info cargo run -p deltachat-repl -- ~/deltachat-db
```
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.
Optionally, install `deltachat-repl` binary with
```
$ cargo install --path deltachat-repl/
```
and run as
```
$ deltachat-repl ~/deltachat-db
```
Configure your account (if not already configured):
```

View File

@@ -14,7 +14,7 @@ async fn address_book_benchmark(n: u32, read_count: u32) {
.unwrap();
let book = (0..n)
.map(|i| format!("Name {}\naddr{}@example.org\n", i, i))
.map(|i| format!("Name {i}\naddr{i}@example.org\n"))
.collect::<Vec<String>>()
.join("");

View File

@@ -14,7 +14,7 @@ async fn get_chat_msgs_benchmark(dbfile: &Path, chats: &[ChatId]) {
.unwrap();
for c in chats.iter().take(10) {
black_box(chat::get_chat_msgs(&context, *c, 0).await.ok());
black_box(chat::get_chat_msgs(&context, *c).await.ok());
}
}

47
benches/send_events.rs Normal file
View File

@@ -0,0 +1,47 @@
use criterion::{criterion_group, criterion_main, Criterion};
use deltachat::context::Context;
use deltachat::stock_str::StockStrings;
use deltachat::{info, Event, EventType, Events};
use tempfile::tempdir;
async fn send_events_benchmark(context: &Context) {
let emitter = context.get_event_emitter();
for _i in 0..1_000_000 {
info!(context, "interesting event...");
}
info!(context, "DONE");
loop {
match emitter.recv().await.unwrap() {
Event {
typ: EventType::Info(info),
..
} if info.contains("DONE") => {
break;
}
_ => {}
}
}
}
fn criterion_benchmark(c: &mut Criterion) {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let rt = tokio::runtime::Runtime::new().unwrap();
let context = rt.block_on(async {
Context::new(&dbfile, 100, Events::new(), StockStrings::new())
.await
.expect("failed to create context")
});
let executor = tokio::runtime::Runtime::new().unwrap();
c.bench_function("Sending 1.000.000 events", |b| {
b.to_async(&executor)
.iter(|| send_events_benchmark(&context))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.106.0"
version = "1.108.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
@@ -29,6 +29,5 @@ once_cell = "1.17.0"
[features]
default = ["vendored"]
vendored = ["deltachat/vendored"]
nightly = ["deltachat/nightly"]
jsonrpc = ["deltachat-jsonrpc"]

View File

@@ -1159,7 +1159,7 @@ uint32_t dc_add_device_msg (dc_context_t* context, const char*
/**
* Check if a device-message with a given label was ever added.
* Device-messages can be added dc_add_device_msg().
* Device-messages can be added with dc_add_device_msg().
*
* @memberof dc_context_t
* @param context The context object.
@@ -5671,7 +5671,7 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_INCOMING_MSG 2005
/**
* Downloading a bunch of messages just finished. This is an experimental
* Downloading a bunch of messages just finished. This is an
* event to allow the UI to only show one notification per message bunch,
* instead of cluttering the user with many notifications.
* For each of the msg_ids, an additional #DC_EVENT_INCOMING_MSG event was emitted before.

View File

@@ -23,7 +23,7 @@ use std::sync::Arc;
use std::time::{Duration, SystemTime};
use anyhow::Context as _;
use deltachat::chat::{ChatId, ChatVisibility, MuteDuration, ProtectionStatus};
use deltachat::chat::{ChatId, ChatVisibility, MessageListOptions, MuteDuration, ProtectionStatus};
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
use deltachat::contact::{Contact, ContactId, Origin};
use deltachat::context::Context;
@@ -60,7 +60,8 @@ use self::string::*;
// this avoids panics if the ui just forgets to handle a case
// - finally, this behaviour matches the old core-c API and UIs already depend on it
// TODO: constants
const DC_GCM_ADDDAYMARKER: u32 = 0x01;
const DC_GCM_INFO_ONLY: u32 = 0x02;
// dc_context_t
@@ -115,7 +116,7 @@ pub unsafe extern "C" fn dc_context_new(
match ctx {
Ok(ctx) => Box::into_raw(Box::new(ctx)),
Err(err) => {
eprintln!("failed to create context: {:#}", err);
eprintln!("failed to create context: {err:#}");
ptr::null_mut()
}
}
@@ -139,7 +140,7 @@ pub unsafe extern "C" fn dc_context_new_closed(dbfile: *const libc::c_char) -> *
)) {
Ok(context) => Box::into_raw(Box::new(context)),
Err(err) => {
eprintln!("failed to create context: {:#}", err);
eprintln!("failed to create context: {err:#}");
ptr::null_mut()
}
}
@@ -214,7 +215,7 @@ pub unsafe extern "C" fn dc_set_config(
if key.starts_with("ui.") {
ctx.set_ui_config(&key, value.as_deref())
.await
.with_context(|| format!("Can't set {} to {:?}", key, value))
.with_context(|| format!("Can't set {key} to {value:?}"))
.log_err(ctx, "dc_set_config() failed")
.is_ok() as libc::c_int
} else {
@@ -222,7 +223,7 @@ pub unsafe extern "C" fn dc_set_config(
Ok(key) => ctx
.set_config(key, value.as_deref())
.await
.with_context(|| format!("Can't set {} to {:?}", key, value))
.with_context(|| format!("Can't set {key} to {value:?}"))
.log_err(ctx, "dc_set_config() failed")
.is_ok() as libc::c_int,
Err(_) => {
@@ -349,7 +350,7 @@ fn render_info(
) -> std::result::Result<String, std::fmt::Error> {
let mut res = String::new();
for (key, value) in &info {
writeln!(&mut res, "{}={}", key, value)?;
writeln!(&mut res, "{key}={value}")?;
}
Ok(res)
@@ -1156,12 +1157,21 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
}
let ctx = &*context;
let info_only = (flags & DC_GCM_INFO_ONLY) != 0;
let add_daymarker = (flags & DC_GCM_ADDDAYMARKER) != 0;
block_on(async move {
Box::into_raw(Box::new(
chat::get_chat_msgs(ctx, ChatId::new(chat_id), flags)
.await
.unwrap_or_log_default(ctx, "failed to get chat msgs")
.into(),
chat::get_chat_msgs_ex(
ctx,
ChatId::new(chat_id),
MessageListOptions {
info_only,
add_daymarker,
},
)
.await
.unwrap_or_log_default(ctx, "failed to get chat msgs")
.into(),
))
})
}
@@ -1285,11 +1295,11 @@ pub unsafe extern "C" fn dc_get_chat_media(
} else {
Some(ChatId::new(chat_id))
};
let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {}", msg_type));
let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {msg_type}"));
let or_msg_type2 =
from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {}", or_msg_type2));
from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {or_msg_type2}"));
let or_msg_type3 =
from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3));
from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {or_msg_type3}"));
block_on(async move {
Box::into_raw(Box::new(
@@ -1321,11 +1331,11 @@ pub unsafe extern "C" fn dc_get_next_media(
};
let ctx = &*context;
let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {}", msg_type));
let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {msg_type}"));
let or_msg_type2 =
from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {}", or_msg_type2));
from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {or_msg_type2}"));
let or_msg_type3 =
from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3));
from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {or_msg_type3}"));
block_on(async move {
chat::get_next_media(
@@ -2185,7 +2195,7 @@ pub unsafe extern "C" fn dc_imex(
let what = match imex::ImexMode::from_i32(what_raw) {
Some(what) => what,
None => {
eprintln!("ignoring invalid argument {} to dc_imex", what_raw);
eprintln!("ignoring invalid argument {what_raw} to dc_imex");
return;
}
};
@@ -3073,7 +3083,7 @@ pub unsafe extern "C" fn dc_msg_new(
return ptr::null_mut();
}
let context = &*context;
let viewtype = from_prim(viewtype).expect(&format!("invalid viewtype = {}", viewtype));
let viewtype = from_prim(viewtype).expect(&format!("invalid viewtype = {viewtype}"));
let msg = MessageWrapper {
context,
message: message::Message::new(viewtype),
@@ -3256,7 +3266,7 @@ pub unsafe extern "C" fn dc_msg_get_webxdc_blob(
ptr as *mut libc::c_char
}
Err(err) => {
eprintln!("failed read blob from archive: {}", err);
eprintln!("failed read blob from archive: {err}");
ptr::null_mut()
}
}
@@ -4309,7 +4319,7 @@ pub unsafe extern "C" fn dc_accounts_new(
Ok(accs) => Box::into_raw(Box::new(AccountsWrapper::new(accs))),
Err(err) => {
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
eprintln!("failed to create accounts: {:#}", err);
eprintln!("failed to create accounts: {err:#}");
ptr::null_mut()
}
}
@@ -4377,8 +4387,7 @@ pub unsafe extern "C" fn dc_accounts_select_account(
Ok(()) => 1,
Err(err) => {
accounts.emit_event(EventType::Error(format!(
"Failed to select account: {:#}",
err
"Failed to select account: {err:#}"
)));
0
}
@@ -4400,10 +4409,7 @@ pub unsafe extern "C" fn dc_accounts_add_account(accounts: *mut dc_accounts_t) -
match accounts.add_account().await {
Ok(id) => id,
Err(err) => {
accounts.emit_event(EventType::Error(format!(
"Failed to add account: {:#}",
err
)));
accounts.emit_event(EventType::Error(format!("Failed to add account: {err:#}")));
0
}
}
@@ -4424,10 +4430,7 @@ pub unsafe extern "C" fn dc_accounts_add_closed_account(accounts: *mut dc_accoun
match accounts.add_closed_account().await {
Ok(id) => id,
Err(err) => {
accounts.emit_event(EventType::Error(format!(
"Failed to add account: {:#}",
err
)));
accounts.emit_event(EventType::Error(format!("Failed to add account: {err:#}")));
0
}
}
@@ -4452,8 +4455,7 @@ pub unsafe extern "C" fn dc_accounts_remove_account(
Ok(()) => 1,
Err(err) => {
accounts.emit_event(EventType::Error(format!(
"Failed to remove account: {:#}",
err
"Failed to remove account: {err:#}"
)));
0
}
@@ -4483,8 +4485,7 @@ pub unsafe extern "C" fn dc_accounts_migrate_account(
Ok(id) => id,
Err(err) => {
accounts.emit_event(EventType::Error(format!(
"Failed to migrate account: {:#}",
err
"Failed to migrate account: {err:#}"
)));
0
}

View File

@@ -22,20 +22,15 @@ pub enum Lot {
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Meaning {
#[default]
None = 0,
Text1Draft = 1,
Text1Username = 2,
Text1Self = 3,
}
impl Default for Meaning {
fn default() -> Self {
Meaning::None
}
}
impl Lot {
pub fn get_text1(&self) -> Option<&str> {
match self {
@@ -151,9 +146,9 @@ impl Lot {
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum LotState {
// Default
#[default]
Undefined = 0,
// Qr States
@@ -215,12 +210,6 @@ pub enum LotState {
MsgOutMdnRcvd = 28,
}
impl Default for LotState {
fn default() -> Self {
LotState::Undefined
}
}
impl From<MessageState> for LotState {
fn from(s: MessageState) -> Self {
use MessageState::*;

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.106.0"
version = "1.108.0"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"
@@ -19,20 +19,20 @@ serde = { version = "1.0", features = ["derive"] }
tempfile = "3.3.0"
log = "0.4"
async-channel = { version = "1.8.0" }
futures = { version = "0.3.25" }
futures = { version = "0.3.26" }
serde_json = "1.0.91"
yerpc = { version = "^0.3.1", features = ["anyhow_expose"] }
yerpc = { version = "^0.4.0", features = ["anyhow_expose"] }
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
tokio = { version = "1.23.1" }
tokio = { version = "1.25.0" }
sanitize-filename = "0.4"
walkdir = "2.3.2"
# optional dependencies
axum = { version = "0.6.1", optional = true, features = ["ws"] }
axum = { version = "0.6.4", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
tokio = { version = "1.23.1", features = ["full", "rt-multi-thread"] }
tokio = { version = "1.25.0", features = ["full", "rt-multi-thread"] }
[features]

View File

@@ -6,8 +6,9 @@ use anyhow::{anyhow, bail, ensure, Context, Result};
pub use deltachat::accounts::Accounts;
use deltachat::{
chat::{
self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, marknoticed_chat,
remove_contact_from_chat, Chat, ChatId, ChatItem, ProtectionStatus,
self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
ProtectionStatus,
},
chatlist::Chatlist,
config::Config,
@@ -44,6 +45,7 @@ use types::message::MessageObject;
use types::provider_info::ProviderInfo;
use types::webxdc::WebxdcMessageInfo;
use self::types::message::MessageLoadResult;
use self::types::{
chat::{BasicChat, JSONRPCChatVisibility, MuteDuration},
location::JsonrpcLocation,
@@ -84,6 +86,11 @@ impl CommandApi {
#[rpc(all_positional, ts_outdir = "typescript/generated")]
impl CommandApi {
/// Test function.
async fn sleep(&self, delay: f64) {
tokio::time::sleep(std::time::Duration::from_secs_f64(delay)).await
}
// ---------------------------------------------
// Misc top level functions
// ---------------------------------------------
@@ -134,7 +141,7 @@ impl CommandApi {
if let Some(ctx) = context_option {
accounts.push(Account::from_context(&ctx, id).await?)
} else {
println!("account with id {} doesn't exist anymore", id);
println!("account with id {id} doesn't exist anymore");
}
}
Ok(accounts)
@@ -242,7 +249,7 @@ impl CommandApi {
for (key, value) in config.into_iter() {
set_config(&ctx, &key, value.as_deref())
.await
.with_context(|| format!("Can't set {} to {:?}", key, value))?;
.with_context(|| format!("Can't set {key} to {value:?}"))?;
}
Ok(())
}
@@ -459,7 +466,7 @@ impl CommandApi {
Ok(res) => res,
Err(err) => ChatListItemFetchResult::Error {
id: entry.0,
error: format!("{:?}", err),
error: format!("{err:#}"),
},
},
);
@@ -803,7 +810,7 @@ impl CommandApi {
let ctx = self.get_context(account_id).await?;
// TODO: implement this in core with an SQL query, that will be way faster
let messages = get_chat_msgs(&ctx, ChatId::new(chat_id), 0).await?;
let messages = get_chat_msgs(&ctx, ChatId::new(chat_id)).await?;
let mut first_unread_message_id = None;
for item in messages.into_iter().rev() {
if let ChatItem::Message { msg_id } = item {
@@ -878,9 +885,23 @@ impl CommandApi {
markseen_msgs(&ctx, msg_ids.into_iter().map(MsgId::new).collect()).await
}
async fn get_message_ids(&self, account_id: u32, chat_id: u32, flags: u32) -> Result<Vec<u32>> {
async fn get_message_ids(
&self,
account_id: u32,
chat_id: u32,
info_only: bool,
add_daymarker: bool,
) -> Result<Vec<u32>> {
let ctx = self.get_context(account_id).await?;
let msg = get_chat_msgs(&ctx, ChatId::new(chat_id), flags).await?;
let msg = get_chat_msgs_ex(
&ctx,
ChatId::new(chat_id),
MessageListOptions {
info_only,
add_daymarker,
},
)
.await?;
Ok(msg
.iter()
.map(|chat_item| -> u32 {
@@ -896,10 +917,19 @@ impl CommandApi {
&self,
account_id: u32,
chat_id: u32,
flags: u32,
info_only: bool,
add_daymarker: bool,
) -> Result<Vec<JSONRPCMessageListItem>> {
let ctx = self.get_context(account_id).await?;
let msg = get_chat_msgs(&ctx, ChatId::new(chat_id), flags).await?;
let msg = get_chat_msgs_ex(
&ctx,
ChatId::new(chat_id),
MessageListOptions {
info_only,
add_daymarker,
},
)
.await?;
Ok(msg
.iter()
.map(|chat_item| (*chat_item).into())
@@ -916,17 +946,27 @@ impl CommandApi {
MsgId::new(message_id).get_html(&ctx).await
}
/// get multiple messages in one call,
/// if loading one message fails the error is stored in the result object in it's place.
///
/// this is the batch variant of [get_message]
async fn get_messages(
&self,
account_id: u32,
message_ids: Vec<u32>,
) -> Result<HashMap<u32, MessageObject>> {
) -> Result<HashMap<u32, MessageLoadResult>> {
let ctx = self.get_context(account_id).await?;
let mut messages: HashMap<u32, MessageObject> = HashMap::new();
let mut messages: HashMap<u32, MessageLoadResult> = HashMap::new();
for message_id in message_ids {
let message_result = MessageObject::from_message_id(&ctx, message_id).await;
messages.insert(
message_id,
MessageObject::from_message_id(&ctx, message_id).await?,
match message_result {
Ok(message) => MessageLoadResult::Message(message),
Err(error) => MessageLoadResult::LoadingError {
error: format!("{error:#}"),
},
},
);
}
Ok(messages)
@@ -1714,7 +1754,7 @@ async fn set_config(
ctx.set_ui_config(key, value).await?;
} else {
ctx.set_config(
Config::from_str(key).with_context(|| format!("unknown key {:?}", key))?,
Config::from_str(key).with_context(|| format!("unknown key {key:?}"))?,
value,
)
.await?;
@@ -1736,7 +1776,7 @@ async fn get_config(
if key.starts_with("ui.") {
ctx.get_ui_config(key).await
} else {
ctx.get_config(Config::from_str(key).with_context(|| format!("unknown key {:?}", key))?)
ctx.get_config(Config::from_str(key).with_context(|| format!("unknown key {key:?}"))?)
.await
}
}

View File

@@ -19,6 +19,13 @@ use super::contact::ContactObject;
use super::reactions::JSONRPCReactions;
use super::webxdc::WebxdcMessageInfo;
#[derive(Serialize, TypeDef)]
#[serde(rename_all = "camelCase", tag = "variant")]
pub enum MessageLoadResult {
Message(MessageObject),
LoadingError { error: String },
}
#[derive(Serialize, TypeDef)]
#[serde(rename = "Message", rename_all = "camelCase")]
pub struct MessageObject {
@@ -48,6 +55,10 @@ pub struct MessageObject {
is_setupmessage: bool,
is_info: bool,
is_forwarded: bool,
/// True if the message was sent by a bot.
is_bot: bool,
/// when is_info is true this describes what type of system message it is
system_message_type: SystemMessageType,
@@ -182,6 +193,7 @@ impl MessageObject {
is_setupmessage: message.is_setupmessage(),
is_info: message.is_info(),
is_forwarded: message.is_forwarded(),
is_bot: message.is_bot(),
system_message_type: message.get_info_type().into(),
duration: message.get_duration(),

View File

@@ -10,7 +10,7 @@ pub mod reactions;
pub mod webxdc;
pub fn color_int_to_hex_string(color: u32) -> String {
format!("{:#08x}", color).replace("0x", "#")
format!("{color:#08x}").replace("0x", "#")
}
fn maybe_empty_string_to_option(string: String) -> Option<String> {

View File

@@ -37,7 +37,7 @@ mod tests {
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
session.handle_incoming(request).await;
let result = receiver.next().await;
println!("{:?}", result);
println!("{result:?}");
assert_eq!(result, Some(response.to_owned()));
}
{
@@ -45,7 +45,7 @@ mod tests {
let response = r#"{"jsonrpc":"2.0","id":2,"result":[1]}"#;
session.handle_incoming(request).await;
let result = receiver.next().await;
println!("{:?}", result);
println!("{result:?}");
assert_eq!(result, Some(response.to_owned()));
}

View File

@@ -68,22 +68,22 @@ async function run() {
null
);
for (const [chatId, _messageId] of chats) {
const chat = await client.rpc.getFullChatById(
selectedAccount,
chatId
);
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
write($main, `<h3>${chat.name}</h3>`);
const messageIds = await client.rpc.getMessageIds(
selectedAccount,
chatId,
0
false,
false
);
const messages = await client.rpc.getMessages(
selectedAccount,
messageIds
);
for (const [_messageId, message] of Object.entries(messages)) {
write($main, `<p>${message.text}</p>`);
if (message.variant === "message")
write($main, `<p>${message.text}</p>`);
else write($main, `<p>loading error: ${message.error}</p>`);
}
}
}

View File

@@ -3,24 +3,27 @@ import { DeltaChat } from "../dist/deltachat.js";
run().catch(console.error);
async function run() {
const delta = new DeltaChat('ws://localhost:20808/ws');
const delta = new DeltaChat("ws://localhost:20808/ws");
delta.on("event", (event) => {
console.log("event", event.data);
});
const email = process.argv[2]
const password = process.argv[3]
if (!email || !password) throw new Error('USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>')
console.log(`creating acccount for ${email}`)
const id = await delta.rpc.addAccount()
console.log(`created account id ${id}`)
const email = process.argv[2];
const password = process.argv[3];
if (!email || !password)
throw new Error(
"USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>"
);
console.log(`creating acccount for ${email}`);
const id = await delta.rpc.addAccount();
console.log(`created account id ${id}`);
await delta.rpc.setConfig(id, "addr", email);
await delta.rpc.setConfig(id, "mail_pw", password);
console.log('configuration updated')
await delta.rpc.configure(id)
console.log('account configured!')
console.log("configuration updated");
await delta.rpc.configure(id);
console.log("account configured!");
const accounts = await delta.rpc.getAllAccounts();
console.log("accounts", accounts);
console.log("waiting for events...")
console.log("waiting for events...");
}

View File

@@ -10,5 +10,5 @@ async function run() {
const accounts = await delta.rpc.getAllAccounts();
console.log("accounts", accounts);
console.log("waiting for events...")
console.log("waiting for events...");
}

View File

@@ -38,8 +38,8 @@
"example:start": "http-server .",
"extract-constants": "node ./scripts/generate-constants.js",
"generate-bindings": "cargo test",
"prettier:check": "prettier --check **.ts",
"prettier:fix": "prettier --write **.ts",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
"test": "run-s test:prepare test:run-coverage test:report-coverage",
"test:prepare": "cargo build --package deltachat-rpc-server --bin deltachat-rpc-server",
"test:report-coverage": "node report_api_coverage.mjs",
@@ -48,5 +48,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.106.0"
}
"version": "1.108.0"
}

View File

@@ -43,7 +43,7 @@ export class BaseDeltaChat<
const method = request.method;
if (method === "event") {
const event = request.params! as DCWireEvent<Event>;
//@ts-ignore
//@ts-ignore
this.emit(event.event.type, event.contextId, event.event as any);
this.emit("ALL", event.contextId, event.event as any);
@@ -120,7 +120,7 @@ export class StdioTransport extends BaseTransport {
});
}
_send(message: RPC.Message): void {
_send(message: any): void {
const serialized = JSON.stringify(message);
this.input.write(serialized + "\n");
}

View File

@@ -4,10 +4,7 @@ import chaiAsPromised from "chai-as-promised";
chai.use(chaiAsPromised);
import { StdioDeltaChat as DeltaChat } from "../deltachat.js";
import {
RpcServerHandle,
startServer,
} from "./test_base.js";
import { RpcServerHandle, startServer } from "./test_base.js";
describe("basic tests", () => {
let serverHandle: RpcServerHandle;
@@ -15,9 +12,9 @@ describe("basic tests", () => {
before(async () => {
serverHandle = await startServer();
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout)
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
// dc.on("ALL", (event) => {
//console.log("event", event);
//console.log("event", event);
// });
});
@@ -111,8 +108,8 @@ describe("basic tests", () => {
assert((await dc.rpc.getConfig(accountId, "addr")) == "valid@email");
});
it("set invalid key should throw", async function () {
await expect(dc.rpc.setConfig(accountId, "invalid_key", "some value")).to.be
.eventually.rejected;
await expect(dc.rpc.setConfig(accountId, "invalid_key", "some value")).to
.be.eventually.rejected;
});
it("get invalid key should throw", async function () {
await expect(dc.rpc.getConfig(accountId, "invalid_key")).to.be.eventually
@@ -125,7 +122,10 @@ describe("basic tests", () => {
it("set and retrive (batch)", async function () {
const config = { addr: "valid@email", mail_pw: "1234" };
await dc.rpc.batchSetConfig(accountId, config);
const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config));
const retrieved = await dc.rpc.batchGetConfig(
accountId,
Object.keys(config)
);
expect(retrieved).to.deep.equal(config);
});
it("set and retrive ui.* (batch)", async function () {
@@ -134,7 +134,10 @@ describe("basic tests", () => {
"ui.enter_key_sends": "true",
};
await dc.rpc.batchSetConfig(accountId, config);
const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config));
const retrieved = await dc.rpc.batchGetConfig(
accountId,
Object.keys(config)
);
expect(retrieved).to.deep.equal(config);
});
it("set and retrive mixed(ui and core) (batch)", async function () {
@@ -145,7 +148,10 @@ describe("basic tests", () => {
mail_pw: "123456",
};
await dc.rpc.batchSetConfig(accountId, config);
const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config));
const retrieved = await dc.rpc.batchGetConfig(
accountId,
Object.keys(config)
);
expect(retrieved).to.deep.equal(config);
});
});

View File

@@ -97,7 +97,8 @@ describe("online tests", function () {
const messageList = await dc.rpc.getMessageIds(
accountId2,
chatIdOnAccountB,
0
false,
false
);
expect(messageList).have.length(1);
@@ -133,7 +134,8 @@ describe("online tests", function () {
const messageList = await dc.rpc.getMessageIds(
accountId2,
chatIdOnAccountB,
0
false,
false
);
const message = await dc.rpc.getMessage(
accountId2,
@@ -150,7 +152,7 @@ describe("online tests", function () {
await eventPromise2;
const messageId = (
await dc.rpc.getMessageIds(accountId1, chatId, 0)
await dc.rpc.getMessageIds(accountId1, chatId, false, false)
).reverse()[0];
const message2 = await dc.rpc.getMessage(accountId1, messageId);
expect(message2.text).equal("super secret message");

View File

@@ -59,13 +59,13 @@ export async function startServer(): Promise<RpcServerHandle> {
export async function createTempUser(url: string) {
const response = await fetch(url, {
method: "POST",
method: "POST",
headers: {
"cache-control": "no-cache",
},
});
if (!response.ok) throw new Error('Received invalid response')
return response.json();
if (!response.ok) throw new Error("Received invalid response");
return response.json();
}
function getTargetDir(): Promise<string> {
@@ -89,4 +89,3 @@ function getTargetDir(): Promise<string> {
);
});
}

View File

@@ -0,0 +1,8 @@
[package]
name = "ratelimit"
version = "1.0.0"
description = "Token bucket implementation"
edition = "2021"
license = "MPL-2.0"
[dependencies]

View File

@@ -7,7 +7,7 @@
use std::time::{Duration, SystemTime};
#[derive(Debug)]
pub(crate) struct Ratelimit {
pub struct Ratelimit {
/// Time of the last update.
last_update: SystemTime,
@@ -25,7 +25,7 @@ impl Ratelimit {
/// Returns a new rate limiter with the given constraints.
///
/// Rate limiter will allow to send no more than `quota` messages within duration `window`.
pub(crate) fn new(window: Duration, quota: f64) -> Self {
pub fn new(window: Duration, quota: f64) -> Self {
Self::new_at(window, quota, SystemTime::now())
}
@@ -57,7 +57,7 @@ impl Ratelimit {
/// Returns true if can send another message now.
///
/// This method takes mutable reference
pub(crate) fn can_send(&self) -> bool {
pub fn can_send(&self) -> bool {
self.can_send_at(SystemTime::now())
}
@@ -71,7 +71,7 @@ impl Ratelimit {
/// It is possible to send message even if over quota, e.g. if the message sending is initiated
/// by the user and should not be rate limited. However, sending messages when over quota
/// further postpones the time when it will be allowed to send low priority messages.
pub(crate) fn send(&mut self) {
pub fn send(&mut self) {
self.send_at(SystemTime::now())
}
@@ -87,7 +87,7 @@ impl Ratelimit {
}
/// Calculates the time until `can_send` will return `true`.
pub(crate) fn until_can_send(&self) -> Duration {
pub fn until_can_send(&self) -> Duration {
self.until_can_send_at(SystemTime::now())
}
}

19
deltachat-repl/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "deltachat-repl"
version = "1.108.0"
edition = "2021"
[dependencies]
ansi_term = "0.12.1"
anyhow = "1"
deltachat = { path = "..", features = ["internals"]}
dirs = "4"
log = "0.4.16"
pretty_env_logger = "0.4"
rusqlite = "0.28"
rustyline = "10"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
[features]
default = ["vendored"]
vendored = ["deltachat/vendored"]

View File

@@ -31,7 +31,7 @@ use tokio::fs;
/// Argument is a bitmask, executing single or multiple actions in one call.
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
async fn reset_tables(context: &Context, bits: i32) {
println!("Resetting tables ({})...", bits);
println!("Resetting tables ({bits})...");
if 0 != bits & 1 {
context
.sql()
@@ -101,7 +101,7 @@ async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<
let data = read_file(context, filename).await?;
if let Err(err) = receive_imf(context, &data, false).await {
println!("receive_imf errored: {:?}", err);
println!("receive_imf errored: {err:?}");
}
Ok(())
}
@@ -148,7 +148,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
let name = name_f.to_string_lossy();
if name.ends_with(".eml") {
let path_plus_name = format!("{}/{}", &real_spec, name);
println!("Import: {}", path_plus_name);
println!("Import: {path_plus_name}");
if poke_eml_file(context, path_plus_name).await.is_ok() {
read_cnt += 1
}
@@ -168,7 +168,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
.await
.expect("invalid contact");
let contact_name = if let Some(name) = msg.get_override_sender_name() {
format!("~{}", name)
format!("~{name}")
} else {
contact.get_display_name().to_string()
};
@@ -223,7 +223,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
"[WEBXDC: {}, icon={}, document={}, summary={}, source_code_url={}]",
info.name, info.icon, info.document, info.summary, info.source_code_url
),
Err(err) => format!("[get_webxdc_info() failed: {}]", err),
Err(err) => format!("[get_webxdc_info() failed: {err}]"),
}
} else {
"".to_string()
@@ -435,10 +435,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
),
},
"initiate-key-transfer" => match initiate_key_transfer(&context).await {
Ok(setup_code) => println!(
"Setup code for the transferred setup message: {}",
setup_code,
),
Ok(setup_code) => {
println!("Setup code for the transferred setup message: {setup_code}",)
}
Err(err) => bail!("Failed to generate setup code: {}", err),
},
"get-setupcodebegin" => {
@@ -528,7 +527,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(!arg1.is_empty(), "Argument <key> missing.");
let key = config::Config::from_str(arg1)?;
let val = context.get_config(key).await;
println!("{}={:?}", key, val);
println!("{key}={val:?}");
}
"info" => {
println!("{:#?}", context.get_info().await);
@@ -540,7 +539,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
match context.get_connectivity_html().await {
Ok(html) => {
fs::write(&file, html).await?;
println!("Report written to: {:#?}", file);
println!("Report written to: {file:#?}");
}
Err(err) => {
bail!("Failed to get connectivity html: {}", err);
@@ -613,7 +612,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"{}{}{} [{}]{}",
summary
.prefix
.map_or_else(String::new, |prefix| format!("{}: ", prefix)),
.map_or_else(String::new, |prefix| format!("{prefix}: ")),
summary.text,
statestr,
&timestr,
@@ -631,8 +630,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
if location::is_sending_locations_to_chat(&context, None).await? {
println!("Location streaming enabled.");
}
println!("{} chats", cnt);
println!("{:?} to create this list", time_needed);
println!("{cnt} chats");
println!("{time_needed:?} to create this list");
}
"chat" => {
if sel_chat.is_none() && arg1.is_empty() {
@@ -640,7 +639,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
}
if !arg1.is_empty() {
let id = ChatId::new(arg1.parse()?);
println!("Selecting chat {}", id);
println!("Selecting chat {id}");
sel_chat = Some(Chat::load_from_db(&context, id).await?);
*chat_id = id;
}
@@ -649,8 +648,15 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let sel_chat = sel_chat.as_ref().unwrap();
let time_start = std::time::SystemTime::now();
let msglist =
chat::get_chat_msgs(&context, sel_chat.get_id(), DC_GCM_ADDDAYMARKER).await?;
let msglist = chat::get_chat_msgs_ex(
&context,
sel_chat.get_id(),
chat::MessageListOptions {
info_only: false,
add_daymarker: true,
},
)
.await?;
let time_needed = time_start.elapsed().unwrap_or_default();
let msglist: Vec<MsgId> = msglist
@@ -686,7 +692,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
},
match sel_chat.get_profile_image(&context).await? {
Some(icon) => match icon.to_str() {
Some(icon) => format!(" Icon: {}", icon),
Some(icon) => format!(" Icon: {icon}"),
_ => " Icon: Err".to_string(),
},
_ => "".to_string(),
@@ -712,8 +718,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let time_noticed_needed = time_noticed_start.elapsed().unwrap_or_default();
println!(
"{:?} to create this list, {:?} to mark all messages as noticed.",
time_needed, time_noticed_needed
"{time_needed:?} to create this list, {time_noticed_needed:?} to mark all messages as noticed."
);
}
"createchat" => {
@@ -721,26 +726,26 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let contact_id = ContactId::new(arg1.parse()?);
let chat_id = ChatId::create_for_contact(&context, contact_id).await?;
println!("Single#{} created successfully.", chat_id,);
println!("Single#{chat_id} created successfully.",);
}
"creategroup" => {
ensure!(!arg1.is_empty(), "Argument <name> missing.");
let chat_id =
chat::create_group_chat(&context, ProtectionStatus::Unprotected, arg1).await?;
println!("Group#{} created successfully.", chat_id);
println!("Group#{chat_id} created successfully.");
}
"createbroadcast" => {
let chat_id = chat::create_broadcast_list(&context).await?;
println!("Broadcast#{} created successfully.", chat_id);
println!("Broadcast#{chat_id} created successfully.");
}
"createprotected" => {
ensure!(!arg1.is_empty(), "Argument <name> missing.");
let chat_id =
chat::create_group_chat(&context, ProtectionStatus::Protected, arg1).await?;
println!("Group#{} created and protected successfully.", chat_id);
println!("Group#{chat_id} created and protected successfully.");
}
"addmember" => {
ensure!(sel_chat.is_some(), "No chat selected");
@@ -770,7 +775,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
chat::set_chat_name(
&context,
sel_chat.as_ref().unwrap().get_id(),
format!("{} {}", arg1, arg2).trim(),
format!("{arg1} {arg2}").trim(),
)
.await?;
@@ -874,7 +879,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(sel_chat.is_some(), "No chat selected.");
ensure!(!arg1.is_empty(), "No message text given.");
let msg = format!("{} {}", arg1, arg2);
let msg = format!("{arg1} {arg2}");
chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), msg).await?;
}
@@ -916,7 +921,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
}
"sendsyncmsg" => match context.send_sync_msg().await? {
Some(msg_id) => println!("sync message sent as {}.", msg_id),
Some(msg_id) => println!("sync message sent as {msg_id}."),
None => println!("sync message not needed."),
},
"sendupdate" => {
@@ -936,7 +941,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"listmsgs" => {
ensure!(!arg1.is_empty(), "Argument <query> missing.");
let query = format!("{} {}", arg1, arg2).trim().to_string();
let query = format!("{arg1} {arg2}").trim().to_string();
let chat = sel_chat.as_ref().map(|sel_chat| sel_chat.get_id());
let time_start = std::time::SystemTime::now();
let msglist = context.search_msgs(chat, &query).await?;
@@ -954,7 +959,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
},
query,
);
println!("{:?} to create this list", time_needed);
println!("{time_needed:?} to create this list");
}
"draft" => {
ensure!(sel_chat.is_some(), "No chat selected.");
@@ -1000,9 +1005,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
println!("{} images or videos: ", images.len());
for (i, data) in images.iter().enumerate() {
if 0 == i {
print!("{}", data);
print!("{data}");
} else {
print!(", {}", data);
print!(", {data}");
}
}
println!();
@@ -1073,12 +1078,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = MsgId::new(arg1.parse()?);
let res = message::get_msg_info(&context, id).await?;
println!("{}", res);
println!("{res}");
}
"download" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = MsgId::new(arg1.parse()?);
println!("Scheduling download for {:?}", id);
println!("Scheduling download for {id:?}");
id.download_full(&context).await?;
}
"html" => {
@@ -1089,7 +1094,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
.join(format!("msg-{}.html", id.to_u32()));
let html = id.get_html(&context).await?.unwrap_or_default();
fs::write(&file, html).await?;
println!("HTML written to: {:#?}", file);
println!("HTML written to: {file:#?}");
}
"listfresh" => {
let msglist = context.get_fresh_msgs().await?;
@@ -1151,7 +1156,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(!arg1.is_empty(), "Arguments [<name>] <addr> expected.");
if !arg2.is_empty() {
let book = format!("{}\n{}", arg1, arg2);
let book = format!("{arg1}\n{arg2}");
Contact::add_address_book(&context, &book).await?;
} else {
Contact::create(&context, "", arg1).await?;
@@ -1178,10 +1183,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let chatlist = Chatlist::try_load(&context, 0, None, Some(contact_id)).await?;
let chatlist_cnt = chatlist.len();
if chatlist_cnt > 0 {
res += &format!(
"\n\n{} chats shared with Contact#{}: ",
chatlist_cnt, contact_id,
);
res += &format!("\n\n{chatlist_cnt} chats shared with Contact#{contact_id}: ",);
for i in 0..chatlist_cnt {
if 0 != i {
res += ", ";
@@ -1191,7 +1193,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
}
}
println!("{}", res);
println!("{res}");
}
"delcontact" => {
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
@@ -1215,13 +1217,13 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"checkqr" => {
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
let qr = check_qr(&context, arg1).await?;
println!("qr={:?}", qr);
println!("qr={qr:?}");
}
"setqr" => {
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
match set_config_from_qr(&context, arg1).await {
Ok(()) => println!("Config set from QR code, you can now call 'configure'"),
Err(err) => println!("Cannot set config from QR code: {:?}", err),
Err(err) => println!("Cannot set config from QR code: {err:?}"),
}
}
"providerinfo" => {
@@ -1231,7 +1233,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
.await?;
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
Some(info) => {
println!("Information for provider belonging to {}:", arg1);
println!("Information for provider belonging to {arg1}:");
println!("status: {}", info.status as u32);
println!("before_login_hint: {}", info.before_login_hint);
println!("after_login_hint: {}", info.after_login_hint);
@@ -1241,7 +1243,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
}
}
None => {
println!("No information for provider belonging to {} found.", arg1);
println!("No information for provider belonging to {arg1} found.");
}
}
}
@@ -1250,7 +1252,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
if let Ok(buf) = read_file(&context, &arg1).await {
let (width, height) = get_filemeta(&buf)?;
println!("width={}, height={}", width, height);
println!("width={width}, height={height}");
} else {
bail!("Command failed.");
}
@@ -1261,8 +1263,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let device_cnt = message::estimate_deletion_cnt(&context, false, seconds).await?;
let server_cnt = message::estimate_deletion_cnt(&context, true, seconds).await?;
println!(
"estimated count of messages older than {} seconds:\non device: {}\non server: {}",
seconds, device_cnt, server_cnt
"estimated count of messages older than {seconds} seconds:\non device: {device_cnt}\non server: {server_cnt}"
);
}
"" => (),

View File

@@ -67,8 +67,7 @@ fn receive_event(event: EventType) {
info!(
"{}",
yellow.paint(format!(
"Received MSGS_CHANGED(chat_id={}, msg_id={})",
chat_id, msg_id,
"Received MSGS_CHANGED(chat_id={chat_id}, msg_id={msg_id})",
))
);
}
@@ -80,8 +79,7 @@ fn receive_event(event: EventType) {
info!(
"{}",
yellow.paint(format!(
"Received REACTIONS_CHANGED(chat_id={}, msg_id={}, contact_id={})",
chat_id, msg_id, contact_id
"Received REACTIONS_CHANGED(chat_id={chat_id}, msg_id={msg_id}, contact_id={contact_id})"
))
);
}
@@ -91,7 +89,7 @@ fn receive_event(event: EventType) {
EventType::LocationChanged(contact) => {
info!(
"{}",
yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact))
yellow.paint(format!("Received LOCATION_CHANGED(contact={contact:?})"))
);
}
EventType::ConfigureProgress { progress, comment } => {
@@ -99,21 +97,20 @@ fn receive_event(event: EventType) {
info!(
"{}",
yellow.paint(format!(
"Received CONFIGURE_PROGRESS({} ‰, {})",
progress, comment
"Received CONFIGURE_PROGRESS({progress} ‰, {comment})"
))
);
} else {
info!(
"{}",
yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress))
yellow.paint(format!("Received CONFIGURE_PROGRESS({progress} ‰)"))
);
}
}
EventType::ImexProgress(progress) => {
info!(
"{}",
yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress))
yellow.paint(format!("Received IMEX_PROGRESS({progress} ‰)"))
);
}
EventType::ImexFileWritten(file) => {
@@ -125,7 +122,7 @@ fn receive_event(event: EventType) {
EventType::ChatModified(chat) => {
info!(
"{}",
yellow.paint(format!("Received CHAT_MODIFIED({})", chat))
yellow.paint(format!("Received CHAT_MODIFIED({chat})"))
);
}
_ => {
@@ -362,7 +359,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
false
}
Err(err) => {
println!("Error: {}", err);
println!("Error: {err}");
true
}
}
@@ -377,7 +374,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
break;
}
Err(err) => {
println!("Error: {}", err);
println!("Error: {err}");
break;
}
}
@@ -444,7 +441,7 @@ async fn handle_cmd(
if arg0 == "getbadqr" && qr.len() > 40 {
qr.replace_range(12..22, "0000000000")
}
println!("{}", qr);
println!("{qr}");
let output = Command::new("qrencode")
.args(["-t", "ansiutf8", qr.as_str(), "-o", "-"])
.output()
@@ -460,7 +457,7 @@ async fn handle_cmd(
match get_securejoin_qr_svg(&ctx, group).await {
Ok(svg) => {
fs::write(&file, svg).await?;
println!("QR code svg written to: {:#?}", file);
println!("QR code svg written to: {file:#?}");
}
Err(err) => {
bail!("Failed to get QR code svg: {}", err);

View File

@@ -64,7 +64,8 @@ async def main():
bot = Bot(account, hooks)
if not await bot.is_configured():
asyncio.create_task(bot.configure(email=sys.argv[1], password=sys.argv[2]))
# Save a reference to avoid garbage collection of the task.
_configure_task = asyncio.create_task(bot.configure(email=sys.argv[1], password=sys.argv[2]))
await bot.run_forever()

View File

@@ -32,7 +32,7 @@ async def main():
async def process_messages():
for message in await account.get_fresh_messages_in_arrival_order():
snapshot = await message.get_snapshot()
if not snapshot.is_info:
if not snapshot.is_bot and not snapshot.is_info:
await snapshot.chat.send_text(snapshot.text)
await snapshot.message.mark_seen()

View File

@@ -13,13 +13,6 @@ dynamic = [
"version"
]
[tool.setuptools]
# We declare the package not-zip-safe so that our type hints are also available
# when checking client code that uses our (installed) package.
# Ref:
# https://mypy.readthedocs.io/en/stable/installed_packages.html?highlight=zip#using-installed-packages-with-mypy-pep-561
zip-safe = false
[tool.setuptools.package-data]
deltachat_rpc_client = [
"py.typed"

View File

@@ -104,7 +104,8 @@ async def _run_cli(
if not await client.is_configured():
assert args.email, "Account is not configured and email must be provided"
assert args.password, "Account is not configured and password must be provided"
asyncio.create_task(client.configure(email=args.email, password=args.password))
# Save a reference to avoid garbage collection of the task.
_configure_task = asyncio.create_task(client.configure(email=args.email, password=args.password))
await client.run_forever()

View File

@@ -1,4 +1,5 @@
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
from dataclasses import dataclass
from ._utils import AttrDict
from .chat import Chat
@@ -11,28 +12,17 @@ if TYPE_CHECKING:
from .deltachat import DeltaChat
@dataclass
class Account:
"""Delta Chat account."""
def __init__(self, manager: "DeltaChat", account_id: int) -> None:
self.manager = manager
self.id = account_id
manager: "DeltaChat"
id: int
@property
def _rpc(self) -> Rpc:
return self.manager.rpc
def __eq__(self, other) -> bool:
if not isinstance(other, Account):
return False
return self.id == other.id and self.manager == other.manager
def __ne__(self, other) -> bool:
return not self == other
def __repr__(self) -> str:
return f"<Account id={self.id}>"
async def wait_for_event(self) -> AttrDict:
"""Wait until the next event and return it."""
return AttrDict(await self._rpc.wait_for_event(self.id))

View File

@@ -1,6 +1,7 @@
import calendar
from datetime import datetime
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
from dataclasses import dataclass
from ._utils import AttrDict
from .const import ChatVisibility
@@ -12,28 +13,17 @@ if TYPE_CHECKING:
from .account import Account
@dataclass
class Chat:
"""Chat object which manages members and through which you can send and retrieve messages."""
def __init__(self, account: "Account", chat_id: int) -> None:
self.account = account
self.id = chat_id
account: "Account"
id: int
@property
def _rpc(self) -> Rpc:
return self.account._rpc
def __eq__(self, other) -> bool:
if not isinstance(other, Chat):
return False
return self.id == other.id and self.account == other.account
def __ne__(self, other) -> bool:
return not self == other
def __repr__(self) -> str:
return f"<Chat id={self.id} account={self.account.id}>"
async def delete(self) -> None:
"""Delete this chat and all its messages.
@@ -174,9 +164,9 @@ class Chat:
snapshot["message"] = Message(self.account, snapshot.id)
return snapshot
async def get_messages(self, flags: int = 0) -> List[Message]:
async def get_messages(self, info_only: bool = False, add_daymarker: bool = False) -> List[Message]:
"""get the list of messages in this chat."""
msgs = await self._rpc.get_message_ids(self.account.id, self.id, flags)
msgs = await self._rpc.get_message_ids(self.account.id, self.id, info_only, add_daymarker)
return [Message(self.account, msg_id) for msg_id in msgs]
async def get_fresh_message_count(self) -> int:

View File

@@ -1,4 +1,5 @@
from typing import TYPE_CHECKING
from dataclasses import dataclass
from ._utils import AttrDict
from .rpc import Rpc
@@ -8,6 +9,7 @@ if TYPE_CHECKING:
from .chat import Chat
@dataclass
class Contact:
"""
Contact API.
@@ -15,20 +17,8 @@ class Contact:
Essentially a wrapper for RPC, account ID and a contact ID.
"""
def __init__(self, account: "Account", contact_id: int) -> None:
self.account = account
self.id = contact_id
def __eq__(self, other) -> bool:
if not isinstance(other, Contact):
return False
return self.id == other.id and self.account == other.account
def __ne__(self, other) -> bool:
return not self == other
def __repr__(self) -> str:
return f"<Contact id={self.id} account={self.account.id}>"
account: "Account"
id: int
@property
def _rpc(self) -> Rpc:

View File

@@ -96,6 +96,9 @@ class NewMessage(EventFilter):
content.
:param command: If set, only match messages with the given command (ex. /help).
Setting this property implies `is_info==False`.
:param is_bot: If set to True only match messages sent by bots, if set to None
match messages from bots and users. If omitted or set to False
only messages from users will be matched.
:param is_info: If set to True only match info/system messages, if set to False
only match messages that are not info/system messages. If omitted
info/system messages as well as normal messages will be matched.
@@ -113,10 +116,12 @@ class NewMessage(EventFilter):
re.Pattern,
] = None,
command: Optional[str] = None,
is_bot: Optional[bool] = False,
is_info: Optional[bool] = None,
func: Optional[Callable[[AttrDict], bool]] = None,
) -> None:
super().__init__(func=func)
self.is_bot = is_bot
self.is_info = is_info
if command is not None and not isinstance(command, str):
raise TypeError("Invalid command")
@@ -133,19 +138,28 @@ class NewMessage(EventFilter):
raise TypeError("Invalid pattern type")
def __hash__(self) -> int:
return hash((self.pattern, self.func))
return hash((self.pattern, self.command, self.is_bot, self.is_info, self.func))
def __eq__(self, other) -> bool:
if isinstance(other, NewMessage):
return (self.pattern, self.command, self.is_info, self.func) == (
return (
self.pattern,
self.command,
self.is_bot,
self.is_info,
self.func,
) == (
other.pattern,
other.command,
other.is_bot,
other.is_info,
other.func,
)
return False
async def filter(self, event: AttrDict) -> bool:
if self.is_bot is not None and self.is_bot != event.message_snapshot.is_bot:
return False
if self.is_info is not None and self.is_info != event.message_snapshot.is_info:
return False
if self.command and self.command != event.command:

View File

@@ -1,5 +1,6 @@
import json
from typing import TYPE_CHECKING, Union
from dataclasses import dataclass
from ._utils import AttrDict
from .contact import Contact
@@ -9,23 +10,12 @@ if TYPE_CHECKING:
from .account import Account
@dataclass
class Message:
"""Delta Chat Message object."""
def __init__(self, account: "Account", msg_id: int) -> None:
self.account = account
self.id = msg_id
def __eq__(self, other) -> bool:
if not isinstance(other, Message):
return False
return self.id == other.id and self.account == other.account
def __ne__(self, other) -> bool:
return not self == other
def __repr__(self) -> str:
return f"<Message id={self.id} account={self.account.id}>"
account: "Account"
id: int
@property
def _rpc(self) -> Rpc:

View File

@@ -2,6 +2,7 @@ import json
import os
from typing import AsyncGenerator, List, Optional
import asyncio
import aiohttp
import pytest_asyncio
@@ -51,11 +52,13 @@ class ACFactory:
await bot.configure(credentials["email"], credentials["password"])
return bot
async def get_online_account(self) -> Account:
account = await self.new_configured_account()
await account.start_io()
return account
async def get_online_accounts(self, num: int) -> List[Account]:
accounts = [await self.new_configured_account() for _ in range(num)]
for account in accounts:
await account.start_io()
return accounts
return await asyncio.gather(*[self.get_online_account() for _ in range(num)])
async def send_message(
self,

View File

@@ -1,6 +1,7 @@
from unittest.mock import MagicMock
import pytest
import asyncio
from deltachat_rpc_client import EventType, events
from deltachat_rpc_client.rpc import JsonRpcError
@@ -13,6 +14,17 @@ async def test_system_info(rpc) -> None:
assert "deltachat_core_version" in system_info
@pytest.mark.asyncio()
async def test_sleep(rpc) -> None:
"""Test that long-running task does not block short-running task from completion."""
sleep_5_task = asyncio.create_task(rpc.sleep(5.0))
sleep_3_task = asyncio.create_task(rpc.sleep(3.0))
done, pending = await asyncio.wait([sleep_5_task, sleep_3_task], return_when=asyncio.FIRST_COMPLETED)
assert sleep_3_task in done
assert sleep_5_task in pending
sleep_5_task.cancel()
@pytest.mark.asyncio()
async def test_email_address_validity(rpc) -> None:
valid_addresses = [
@@ -47,6 +59,7 @@ async def test_configure_starttls(acfactory) -> None:
# Use STARTTLS
await account.set_config("mail_security", "2")
await account.set_config("send_security", "2")
await account.configure()
assert await account.is_configured()
@@ -215,6 +228,7 @@ async def test_message(acfactory) -> None:
snapshot = await message.get_snapshot()
assert snapshot.chat_id == chat_id
assert snapshot.text == "Hello!"
assert not snapshot.is_bot
assert repr(message)
with pytest.raises(JsonRpcError): # chat is not accepted
@@ -226,18 +240,46 @@ async def test_message(acfactory) -> None:
await message.send_reaction("😎")
@pytest.mark.asyncio()
async def test_is_bot(acfactory) -> None:
"""Test that we can recognize messages submitted by bots."""
alice, bob = await acfactory.get_online_accounts(2)
bob_addr = await bob.get_config("addr")
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
alice_chat_bob = await alice_contact_bob.create_chat()
# Alice becomes a bot.
await alice.set_config("bot", "1")
await alice_chat_bob.send_text("Hello!")
while True:
event = await bob.wait_for_event()
if event.type == EventType.INCOMING_MSG:
msg_id = event.msg_id
message = bob.get_message_by_id(msg_id)
snapshot = await message.get_snapshot()
assert snapshot.chat_id == event.chat_id
assert snapshot.text == "Hello!"
assert snapshot.is_bot
break
@pytest.mark.asyncio()
async def test_bot(acfactory) -> None:
mock = MagicMock()
user = (await acfactory.get_online_accounts(1))[0]
bot = await acfactory.new_configured_bot()
bot2 = await acfactory.new_configured_bot()
assert await bot.is_configured()
assert await bot.account.get_config("bot") == "1"
hook = lambda e: mock.hook(e.msg_id), events.RawEvent(EventType.INCOMING_MSG)
hook = lambda e: mock.hook(e.msg_id) and None, events.RawEvent(EventType.INCOMING_MSG)
bot.add_hook(*hook)
event = await acfactory.process_message(from_account=user, to_client=bot, text="Hello!")
snapshot = await bot.account.get_message_by_id(event.msg_id).get_snapshot()
assert not snapshot.is_bot
mock.hook.assert_called_once_with(event.msg_id)
bot.remove_hook(*hook)
@@ -252,6 +294,8 @@ async def test_bot(acfactory) -> None:
mock.hook.assert_called_with(event.msg_id)
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello!")
mock.hook.assert_called_with(event.msg_id)
await acfactory.process_message(from_account=bot2.account, to_client=bot, text="hello")
assert len(mock.hook.mock_calls) == 2 # bot messages are ignored between bots
await acfactory.process_message(from_account=user, to_client=bot, text="hey!")
assert len(mock.hook.mock_calls) == 2
bot.remove_hook(*hook)

View File

@@ -25,5 +25,5 @@ deps =
ruff
black
commands =
black --check src/ examples/ tests/
black --check --diff src/ examples/ tests/
ruff src/ examples/ tests/

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.106.0"
version = "1.108.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -21,5 +21,5 @@ futures-lite = "1.12.0"
log = "0.4"
serde_json = "1.0.91"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.23.1", features = ["io-std"] }
yerpc = { version = "0.3.1", features = ["anyhow_expose"] }
tokio = { version = "1.25.0", features = ["io-std"] }
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }

View File

@@ -0,0 +1,31 @@
# Delta Chat RPC server
This program provides a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) interface to DeltaChat
over standard I/O.
## Install
To install run:
```sh
cargo install --path ../deltachat-rpc-server
```
The `deltachat-rpc-server` executable will be installed into `$HOME/.cargo/bin` that should be available
in your `PATH`.
## Usage
To use just run `deltachat-rpc-server` command. The accounts folder will be created in the current
working directory unless `DC_ACCOUNTS_PATH` is set:
```sh
export DC_ACCOUNTS_PATH=$HOME/delta/
deltachat-rpc-server
```
The common use case for this program is to create bindings to use Delta Chat core from programming
languages other than Rust, for example:
1. Python: https://github.com/deltachat/deltachat-core-rust/tree/master/deltachat-rpc-client/
2. Go: https://github.com/deltachat/deltachat-rpc-client-go/

View File

@@ -40,7 +40,7 @@ async fn main() -> Result<()> {
while let Some(message) = out_receiver.next().await {
let message = serde_json::to_string(&message)?;
log::trace!("RPC send {}", message);
println!("{}", message);
println!("{message}");
}
Ok(())
});
@@ -51,7 +51,10 @@ async fn main() -> Result<()> {
let mut lines = BufReader::new(stdin).lines();
while let Some(message) = lines.next_line().await? {
log::trace!("RPC recv {}", message);
session.handle_incoming(&message).await;
let session = session.clone();
tokio::spawn(async move {
session.handle_incoming(&message).await;
});
}
log::info!("EOF reached on stdin");
Ok(())

View File

@@ -28,7 +28,7 @@ fn cb(event: EventType) {
}
}
/// Run with `RUST_LOG=simple=info cargo run --release --example simple --features repl -- email pw`.
/// Run with `RUST_LOG=simple=info cargo run --release --example simple -- email pw`.
#[tokio::main]
async fn main() {
pretty_env_logger::try_init_timed().ok();
@@ -74,7 +74,7 @@ async fn main() {
for i in 0..1 {
log::info!("sending message {}", i);
chat::send_text_msg(&ctx, chat_id, format!("Hi, here is my {}nth message!", i))
chat::send_text_msg(&ctx, chat_id, format!("Hi, here is my {i}nth message!"))
.await
.unwrap();
}

83
fuzz/Cargo.lock generated
View File

@@ -141,21 +141,18 @@ dependencies = [
[[package]]
name = "async-smtp"
version = "0.5.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6da21e1dd19fbad3e095ad519fb1558ab77fd82e5c4778dca8f9be0464589e1e"
checksum = "7384febcabdd07a498c9f4fbaa7e488ff4eb60d0ade14b47b09ec44b8f645301"
dependencies = [
"async-native-tls",
"async-trait",
"anyhow",
"base64 0.13.1",
"bufstream",
"fast-socks5",
"futures",
"hostname",
"log",
"nom 7.1.1",
"pin-project",
"pin-utils",
"thiserror",
"tokio",
]
@@ -235,9 +232,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
@@ -699,7 +696,7 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "deltachat"
version = "1.104.0"
version = "1.107.0"
dependencies = [
"anyhow",
"async-channel",
@@ -708,7 +705,7 @@ dependencies = [
"async-smtp",
"async_zip",
"backtrace",
"base64 0.20.0",
"base64 0.21.0",
"bitflags",
"chrono",
"deltachat_derive",
@@ -738,6 +735,7 @@ dependencies = [
"r2d2",
"r2d2_sqlite",
"rand 0.8.5",
"ratelimit",
"regex",
"reqwest",
"rusqlite",
@@ -1812,6 +1810,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.2"
@@ -1948,9 +1955,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.24.0+1.1.1s"
version = "111.25.0+1.1.1t"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd"
checksum = "3173cd3626c43e3854b1b727422a276e568d9ec5fe8cec197822cf52cfb743d6"
dependencies = [
"cc",
]
@@ -2326,6 +2333,10 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "ratelimit"
version = "1.0.0"
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -2363,11 +2374,11 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.13"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
dependencies = [
"base64 0.13.1",
"base64 0.21.0",
"bytes",
"encoding_rs",
"futures-core",
@@ -2578,6 +2589,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -2863,9 +2883,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.24.1"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg",
"bytes",
@@ -2952,11 +2972,36 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.10"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
]
[[package]]

View File

@@ -28,7 +28,7 @@ This code used to live at [`deltachat-node`](https://github.com/deltachat/deltac
## Install
By default the installation will build try to use the bundled prebuilds in the
By default the installation will try to use the bundled prebuilds in the
npm package. If this fails it falls back to compile `../deltachat-core-rust` from
this repository, using `scripts/rebuild-core.js`.

View File

@@ -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.106.0"
"version": "1.108.0"
}

View File

@@ -14,10 +14,10 @@ class EchoPlugin:
message.create_chat()
addr = message.get_sender_contact().addr
if message.is_system_message():
message.chat.send_text("echoing system message from {}:\n{}".format(addr, message))
message.chat.send_text(f"echoing system message from {addr}:\n{message}")
else:
text = message.text
message.chat.send_text("echoing from {}:\n{}".format(addr, text))
message.chat.send_text(f"echoing from {addr}:\n{text}")
@account_hookimpl
def ac_message_delivered(self, message):

View File

@@ -14,7 +14,7 @@ class GroupTrackingPlugin:
message.create_chat()
addr = message.get_sender_contact().addr
text = message.text
message.chat.send_text("echoing from {}:\n{}".format(addr, text))
message.chat.send_text(f"echoing from {addr}:\n{text}")
@account_hookimpl
def ac_outgoing_message(self, message):
@@ -28,7 +28,7 @@ class GroupTrackingPlugin:
def ac_chat_modified(self, chat):
print("ac_chat_modified:", chat.id, chat.get_name())
for member in chat.get_contacts():
print("chat member: {}".format(member.addr))
print(f"chat member: {member.addr}")
@account_hookimpl
def ac_member_added(self, chat, contact, actor, message):
@@ -40,7 +40,7 @@ class GroupTrackingPlugin:
),
)
for member in chat.get_contacts():
print("chat member: {}".format(member.addr))
print(f"chat member: {member.addr}")
@account_hookimpl
def ac_member_removed(self, chat, contact, actor, message):

View File

@@ -36,6 +36,11 @@ dynamic = [
[project.entry-points.pytest11]
"deltachat.testplugin" = "deltachat.testplugin"
[tool.setuptools.package-data]
deltachat = [
"py.typed"
]
[tool.setuptools_scm]
root = ".."
tag_regex = '^(?P<prefix>py-)?(?P<version>[^\+]+)(?P<suffix>.*)?$'
@@ -45,7 +50,7 @@ git_describe_command = "git describe --dirty --tags --long --match py-*.*"
line-length = 120
[tool.ruff]
select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM"]
select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP031", "UP032"]
line-length = 120
[tool.isort]

View File

@@ -55,6 +55,7 @@ def run_cmdline(argv=None, account_plugins=None):
ac.run_account(addr=args.email, password=args.password, account_plugins=account_plugins, show_ffi=args.show_ffi)
print("{}: waiting for message".format(ac.get_config("addr")))
addr = ac.get_config("addr")
print(f"{addr}: waiting for message")
ac.wait_shutdown()

View File

@@ -171,7 +171,7 @@ def extract_defines(flags):
match = defines_re.match(line)
if match:
defines.append(match.group(1))
return "\n".join("#define {} ...".format(d) for d in defines)
return "\n".join(f"#define {d} ..." for d in defines)
def ffibuilder():

View File

@@ -1,13 +1,12 @@
"""Account class implementation."""
from __future__ import print_function
import os
from array import array
from contextlib import contextmanager
from email.utils import parseaddr
from threading import Event
from typing import Any, Dict, Generator, List, Optional, Union
from typing import Any, Dict, Generator, List, Optional, Union, TYPE_CHECKING
from . import const, hookspec
from .capi import ffi, lib
@@ -20,10 +19,12 @@ from .cutil import (
from_optional_dc_charpointer,
iter_array,
)
from .events import EventThread, FFIEventLogger
from .message import Message
from .tracker import ConfigureTracker, ImexTracker
if TYPE_CHECKING:
from .events import FFIEventTracker
class MissingCredentials(ValueError):
"""Account is missing `addr` and `mail_pw` config values."""
@@ -54,7 +55,7 @@ def get_dc_info_as_dict(dc_context):
return info_dict
class Account(object):
class Account:
"""Each account is tied to a sqlite database file which is fully managed
by the underlying deltachat core library. All public Account methods are
meant to be memory-safe and return memory-safe objects.
@@ -62,7 +63,12 @@ class Account(object):
MissingCredentials = MissingCredentials
_logid: str
_evtracker: "FFIEventTracker"
def __init__(self, db_path, os_name=None, logging=True, closed=False) -> None:
from .events import EventThread
"""initialize account object.
:param db_path: a path to the account database. The database
@@ -84,7 +90,7 @@ class Account(object):
ptr = lib.dc_context_new_closed(db_path) if closed else lib.dc_context_new(ffi.NULL, db_path, ffi.NULL)
if ptr == ffi.NULL:
raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path))
raise ValueError(f"Could not dc_context_new: {os_name} {db_path}")
self._dc_context = ffi.gc(
ptr,
lib.dc_context_unref,
@@ -116,7 +122,7 @@ class Account(object):
self._logging = True
def __repr__(self):
return "<Account path={}>".format(self.db_path)
return f"<Account path={self.db_path}>"
# def __del__(self):
# self.shutdown()
@@ -127,7 +133,7 @@ class Account(object):
def _check_config_key(self, name: str) -> None:
if name not in self._configkeys:
raise KeyError("{!r} not a valid config key, existing keys: {!r}".format(name, self._configkeys))
raise KeyError(f"{name!r} not a valid config key, existing keys: {self._configkeys!r}")
def get_info(self) -> Dict[str, str]:
"""return dictionary of built config parameters."""
@@ -141,7 +147,7 @@ class Account(object):
log("=============== " + self.get_config("displayname") + " ===============")
cursor = 0
for name, val in self.get_info().items():
entry = "{}={}".format(name.upper(), val)
entry = f"{name.upper()}={val}"
if cursor + len(entry) > 80:
log("")
cursor = 0
@@ -186,7 +192,7 @@ class Account(object):
self._check_config_key(name)
namebytes = name.encode("utf8")
res = lib.dc_get_config(self._dc_context, namebytes)
assert res != ffi.NULL, "config value not found for: {!r}".format(name)
assert res != ffi.NULL, f"config value not found for: {name!r}"
return from_dc_charpointer(res)
def _preconfigure_keypair(self, addr: str, public: str, secret: str) -> None:
@@ -296,12 +302,12 @@ class Account(object):
addr, displayname = obj.get_config("addr"), obj.get_config("displayname")
elif isinstance(obj, Contact):
if obj.account != self:
raise ValueError("account mismatch {}".format(obj))
raise ValueError(f"account mismatch {obj}")
addr, displayname = obj.addr, obj.name
elif isinstance(obj, str):
displayname, addr = parseaddr(obj)
else:
raise TypeError("don't know how to create chat for %r" % (obj,))
raise TypeError(f"don't know how to create chat for {obj!r}")
if name is None and displayname:
name = displayname
@@ -368,7 +374,7 @@ class Account(object):
def get_fresh_messages(self) -> Generator[Message, None, None]:
"""yield all fresh messages from all chats."""
dc_array = ffi.gc(lib.dc_get_fresh_msgs(self._dc_context), lib.dc_array_unref)
yield from iter_array(dc_array, lambda x: Message.from_db(self, x))
return (x for x in iter_array(dc_array, lambda x: Message.from_db(self, x)) if x is not None)
def create_chat(self, obj) -> Chat:
"""Create a 1:1 chat with Account, Contact or e-mail address."""
@@ -413,7 +419,7 @@ class Account(object):
def get_device_chat(self) -> Chat:
return Contact(self, const.DC_CONTACT_ID_DEVICE).create_chat()
def get_message_by_id(self, msg_id: int) -> Message:
def get_message_by_id(self, msg_id: int) -> Optional[Message]:
"""return Message instance.
:param msg_id: integer id of this message.
:returns: :class:`deltachat.message.Message` instance.
@@ -428,7 +434,7 @@ class Account(object):
"""
res = lib.dc_get_chat(self._dc_context, chat_id)
if res == ffi.NULL:
raise ValueError("cannot get chat with id={}".format(chat_id))
raise ValueError(f"cannot get chat with id={chat_id}")
lib.dc_chat_unref(res)
return Chat(self, chat_id)
@@ -545,7 +551,7 @@ class Account(object):
res = ffi.gc(lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), lib.dc_lot_unref)
lot = DCLot(res)
if lot.state() == const.DC_QR_ERROR:
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
raise ValueError(f"invalid or unknown QR code: {lot.text1()}")
return ScannedQRCode(lot)
def qr_setup_contact(self, qr):
@@ -596,6 +602,8 @@ class Account(object):
#
def run_account(self, addr=None, password=None, account_plugins=None, show_ffi=False):
from .events import FFIEventLogger
"""get the account running, configure it if necessary. add plugins if provided.
:param addr: the email address of the account
@@ -616,8 +624,6 @@ class Account(object):
assert addr and password, "you must specify email and password once to configure this database/account"
self.set_config("addr", addr)
self.set_config("mail_pw", password)
self.set_config("mvbox_move", "0")
self.set_config("sentbox_watch", "0")
self.set_config("bot", "1")
configtracker = self.configure()
configtracker.wait_finish()
@@ -745,7 +751,7 @@ class Account(object):
try:
self._event_thread.wait(timeout=5)
except RuntimeError as e:
self.log("Waiting for event thread failed: {}".format(e))
self.log(f"Waiting for event thread failed: {e}")
if self._event_thread.is_alive():
self.log("WARN: event thread did not terminate yet, ignoring.")

View File

@@ -18,7 +18,7 @@ from .cutil import (
from .message import Message
class Chat(object):
class Chat:
"""Chat object which manages members and through which you can send and retrieve messages.
You obtain instances of it through :class:`deltachat.account.Account`.
@@ -40,7 +40,7 @@ class Chat(object):
return not self == other
def __repr__(self) -> str:
return "<Chat id={} name={}>".format(self.id, self.get_name())
return f"<Chat id={self.id} name={self.get_name()}>"
@property
def _dc_chat(self):
@@ -282,12 +282,20 @@ class Chat(object):
if msg.is_out_preparing():
assert msg.id != 0
# get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, msg.id)
maybe_msg = Message.from_db(self.account, msg.id)
if maybe_msg is not None:
msg = maybe_msg
else:
raise ValueError("message does not exist")
sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
# modify message in place to avoid bad state for the caller
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
sent_msg = Message.from_db(self.account, sent_id)
if sent_msg is None:
raise ValueError("cannot load just sent message from the database")
msg._dc_msg = sent_msg._dc_msg
return msg
def send_text(self, text):
@@ -444,7 +452,7 @@ class Chat(object):
contact = self.account.create_contact(obj)
ret = lib.dc_add_contact_to_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not add contact {!r} to chat".format(contact))
raise ValueError(f"could not add contact {contact!r} to chat")
return contact
def remove_contact(self, obj):
@@ -457,7 +465,7 @@ class Chat(object):
contact = self.account.get_contact(obj)
ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not remove contact {!r} from chat".format(contact))
raise ValueError(f"could not remove contact {contact!r} from chat")
def get_contacts(self):
"""get all contacts for this chat.
@@ -493,7 +501,7 @@ class Chat(object):
p = as_dc_charpointer(img_path)
res = lib.dc_set_chat_profile_image(self.account._dc_context, self.id, p)
if res != 1:
raise ValueError("Setting Profile Image {!r} failed".format(p))
raise ValueError(f"Setting Profile Image {p!r} failed")
def remove_profile_image(self):
"""remove group profile image.

View File

@@ -9,7 +9,7 @@ from .chat import Chat
from .cutil import from_dc_charpointer, from_optional_dc_charpointer
class Contact(object):
class Contact:
"""Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
@@ -31,7 +31,7 @@ class Contact(object):
return not self == other
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self.account._dc_context)
return f"<Contact id={self.id} addr={self.addr} dc_context={self.account._dc_context}>"
@property
def _dc_contact(self):

View File

@@ -82,7 +82,7 @@ class DirectImap:
configured, otherwise None.
"""
if "_" not in config_name:
config_name = "configured_{}_folder".format(config_name)
config_name = f"configured_{config_name}_folder"
foldername = self.account.get_config(config_name)
if foldername:
return self.select_folder(foldername)
@@ -203,7 +203,7 @@ class IdleManager:
"""(blocking) wait for next idle message from server."""
self.log("imap-direct: calling idle_check")
res = self.direct_imap.conn.idle.poll(timeout=timeout)
self.log("imap-direct: idle_check returned {!r}".format(res))
self.log(f"imap-direct: idle_check returned {res!r}")
return res
def wait_for_new_message(self, timeout=None) -> bytes:

View File

@@ -13,6 +13,7 @@ from .capi import ffi, lib
from .cutil import from_optional_dc_charpointer
from .hookspec import account_hookimpl
from .message import map_system_message
from .account import Account
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):
@@ -31,11 +32,11 @@ class FFIEvent:
def __str__(self):
if self.name == "DC_EVENT_INFO":
return "INFO {data2}".format(data2=self.data2)
return f"INFO {self.data2}"
if self.name == "DC_EVENT_WARNING":
return "WARNING {data2}".format(data2=self.data2)
return f"WARNING {self.data2}"
if self.name == "DC_EVENT_ERROR":
return "ERROR {data2}".format(data2=self.data2)
return f"ERROR {self.data2}"
return "{name} data1={data1} data2={data2}".format(**self.__dict__)
@@ -68,7 +69,7 @@ class FFIEventLogger:
locname = tname
if self.logid:
locname += "-" + self.logid
s = "{:2.2f} [{}] {}".format(elapsed, locname, message)
s = f"{elapsed:2.2f} [{locname}] {message}"
if os.name == "posix":
WARN = "\033[93m"
@@ -103,7 +104,7 @@ class FFIEventTracker:
timeout = timeout if timeout is not None else self._timeout
ev = self._event_queue.get(timeout=timeout)
if check_error and ev.name == "DC_EVENT_ERROR":
raise ValueError("unexpected event: {}".format(ev))
raise ValueError(f"unexpected event: {ev}")
return ev
def iter_events(self, timeout=None, check_error=True):
@@ -111,7 +112,7 @@ class FFIEventTracker:
yield self.get(timeout=timeout, check_error=check_error)
def get_matching(self, event_name_regex, check_error=True, timeout=None):
rex = re.compile("^(?:{})$".format(event_name_regex))
rex = re.compile(f"^(?:{event_name_regex})$")
for ev in self.iter_events(timeout=timeout, check_error=check_error):
if rex.match(ev.name):
return ev
@@ -162,20 +163,20 @@ class FFIEventTracker:
def ensure_event_not_queued(self, event_name_regex):
__tracebackhide__ = True
rex = re.compile("(?:{}).*".format(event_name_regex))
rex = re.compile(f"(?:{event_name_regex}).*")
while 1:
try:
ev = self._event_queue.get(False)
except Empty:
break
else:
assert not rex.match(ev.name), "event found {}".format(ev)
assert not rex.match(ev.name), f"event found {ev}"
def wait_securejoin_inviter_progress(self, target):
while 1:
event = self.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS")
if event.data2 >= target:
print("** SECUREJOINT-INVITER PROGRESS {}".format(target), self.account)
print(f"** SECUREJOINT-INVITER PROGRESS {target}", self.account)
break
def wait_idle_inbox_ready(self):
@@ -221,7 +222,7 @@ class EventThread(threading.Thread):
With each Account init this callback thread is started.
"""
def __init__(self, account) -> None:
def __init__(self, account: Account) -> None:
self.account = account
super(EventThread, self).__init__(name="events")
self.daemon = True
@@ -247,36 +248,37 @@ class EventThread(threading.Thread):
def run(self) -> None:
"""get and run events until shutdown."""
with self.log_execution("EVENT THREAD"):
self._inner_run()
event_emitter = ffi.gc(
lib.dc_get_event_emitter(self.account._dc_context),
lib.dc_event_emitter_unref,
)
while not self._marked_for_shutdown:
with self.swallow_and_log_exception("Unexpected error in event thread"):
event = lib.dc_get_next_event(event_emitter)
if event == ffi.NULL or self._marked_for_shutdown:
break
self._process_event(event)
def _inner_run(self):
event_emitter = ffi.gc(
lib.dc_get_event_emitter(self.account._dc_context),
lib.dc_event_emitter_unref,
)
while not self._marked_for_shutdown:
event = lib.dc_get_next_event(event_emitter)
if event == ffi.NULL or self._marked_for_shutdown:
break
evt = lib.dc_event_get_id(event)
data1 = lib.dc_event_get_data1_int(event)
# the following code relates to the deltachat/_build.py's helper
# function which provides us signature info of an event call
evt_name = get_dc_event_name(evt)
if lib.dc_event_has_string_data(evt):
data2 = from_optional_dc_charpointer(lib.dc_event_get_data2_str(event))
else:
data2 = lib.dc_event_get_data2_int(event)
def _process_event(self, event) -> None:
evt = lib.dc_event_get_id(event)
data1 = lib.dc_event_get_data1_int(event)
# the following code relates to the deltachat/_build.py's helper
# function which provides us signature info of an event call
evt_name = get_dc_event_name(evt)
if lib.dc_event_has_string_data(evt):
data2 = from_optional_dc_charpointer(lib.dc_event_get_data2_str(event))
else:
data2 = lib.dc_event_get_data2_int(event)
lib.dc_event_unref(event)
ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2)
with self.swallow_and_log_exception("ac_process_ffi_event {}".format(ffi_event)):
self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event)
for name, kwargs in self._map_ffi_event(ffi_event):
hook = getattr(self.account._pm.hook, name)
info = "call {} kwargs={} failed".format(name, kwargs)
with self.swallow_and_log_exception(info):
hook(**kwargs)
lib.dc_event_unref(event)
ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2)
with self.swallow_and_log_exception(f"ac_process_ffi_event {ffi_event}"):
self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event)
for name, kwargs in self._map_ffi_event(ffi_event):
hook = getattr(self.account._pm.hook, name)
info = f"call {name} kwargs={kwargs} failed"
with self.swallow_and_log_exception(info):
hook(**kwargs)
@contextmanager
def swallow_and_log_exception(self, info):
@@ -285,7 +287,7 @@ class EventThread(threading.Thread):
except Exception as ex:
logfile = io.StringIO()
traceback.print_exception(*sys.exc_info(), file=logfile)
self.account.log("{}\nException {}\nTraceback:\n{}".format(info, ex, logfile.getvalue()))
self.account.log(f"{info}\nException {ex}\nTraceback:\n{logfile.getvalue()}")
def _map_ffi_event(self, ffi_event: FFIEvent):
name = ffi_event.name
@@ -298,20 +300,22 @@ class EventThread(threading.Thread):
yield "ac_configure_completed", {"success": success, "comment": comment}
elif name == "DC_EVENT_INCOMING_MSG":
msg = account.get_message_by_id(ffi_event.data2)
yield map_system_message(msg) or ("ac_incoming_message", {"message": msg})
if msg is not None:
yield map_system_message(msg) or ("ac_incoming_message", {"message": msg})
elif name == "DC_EVENT_MSGS_CHANGED":
if ffi_event.data2 != 0:
msg = account.get_message_by_id(ffi_event.data2)
if msg.is_outgoing():
res = map_system_message(msg)
if res and res[0].startswith("ac_member"):
yield res
yield "ac_outgoing_message", {"message": msg}
elif msg.is_in_fresh():
yield map_system_message(msg) or (
"ac_incoming_message",
{"message": msg},
)
if msg is not None:
if msg.is_outgoing():
res = map_system_message(msg)
if res and res[0].startswith("ac_member"):
yield res
yield "ac_outgoing_message", {"message": msg}
elif msg.is_in_fresh():
yield map_system_message(msg) or (
"ac_incoming_message",
{"message": msg},
)
elif name == "DC_EVENT_REACTIONS_CHANGED":
assert ffi_event.data1 > 0
msg = account.get_message_by_id(ffi_event.data2)

View File

@@ -12,7 +12,7 @@ from .cutil import as_dc_charpointer, from_dc_charpointer, from_optional_dc_char
from .reactions import Reactions
class Message(object):
class Message:
"""Message object.
You obtain instances of it through :class:`deltachat.account.Account` or
@@ -36,21 +36,21 @@ class Message(object):
def __repr__(self):
c = self.get_sender_contact()
typ = "outgoing" if self.is_outgoing() else "incoming"
return "<Message {} sys={} {} id={} sender={}/{} chat={}/{}>".format(
typ,
self.is_system_message(),
repr(self.text[:10]),
self.id,
c.id,
c.addr,
self.chat.id,
self.chat.get_name(),
return (
f"<Message {typ} sys={self.is_system_message()} {repr(self.text[:100])} "
f"id={self.id} sender={c.id}/{c.addr} chat={self.chat.id}/{self.chat.get_name()}>"
)
@classmethod
def from_db(cls, account, id):
def from_db(cls, account, id) -> Optional["Message"]:
"""Attempt to load the message from the database given its ID.
None is returned if the message does not exist, i.e. deleted."""
assert id > 0
return cls(account, ffi.gc(lib.dc_get_msg(account._dc_context, id), lib.dc_msg_unref))
res = lib.dc_get_msg(account._dc_context, id)
if res == ffi.NULL:
return None
return cls(account, ffi.gc(res, lib.dc_msg_unref))
@classmethod
def new_empty(cls, account, view_type):
@@ -115,7 +115,7 @@ class Message(object):
"""set file for this message from path and mime_type."""
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
if not os.path.exists(path):
raise ValueError("path does not exist: {!r}".format(path))
raise ValueError(f"path does not exist: {path!r}")
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
@props.with_doc

View File

@@ -8,7 +8,7 @@ class ProviderNotFoundError(Exception):
"""The provider information was not found."""
class Provider(object):
class Provider:
"""
Provider information.

View File

View File

@@ -4,7 +4,7 @@ from .capi import ffi, lib
from .cutil import from_dc_charpointer, iter_array
class Reactions(object):
class Reactions:
"""Reactions object.
You obtain instances of it through :class:`deltachat.message.Message`.
@@ -18,7 +18,7 @@ class Reactions(object):
self._dc_reactions = dc_reactions
def __repr__(self):
return "<Reactions dc_reactions={}>".format(self._dc_reactions)
return f"<Reactions dc_reactions={self._dc_reactions}>"
@classmethod
def from_msg(cls, msg):

View File

@@ -1,5 +1,3 @@
from __future__ import print_function
import fnmatch
import io
import os
@@ -131,9 +129,9 @@ def pytest_report_header(config, startdir):
if cfg:
if "?" in cfg:
url, token = cfg.split("?", 1)
summary.append("Liveconfig provider: {}?<token ommitted>".format(url))
summary.append(f"Liveconfig provider: {url}?<token ommitted>")
else:
summary.append("Liveconfig file: {}".format(cfg))
summary.append(f"Liveconfig file: {cfg}")
return summary
@@ -178,13 +176,13 @@ class TestProcess:
except IndexError:
res = requests.post(liveconfig_opt, timeout=60)
if res.status_code != 200:
pytest.fail("newtmpuser count={} code={}: '{}'".format(index, res.status_code, res.text))
pytest.fail(f"newtmpuser count={index} code={res.status_code}: '{res.text}'")
d = res.json()
config = {"addr": d["email"], "mail_pw": d["password"]}
print("newtmpuser {}: addr={}".format(index, config["addr"]))
self._configlist.append(config)
yield config
pytest.fail("more than {} live accounts requested.".format(MAX_LIVE_CREATED_ACCOUNTS))
pytest.fail(f"more than {MAX_LIVE_CREATED_ACCOUNTS} live accounts requested.")
def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path):
db_target_path = pathlib.Path(db_target_path)
@@ -252,7 +250,7 @@ def data(request):
fn = os.path.join(path, *bn.split("/"))
if os.path.exists(fn):
return fn
print("WARNING: path does not exist: {!r}".format(fn))
print(f"WARNING: path does not exist: {fn!r}")
return None
def read_path(self, bn, mode="r"):
@@ -277,6 +275,8 @@ class ACSetup:
CONFIGURED = "CONFIGURED"
IDLEREADY = "IDLEREADY"
_configured_events: Queue
def __init__(self, testprocess, init_time):
self._configured_events = Queue()
self._account2state = {}
@@ -285,7 +285,7 @@ class ACSetup:
self.init_time = init_time
def log(self, *args):
print("[acsetup]", "{:.3f}".format(time.time() - self.init_time), *args)
print("[acsetup]", f"{time.time() - self.init_time:.3f}", *args)
def add_configured(self, account):
"""add an already configured account."""
@@ -339,7 +339,7 @@ class ACSetup:
def _pop_config_success(self):
acc, success, comment = self._configured_events.get()
if not success:
pytest.fail("configuring online account {} failed: {}".format(acc, comment))
pytest.fail(f"configuring online account {acc} failed: {comment}")
self._account2state[acc] = self.CONFIGURED
return acc
@@ -373,13 +373,18 @@ class ACSetup:
imap.delete("1:*", expunge=True)
else:
imap.conn.folder.delete(folder)
acc.log("imap cleaned for addr {}".format(addr))
acc.log(f"imap cleaned for addr {addr}")
self._imap_cleaned.add(addr)
class ACFactory:
"""Account factory"""
init_time: float
_finalizers: List[Callable[[], None]]
_accounts: List[Account]
_acsetup: ACSetup
_preconfigured_keys: List[str]
def __init__(self, request, testprocess, tmpdir, data) -> None:
self.init_time = time.time()
@@ -397,7 +402,7 @@ class ACFactory:
request.addfinalizer(self.finalize)
def log(self, *args):
print("[acfactory]", "{:.3f}".format(time.time() - self.init_time), *args)
print("[acfactory]", f"{time.time() - self.init_time:.3f}", *args)
def finalize(self):
while self._finalizers:
@@ -431,15 +436,16 @@ class ACFactory:
assert "addr" in configdict and "mail_pw" in configdict
return configdict
def _get_cached_account(self, addr):
def _get_cached_account(self, addr) -> Optional[Account]:
if addr in self.testprocess._addr2files:
return self._getaccount(addr)
return None
def get_unconfigured_account(self, closed=False):
def get_unconfigured_account(self, closed=False) -> Account:
return self._getaccount(closed=closed)
def _getaccount(self, try_cache_addr=None, closed=False):
logid = "ac{}".format(len(self._accounts) + 1)
def _getaccount(self, try_cache_addr=None, closed=False) -> Account:
logid = f"ac{len(self._accounts) + 1}"
# we need to use fixed database basename for maybe_cache_* functions to work
path = self.tmpdir.mkdir(logid).join("dc.db")
if try_cache_addr:
@@ -452,10 +458,10 @@ class ACFactory:
self._accounts.append(ac)
return ac
def set_logging_default(self, logging):
def set_logging_default(self, logging) -> None:
self._logging = bool(logging)
def remove_preconfigured_keys(self):
def remove_preconfigured_keys(self) -> None:
self._preconfigured_keys = []
def _preconfigure_key(self, account, addr):
@@ -465,12 +471,12 @@ class ACFactory:
except IndexError:
pass
else:
fname_pub = self.data.read_path("key/{name}-public.asc".format(name=keyname))
fname_sec = self.data.read_path("key/{name}-secret.asc".format(name=keyname))
fname_pub = self.data.read_path(f"key/{keyname}-public.asc")
fname_sec = self.data.read_path(f"key/{keyname}-secret.asc")
if fname_pub and fname_sec:
account._preconfigure_keypair(addr, fname_pub, fname_sec)
return True
print("WARN: could not use preconfigured keys for {!r}".format(addr))
print(f"WARN: could not use preconfigured keys for {addr!r}")
def get_pseudo_configured_account(self, passphrase: Optional[str] = None) -> Account:
# do a pseudo-configured account
@@ -478,7 +484,7 @@ class ACFactory:
if passphrase:
ac.open(passphrase)
acname = ac._logid
addr = "{}@offline.org".format(acname)
addr = f"{acname}@offline.org"
ac.update_config(
{
"addr": addr,
@@ -493,7 +499,7 @@ class ACFactory:
self._acsetup.init_logging(ac)
return ac
def new_online_configuring_account(self, cloned_from=None, cache=False, **kwargs):
def new_online_configuring_account(self, cloned_from=None, cache=False, **kwargs) -> Account:
if cloned_from is None:
configdict = self.get_next_liveconfig()
else:
@@ -515,7 +521,7 @@ class ACFactory:
self._acsetup.start_configure(ac)
return ac
def prepare_account_from_liveconfig(self, configdict):
def prepare_account_from_liveconfig(self, configdict) -> Account:
ac = self.get_unconfigured_account()
assert "addr" in configdict and "mail_pw" in configdict, configdict
configdict.setdefault("bcc_self", False)
@@ -525,11 +531,11 @@ class ACFactory:
self._preconfigure_key(ac, configdict["addr"])
return ac
def wait_configured(self, account):
def wait_configured(self, account) -> None:
"""Wait until the specified account has successfully completed configure."""
self._acsetup.wait_one_configured(account)
def bring_accounts_online(self):
def bring_accounts_online(self) -> None:
print("bringing accounts online")
self._acsetup.bring_online()
print("all accounts online")

View File

@@ -38,7 +38,7 @@ class ImexTracker:
if isinstance(ev, str):
files_written.append(ev)
elif ev == 0:
raise ImexFailed("export failed, exp-files: {}".format(files_written))
raise ImexFailed(f"export failed, exp-files: {files_written}")
elif ev == 1000:
return files_written

View File

@@ -14,7 +14,7 @@ def test_db_busy_error(acfactory, tmpdir):
def log(string):
with log_lock:
print("%3.2f %s" % (time.time() - starttime, string))
print(f"{time.time() - starttime:3.2f} {string}")
# make a number of accounts
accounts = acfactory.get_many_online_accounts(3)
@@ -59,15 +59,15 @@ def test_db_busy_error(acfactory, tmpdir):
if report_type == ReportType.exit:
replier.log("EXIT")
elif report_type == ReportType.ffi_error:
replier.log("ERROR: {}".format(report_args[0]))
replier.log(f"ERROR: {report_args[0]}")
elif report_type == ReportType.message_echo:
continue
else:
raise ValueError("{} unknown report type {}, args={}".format(addr, report_type, report_args))
raise ValueError(f"{addr} unknown report type {report_type}, args={report_args}")
alive_count -= 1
replier.log("shutting down")
replier.account.shutdown()
replier.log("shut down complete, remaining={}".format(alive_count))
replier.log(f"shut down complete, remaining={alive_count}")
class ReportType:
@@ -86,12 +86,12 @@ class AutoReplier:
self.current_sent = 0
self.addr = self.account.get_self_contact().addr
self._thread = threading.Thread(name="Stats{}".format(self.account), target=self.thread_stats)
self._thread = threading.Thread(name=f"Stats{self.account}", target=self.thread_stats)
self._thread.setDaemon(True)
self._thread.start()
def log(self, message):
self._log("{} {}".format(self.addr, message))
self._log(f"{self.addr} {message}")
def thread_stats(self):
# XXX later use, for now we just quit
@@ -107,17 +107,17 @@ class AutoReplier:
return
message.create_chat()
message.mark_seen()
self.log("incoming message: {}".format(message))
self.log(f"incoming message: {message}")
self.current_sent += 1
# we are still alive, let's send a reply
if self.num_bigfiles and self.current_sent % (self.num_send / self.num_bigfiles) == 0:
message.chat.send_text("send big file as reply to: {}".format(message.text))
message.chat.send_text(f"send big file as reply to: {message.text}")
msg = message.chat.send_file(self.account.bigfile)
else:
msg = message.chat.send_text("got message id {}, small text reply".format(message.id))
msg = message.chat.send_text(f"got message id {message.id}, small text reply")
assert msg.text
self.log("message-sent: {}".format(msg))
self.log(f"message-sent: {msg}")
self.report_func(self, ReportType.message_echo)
if self.current_sent >= self.num_send:
self.report_func(self, ReportType.exit)

View File

@@ -239,7 +239,7 @@ def test_fetch_existing(acfactory, lp, mvbox_move):
ac1_clone.start_io()
assert_folders_configured(ac1_clone)
lp.sec("check that ac2 contact was fetchted during configure")
lp.sec("check that ac2 contact was fetched during configure")
ac1_clone._evtracker.get_matching("DC_EVENT_CONTACTS_CHANGED")
ac2_addr = ac2.get_config("addr")
assert any(c.addr == ac2_addr for c in ac1_clone.get_contacts())
@@ -537,3 +537,54 @@ def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
msg_in = ac1_offl._evtracker.wait_next_incoming_message()
assert msg_in.text == msg_out.text
assert msg_in.get_sender_contact().addr == ac2_addr
ac1.set_config("bcc_self", "0")
def test_use_new_verified_group_after_going_online(acfactory, tmpdir, lp):
"""Another test for the bug #3836:
- Bob has two devices, the second is offline.
- Alice creates a verified group and sends a QR invitation to Bob.
- Bob joins the group.
- Bob's second devices goes online, but sees a contact request instead of the verified group.
- The "member added" message is not a system message but a plain text message.
- Sending a message fails as the key is missing -- message info says "proper enc-key for <Alice>
missing, cannot encrypt".
"""
ac1, ac2 = acfactory.get_online_accounts(2)
ac2_offl = acfactory.new_online_configuring_account(cloned_from=ac2)
for ac in [ac2, ac2_offl]:
ac.set_config("bcc_self", "1")
acfactory.bring_accounts_online()
dir = tmpdir.mkdir("exportdir")
ac2.export_self_keys(dir.strpath)
ac2_offl.import_self_keys(dir.strpath)
ac2_offl.stop_io()
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
chat = ac1.create_group_chat("hello", verified=True)
assert chat.is_protected()
qr = chat.get_join_qr()
lp.sec("ac2: start QR-code based join-group protocol")
ac2.qr_join_chat(qr)
ac1._evtracker.wait_securejoin_inviter_progress(1000)
lp.sec("ac2_offl: going online, checking the 'member added' message")
ac2_offl.start_io()
# Receive "Member Me (<addr>) added by <addr>." message.
msg_in = ac2_offl._evtracker.wait_next_incoming_message()
assert msg_in.is_system_message()
assert msg_in.get_sender_contact().addr == ac1.get_config("addr")
chat2 = msg_in.chat
assert chat2.is_protected()
lp.sec("ac2_offl: sending message")
msg_out = chat2.send_text("hello")
lp.sec("ac1: receiving message")
msg_in = ac1._evtracker.wait_next_incoming_message()
assert msg_in.chat == chat
assert msg_in.get_sender_contact().addr == ac2.get_config("addr")
assert msg_in.text == msg_out.text
ac2.set_config("bcc_self", "0")

View File

@@ -1249,7 +1249,7 @@ def test_send_mark_seen_clean_incoming_events(acfactory, lp):
msg = ac2._evtracker.wait_next_incoming_message()
assert msg.text == "hello"
lp.sec("ac2: mark seen {}".format(msg))
lp.sec(f"ac2: mark seen {msg}")
msg.mark_seen()
for ev in ac1._evtracker.iter_events():
@@ -1437,9 +1437,8 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
backupdir = tmpdir.mkdir("backup")
lp.sec("export all to {}".format(backupdir))
lp.sec(f"export all to {backupdir}")
with ac1.temp_plugin(ImexTracker()) as imex_tracker:
ac1.stop_io()
ac1.imex(backupdir.strpath, const.DC_IMEX_EXPORT_BACKUP)
@@ -1475,7 +1474,7 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
assert_account_is_proper(ac1)
assert_account_is_proper(ac2)
lp.sec("Second-time export all to {}".format(backupdir))
lp.sec(f"Second-time export all to {backupdir}")
ac1.stop_io()
path2 = ac1.export_all(backupdir.strpath)
assert os.path.exists(path2)

View File

@@ -1,5 +1,3 @@
from __future__ import print_function
import os.path
import shutil
from filecmp import cmp
@@ -17,7 +15,7 @@ def wait_msg_delivered(account, msg_list):
def wait_msgs_changed(account, msgs_list):
"""wait for one or more MSGS_CHANGED events to match msgs_list contents."""
account.log("waiting for msgs_list={}".format(msgs_list))
account.log(f"waiting for msgs_list={msgs_list}")
msgs_list = list(msgs_list)
while msgs_list:
ev = account._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -27,7 +25,7 @@ def wait_msgs_changed(account, msgs_list):
del msgs_list[i]
break
else:
account.log("waiting mismatch data1={} data2={}".format(data1, data2))
account.log(f"waiting mismatch data1={data1} data2={data2}")
return ev.data1, ev.data2

View File

@@ -1,5 +1,3 @@
from __future__ import print_function
import os
import time
from datetime import datetime, timedelta, timezone
@@ -744,7 +742,7 @@ class TestOfflineChat:
contacts = []
for i in range(10):
lp.sec("create contact")
contact = ac1.create_contact("some{}@example.org".format(i))
contact = ac1.create_contact(f"some{i}@example.org")
contacts.append(contact)
lp.sec("add contact")
chat.add_contact(contact)

View File

@@ -56,7 +56,7 @@ deps =
pygments
restructuredtext_lint
commands =
black --check setup.py install_python_bindings.py src/deltachat examples/ tests/
black --check --diff setup.py install_python_bindings.py src/deltachat examples/ tests/
ruff src/deltachat tests/ examples/
rst-lint --encoding 'utf-8' README.rst

View File

@@ -6,6 +6,8 @@ and an own build machine.
## Description of scripts
- `clippy.sh` runs `cargo clippy` for all Rust code in the project.
- `../.github/workflows` contains jobs run by GitHub Actions.
- `remote_tests_python.sh` rsyncs to a build machine and runs

3
scripts/clippy.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
# Run clippy for all Rust code in the project.
cargo clippy --workspace --tests --examples --benches -- -D warnings

View File

@@ -31,7 +31,7 @@ unset DCC_NEW_TMP_EMAIL
# Try to build wheels for a range of interpreters, but don't fail if they are not available.
# E.g. musllinux_1_1 does not have PyPy interpreters as of 2022-07-10
tox --workdir "$TOXWORKDIR" -e py37,py38,py39,py310,pypy37,pypy38,pypy39,auditwheels --skip-missing-interpreters true
tox --workdir "$TOXWORKDIR" -e py37,py38,py39,py310,py311,pypy37,pypy38,pypy39,auditwheels --skip-missing-interpreters true
echo -----------------------

View File

@@ -69,6 +69,7 @@ def main():
"deltachat-ffi/Cargo.toml",
"deltachat-jsonrpc/Cargo.toml",
"deltachat-rpc-server/Cargo.toml",
"deltachat-repl/Cargo.toml",
]
try:
opts = parser.parse_args()

View File

@@ -143,34 +143,16 @@ impl Accounts {
let ctx = self
.accounts
.remove(&id)
.with_context(|| format!("no account with id {}", id))?;
.with_context(|| format!("no account with id {id}"))?;
ctx.stop_io().await;
drop(ctx);
if let Some(cfg) = self.config.get_account(id) {
let account_path = self.dir.join(cfg.dir);
// Spend up to 1 minute trying to remove the files.
// Files may remain locked up to 30 seconds due to r2d2 bug:
// https://github.com/sfackler/r2d2/issues/99
let mut counter = 0;
loop {
counter += 1;
if let Err(err) = fs::remove_dir_all(&account_path)
.await
.context("failed to remove account data")
{
if counter > 60 {
return Err(err);
}
// Wait 1 second and try again.
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
} else {
break;
}
}
fs::remove_dir_all(&account_path)
.await
.context("failed to remove account data")?;
}
self.config.remove_account(id).await?;
@@ -178,6 +160,8 @@ impl Accounts {
}
/// Migrate an existing account into this structure.
///
/// Returns the ID of new account.
pub async fn migrate_account(&mut self, dbfile: PathBuf) -> Result<u32> {
let blobdir = Context::derive_blobdir(&dbfile);
let walfile = Context::derive_walfile(&dbfile);
@@ -229,11 +213,10 @@ impl Accounts {
Ok(account_config.id)
}
Err(err) => {
// remove temp account
fs::remove_dir_all(std::path::PathBuf::from(&account_config.dir))
let account_path = std::path::PathBuf::from(&account_config.dir);
fs::remove_dir_all(&account_path)
.await
.context("failed to remove account data")?;
self.config.remove_account(account_config.id).await?;
// set selection back
@@ -363,7 +346,8 @@ impl Config {
pub async fn from_file(file: PathBuf) -> Result<Self> {
let dir = file.parent().context("can't get config file directory")?;
let bytes = fs::read(&file).await.context("failed to read file")?;
let mut inner: InnerConfig = toml::from_slice(&bytes).context("failed to parse config")?;
let s = std::str::from_utf8(&bytes)?;
let mut inner: InnerConfig = toml::from_str(s).context("failed to parse config")?;
// Previous versions of the core stored absolute paths in account config.
// Convert them to relative paths.

View File

@@ -11,20 +11,15 @@ use anyhow::{bail, Context as _, Error, Result};
use crate::key::{DcKey, SignedPublicKey};
/// Possible values for encryption preference
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)]
#[derive(PartialEq, Eq, Debug, Default, Clone, Copy, FromPrimitive, ToPrimitive)]
#[repr(u8)]
pub enum EncryptPreference {
#[default]
NoPreference = 0,
Mutual = 1,
Reset = 20,
}
impl Default for EncryptPreference {
fn default() -> Self {
EncryptPreference::NoPreference
}
}
impl fmt::Display for EncryptPreference {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
@@ -90,7 +85,7 @@ impl fmt::Display for Aheader {
res
},
);
write!(fmt, " keydata={}", keydata)
write!(fmt, " keydata={keydata}")
}
}
@@ -152,11 +147,8 @@ mod tests {
#[test]
fn test_from_str() -> Result<()> {
let h: Aheader = format!(
"addr=me@mail.com; prefer-encrypt=mutual; keydata={}",
RAWKEY
)
.parse()?;
let h: Aheader =
format!("addr=me@mail.com; prefer-encrypt=mutual; keydata={RAWKEY}").parse()?;
assert_eq!(h.addr, "me@mail.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
@@ -166,10 +158,7 @@ mod tests {
// EncryptPreference::Reset is an internal value, parser should never return it
#[test]
fn test_from_str_reset() -> Result<()> {
let raw = format!(
"addr=reset@example.com; prefer-encrypt=reset; keydata={}",
RAWKEY
);
let raw = format!("addr=reset@example.com; prefer-encrypt=reset; keydata={RAWKEY}");
let h: Aheader = raw.parse()?;
assert_eq!(h.addr, "reset@example.com");
@@ -179,7 +168,7 @@ mod tests {
#[test]
fn test_from_str_non_critical() -> Result<()> {
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={}", RAWKEY);
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={RAWKEY}");
let h: Aheader = raw.parse()?;
assert_eq!(h.addr, "me@mail.com");
@@ -189,10 +178,7 @@ mod tests {
#[test]
fn test_from_str_superflous_critical() {
let raw = format!(
"addr=me@mail.com; _foo=one; _bar=two; other=me; keydata={}",
RAWKEY
);
let raw = format!("addr=me@mail.com; _foo=one; _bar=two; other=me; keydata={RAWKEY}");
assert!(raw.parse::<Aheader>().is_err());
}
@@ -226,21 +212,20 @@ mod tests {
let ah = Aheader::from_str(fixed_header)?;
assert_eq!(ah.addr, "a@b.example.org");
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
assert_eq!(format!("{}", ah), fixed_header);
assert_eq!(format!("{ah}"), fixed_header);
let rendered = ah.to_string();
assert_eq!(rendered, fixed_header);
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {}", RAWKEY))?;
let ah = Aheader::from_str(&format!(" _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {RAWKEY}"))?;
assert_eq!(ah.addr, "a@b.example.org");
assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
Aheader::from_str(&format!(
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={}",
RAWKEY
"addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={RAWKEY}"
))?;
Aheader::from_str(&format!("addr=a@b.example.org; keydata={}", RAWKEY))?;
Aheader::from_str(&format!("addr=a@b.example.org; keydata={RAWKEY}"))?;
Ok(())
}

View File

@@ -644,7 +644,6 @@ Authentication-Results: dkim=";
.unwrap();
}
#[ignore = "Disallowing keychanges is disabled for now"]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_handle_authres_fails() -> Result<()> {
let mut tcm = TestContextManager::new();
@@ -822,8 +821,7 @@ Authentication-Results: dkim=";
.insert_str(0, "Authentication-Results: example.net; dkim=fail\n");
let rcvd = bob.recv_msg(&sent).await;
// Disallowing keychanges is disabled for now:
// assert!(rcvd.error.unwrap().contains("DKIM failed"));
assert!(rcvd.error.unwrap().contains("DKIM failed"));
// The message info should contain a warning:
assert!(message::get_msg_info(&bob, rcvd.id)
.await

View File

@@ -6,7 +6,7 @@ use std::fmt;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use anyhow::{format_err, Context as _, Error, Result};
use anyhow::{format_err, Context as _, Result};
use image::{DynamicImage, ImageFormat};
use num_traits::FromPrimitive;
use tokio::io::AsyncWriteExt;
@@ -59,7 +59,7 @@ impl<'a> BlobObject<'a> {
let blob = BlobObject {
blobdir,
name: format!("$BLOBDIR/{}", name),
name: format!("$BLOBDIR/{name}"),
};
context.emit_event(EventType::NewBlobFile(blob.as_name().to_string()));
Ok(blob)
@@ -74,7 +74,7 @@ impl<'a> BlobObject<'a> {
) -> Result<(String, fs::File)> {
const MAX_ATTEMPT: u32 = 16;
let mut attempt = 0;
let mut name = format!("{}{}", stem, ext);
let mut name = format!("{stem}{ext}");
loop {
attempt += 1;
let path = dir.join(&name);
@@ -124,7 +124,7 @@ impl<'a> BlobObject<'a> {
let blob = BlobObject {
blobdir: context.get_blobdir(),
name: format!("$BLOBDIR/{}", name),
name: format!("$BLOBDIR/{name}"),
};
context.emit_event(EventType::NewBlobFile(blob.as_name().to_string()));
Ok(blob)
@@ -184,7 +184,7 @@ impl<'a> BlobObject<'a> {
}
Ok(BlobObject {
blobdir: context.get_blobdir(),
name: format!("$BLOBDIR/{}", name),
name: format!("$BLOBDIR/{name}"),
})
}
@@ -285,7 +285,7 @@ impl<'a> BlobObject<'a> {
if ext.is_empty() {
(stem, "".to_string())
} else {
(stem, format!(".{}", ext).to_lowercase())
(stem, format!(".{ext}").to_lowercase())
// Return ("file", ".d_point_and_double_ending.tar.gz")
// which is not perfect but acceptable.
}
@@ -428,7 +428,7 @@ impl<'a> BlobObject<'a> {
blob_abs = blob_abs.with_extension("jpg");
let file_name = blob_abs.file_name().context("No avatar file name (???)")?;
let file_name = file_name.to_str().context("Filename is no UTF-8 (???)")?;
changed_name = Some(format!("$BLOBDIR/{}", file_name));
changed_name = Some(format!("$BLOBDIR/{file_name}"));
}
if encoded.is_empty() {
@@ -443,7 +443,7 @@ impl<'a> BlobObject<'a> {
})
}
pub fn get_exif_orientation(&self, context: &Context) -> Result<i32, Error> {
pub fn get_exif_orientation(&self, context: &Context) -> Result<i32> {
let file = std::fs::File::open(self.to_abs_path())?;
let mut bufreader = std::io::BufReader::new(&file);
let exifreader = exif::Reader::new();
@@ -596,7 +596,7 @@ mod tests {
assert_eq!(fs::read(&foo_path).await.unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
println!("{}", name);
println!("{name}");
assert!(name.starts_with("foo"));
assert!(name.ends_with(".tar.gz"));
}

View File

@@ -1,7 +1,5 @@
//! # Chat module.
#![allow(missing_docs)]
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fmt;
@@ -19,10 +17,11 @@ use crate::color::str_to_color;
use crate::config::Config;
use crate::constants::{
Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_CHAT_ID_LAST_SPECIAL,
DC_CHAT_ID_TRASH, DC_GCM_ADDDAYMARKER, DC_GCM_INFO_ONLY, DC_RESEND_USER_AVATAR_DAYS,
DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS,
};
use crate::contact::{Contact, ContactId, Origin, VerifiedStatus};
use crate::context::Context;
use crate::debug_logging::maybe_set_logging_xdc;
use crate::ephemeral::Timer as EphemeralTimer;
use crate::events::EventType;
use crate::html::new_html_mimepart;
@@ -45,7 +44,9 @@ use crate::{location, sql};
/// An chat item, such as a message or a marker.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ChatItem {
/// Chat message stored in the database.
Message {
/// Database ID of the messsage.
msg_id: MsgId,
},
@@ -57,8 +58,10 @@ pub enum ChatItem {
},
}
/// Chat protection status.
#[derive(
Debug,
Default,
Display,
Clone,
Copy,
@@ -74,14 +77,14 @@ pub enum ChatItem {
)]
#[repr(u32)]
pub enum ProtectionStatus {
/// Chat is not protected.
#[default]
Unprotected = 0,
Protected = 1,
}
impl Default for ProtectionStatus {
fn default() -> Self {
ProtectionStatus::Unprotected
}
/// Chat is protected.
///
/// All members of the chat must be verified.
Protected = 1,
}
/// The reason why messages cannot be sent to the chat.
@@ -282,13 +285,17 @@ impl ChatId {
let chat_id = ChatId::new(u32::try_from(row_id)?);
info!(
context,
"Created group/mailinglist '{}' grpid={} as {}", grpname, grpid, chat_id
"Created group/mailinglist '{}' grpid={} as {}, blocked={}",
grpname,
grpid,
chat_id,
create_blocked,
);
Ok(chat_id)
}
pub async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
context
.sql
.execute(
@@ -673,6 +680,7 @@ impl ChatId {
Ok(())
}
/// Returns ID of the draft message, if there is one.
async fn get_draft_msg_id(self, context: &Context) -> Result<Option<MsgId>> {
let msg_id: Option<MsgId> = context
.sql
@@ -684,6 +692,7 @@ impl ChatId {
Ok(msg_id)
}
/// Returns draft message, if there is one.
pub async fn get_draft(self, context: &Context) -> Result<Option<Message>> {
if self.is_special() {
return Ok(None);
@@ -697,7 +706,7 @@ impl ChatId {
}
}
/// Delete draft message in specified chat, if there is one.
/// Deletes draft message, if there is one.
///
/// Returns `true`, if message was deleted, `false` otherwise.
async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
@@ -819,6 +828,7 @@ impl ChatId {
Ok(count)
}
/// Returns the number of fresh messages in the chat.
pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result<usize> {
// this function is typically used to show a badge counter beside _each_ chatlist item.
// to make this as fast as possible, esp. on older devices, we added an combined index over the rows used for querying.
@@ -885,7 +895,7 @@ impl ChatId {
Ok(promoted)
}
// Returns true if chat is a saved messages chat.
/// Returns true if chat is a saved messages chat.
pub async fn is_self_talk(self, context: &Context) -> Result<bool> {
Ok(self.get_param(context).await?.exists(Param::Selftalk))
}
@@ -901,11 +911,10 @@ impl ChatId {
{
let sql = &context.sql;
let query = format!(
"SELECT {} \
"SELECT {fields} \
FROM msgs WHERE chat_id=? AND state NOT IN (?, ?, ?, ?) AND NOT hidden \
ORDER BY timestamp DESC, id DESC \
LIMIT 1;",
fields
LIMIT 1;"
);
let row = sql
.query_row_optional(
@@ -986,9 +995,9 @@ impl ChatId {
})
.map(|peerstate| peerstate.prefer_encrypt)
{
Some(EncryptPreference::Mutual) => ret_mutual += &format!("{}\n", addr),
Some(EncryptPreference::NoPreference) => ret_nopreference += &format!("{}\n", addr),
Some(EncryptPreference::Reset) | None => ret_reset += &format!("{}\n", addr),
Some(EncryptPreference::Mutual) => ret_mutual += &format!("{addr}\n"),
Some(EncryptPreference::NoPreference) => ret_nopreference += &format!("{addr}\n"),
Some(EncryptPreference::Reset) | None => ret_reset += &format!("{addr}\n"),
};
}
@@ -1119,15 +1128,34 @@ impl rusqlite::types::FromSql for ChatId {
/// if you want an update, you have to recreate the object.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Chat {
/// Database ID.
pub id: ChatId,
/// Chat type, e.g. 1:1 chat, group chat, mailing list.
pub typ: Chattype,
/// Chat name.
pub name: String,
/// Whether the chat is archived or pinned.
pub visibility: ChatVisibility,
/// Group ID.
pub grpid: String,
/// Whether the chat is blocked, unblocked or a contact request.
pub(crate) blocked: Blocked,
/// Additional chat parameters stored in the database.
pub param: Params,
/// If location streaming is enabled in the chat.
is_sending_locations: bool,
/// Duration of the chat being muted.
pub mute_duration: MuteDuration,
/// If the chat is protected (verified).
protected: ProtectionStatus,
}
@@ -1159,7 +1187,7 @@ impl Chat {
},
)
.await
.context(format!("Failed loading chat {} from database", chat_id))?;
.context(format!("Failed loading chat {chat_id} from database"))?;
if chat.id.is_archived_link() {
chat.name = stock_str::archived_chats(context).await;
@@ -1192,6 +1220,7 @@ impl Chat {
Ok(chat)
}
/// Returns whether this is the `saved messages` chat
pub fn is_self_talk(&self) -> bool {
self.param.exists(Param::Selftalk)
}
@@ -1201,6 +1230,7 @@ impl Chat {
self.param.exists(Param::Devicetalk)
}
/// Returns true if chat is a mailing list.
pub fn is_mailing_list(&self) -> bool {
self.typ == Chattype::Mailinglist
}
@@ -1245,7 +1275,7 @@ impl Chat {
}
}
pub async fn update_param(&mut self, context: &Context) -> Result<()> {
pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
context
.sql
.execute(
@@ -1301,6 +1331,10 @@ impl Chat {
Ok(None)
}
/// Returns chat avatar color.
///
/// For 1:1 chats, the color is calculated from the contact's address.
/// For group chats the color is calculated from the chat name.
pub async fn get_color(&self, context: &Context) -> Result<u32> {
let mut color = 0;
@@ -1347,6 +1381,7 @@ impl Chat {
})
}
/// Returns chat visibilitiy, e.g. whether it is archived or pinned.
pub fn get_visibility(&self) -> ChatVisibility {
self.visibility
}
@@ -1359,12 +1394,12 @@ impl Chat {
self.blocked == Blocked::Request
}
/// Returns true if the chat is not promoted.
pub fn is_unpromoted(&self) -> bool {
self.param.get_bool(Param::Unpromoted).unwrap_or_default()
}
/// Returns whether the chat is promoted which means that a message has been
/// send to it and it not only exists on the users device.
/// Returns true if the chat is promoted.
pub fn is_promoted(&self) -> bool {
!self.is_unpromoted()
}
@@ -1379,6 +1414,7 @@ impl Chat {
self.is_sending_locations
}
/// Returns true if the chat is currently muted.
pub fn is_muted(&self) -> bool {
match self.mute_duration {
MuteDuration::NotMuted => false,
@@ -1479,11 +1515,11 @@ impl Chat {
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
// angle brackets are added by the mimefactory later
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
new_references = format!("{parent_references} {parent_rfc724_mid}");
} else if !parent_references.is_empty() {
new_references = parent_references.to_string();
} else if !parent_in_reply_to.is_empty() && !parent_rfc724_mid.is_empty() {
new_references = format!("{} {}", parent_in_reply_to, parent_rfc724_mid);
new_references = format!("{parent_in_reply_to} {parent_rfc724_mid}");
} else if !parent_in_reply_to.is_empty() {
new_references = parent_in_reply_to;
} else {
@@ -1500,7 +1536,6 @@ impl Chat {
}
// add independent location to database
if msg.param.exists(Param::SetLatitude) {
if let Ok(row_id) = context
.sql
@@ -1544,7 +1579,6 @@ impl Chat {
};
// add message to the database
if let Some(update_msg_id) = update_msg_id {
context
.sql
@@ -1626,16 +1660,24 @@ impl Chat {
)
.await?;
msg.id = MsgId::new(u32::try_from(raw_id)?);
maybe_set_logging_xdc(context, msg, self.id).await?;
}
context.interrupt_ephemeral_task().await;
Ok(msg.id)
}
}
/// Whether the chat is pinned or archived.
#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub enum ChatVisibility {
/// Chat is neither archived nor pinned.
Normal,
/// Chat is archived.
Archived,
/// Chat is pinned to the top of the chatlist.
Pinned,
}
@@ -1957,6 +1999,7 @@ impl ChatIdBlocked {
}
}
/// Prepares a message for sending.
pub async fn prepare_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
ensure!(
!chat_id.is_special(),
@@ -2032,6 +2075,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
Ok(())
}
/// Prepares a message to be sent out.
async fn prepare_msg_common(
context: &Context,
chat_id: ChatId,
@@ -2039,6 +2083,8 @@ async fn prepare_msg_common(
change_state_to: MessageState,
) -> Result<MsgId> {
let mut chat = Chat::load_from_db(context, chat_id).await?;
// Check if the chat can be sent to.
if let Some(reason) = chat.why_cant_send(context).await? {
bail!("cannot send to {}: {}", chat_id, reason);
}
@@ -2096,7 +2142,7 @@ pub async fn is_contact_in_chat(
Ok(exists)
}
/// Send a message defined by a dc_msg_t object to a chat.
/// Sends a message object to a chat.
///
/// Sends the event #DC_EVENT_MSGS_CHANGED on succcess.
/// However, this does not imply, the message really reached the recipient -
@@ -2312,6 +2358,9 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result<Option<
Ok(Some(row_id))
}
/// Sends a text message to the given chat.
///
/// Returns database ID of the sent message.
pub async fn send_text_msg(
context: &Context,
chat_id: ChatId,
@@ -2328,6 +2377,7 @@ pub async fn send_text_msg(
send_msg(context, chat_id, &mut msg).await
}
/// Sends invitation to a videochat.
pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId> {
ensure!(
!chat_id.is_special(),
@@ -2356,12 +2406,40 @@ pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Re
send_msg(context, chat_id, &mut msg).await
}
pub async fn get_chat_msgs(
/// Chat message list request options.
#[derive(Debug)]
pub struct MessageListOptions {
/// Return only info messages.
pub info_only: bool,
/// Add day markers before each date regarding the local timezone.
pub add_daymarker: bool,
}
/// Returns all messages belonging to the chat.
pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
get_chat_msgs_ex(
context,
chat_id,
MessageListOptions {
info_only: false,
add_daymarker: false,
},
)
.await
}
/// Returns messages belonging to the chat according to the given options.
pub async fn get_chat_msgs_ex(
context: &Context,
chat_id: ChatId,
flags: u32,
options: MessageListOptions,
) -> Result<Vec<ChatItem>> {
let process_row = if (flags & DC_GCM_INFO_ONLY) != 0 {
let MessageListOptions {
info_only,
add_daymarker,
} = options;
let process_row = if info_only {
|row: &rusqlite::Row| {
// is_info logic taken from Message.is_info()
let params = row.get::<_, String>("param")?;
@@ -2411,7 +2489,7 @@ pub async fn get_chat_msgs(
let cnv_to_local = gm2local_offset();
for (ts, curr_id) in sorted_rows {
if (flags & DC_GCM_ADDDAYMARKER) != 0 {
if add_daymarker {
let curr_local_timestamp = ts + cnv_to_local;
let curr_day = curr_local_timestamp / 86400;
if curr_day != last_day {
@@ -2426,7 +2504,7 @@ pub async fn get_chat_msgs(
Ok(ret)
};
let items = if (flags & DC_GCM_INFO_ONLY) != 0 {
let items = if info_only {
context
.sql
.query_map(
@@ -2615,6 +2693,12 @@ pub(crate) async fn mark_old_messages_as_noticed(
Ok(())
}
/// Returns all database message IDs of the given types.
///
/// If `chat_id` is None, return messages from any chat.
///
/// `Viewtype::Unknown` can be used for `msg_type2` and `msg_type3`
/// if less than 3 viewtypes are requested.
pub async fn get_chat_media(
context: &Context,
chat_id: Option<ChatId>,
@@ -2658,10 +2742,14 @@ pub async fn get_chat_media(
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(i32)]
pub enum Direction {
/// Search forward.
Forward = 1,
/// Search backward.
Backward = -1,
}
/// Searches next/previous message based on the given message and list of types.
pub async fn get_next_media(
context: &Context,
curr_msg_id: MsgId,
@@ -2776,7 +2864,7 @@ async fn find_unused_broadcast_list_name(context: &Context) -> Result<String> {
let base_name = stock_str::broadcast_list(context).await;
for attempt in 1..1000 {
let better_name = if attempt > 1 {
format!("{} {}", base_name, attempt)
format!("{base_name} {attempt}")
} else {
base_name.clone()
};
@@ -2857,7 +2945,6 @@ pub(crate) async fn remove_from_chat_contacts_table(
}
/// Adds a contact to the chat.
/// If the group is promoted, also sends out a system message to all group members
pub async fn add_contact_to_chat(
context: &Context,
chat_id: ChatId,
@@ -2879,7 +2966,7 @@ pub(crate) async fn add_contact_to_chat_ex(
chat_id.reset_gossiped_timestamp(context).await?;
// this also makes sure, no contacts are added to special or normal chats
/*this also makes sure, not contacts are added to special or normal chats*/
let mut chat = Chat::load_from_db(context, chat_id).await?;
ensure!(
chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast,
@@ -2901,7 +2988,7 @@ pub(crate) async fn add_contact_to_chat_ex(
context.emit_event(EventType::ErrorSelfNotInGroup(
"Cannot add contact to group; self not in group.".into(),
));
bail!("can not add contact because the account is not part of the group/broadcast");
bail!("can not add contact because our account is not part of it");
}
if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
@@ -2987,10 +3074,16 @@ pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId)
Ok(needs_attach)
}
/// Chat mute duration.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum MuteDuration {
/// Chat is not muted.
NotMuted,
/// Chat is muted until the user unmutes the chat.
Forever,
/// Chat is muted for a limited period of time.
Until(SystemTime),
}
@@ -3029,6 +3122,7 @@ impl rusqlite::types::FromSql for MuteDuration {
}
}
/// Mutes the chat for a given duration or unmutes it.
pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
ensure!(!chat_id.is_special(), "Invalid chat ID");
context
@@ -3038,11 +3132,12 @@ pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuratio
paramsv![duration, chat_id],
)
.await
.context(format!("Failed to set mute duration for {}", chat_id))?;
.context(format!("Failed to set mute duration for {chat_id}"))?;
context.emit_event(EventType::ChatModified(chat_id));
Ok(())
}
/// Removes contact from the chat.
pub async fn remove_contact_from_chat(
context: &Context,
chat_id: ChatId,
@@ -3200,7 +3295,7 @@ pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -
Ok(())
}
/// Set a new profile image for the chat.
/// Sets a new profile image for the chat.
///
/// The profile image can only be set when you are a member of the
/// chat. To remove the profile image pass an empty string for the
@@ -3246,6 +3341,7 @@ pub async fn set_chat_profile_image(
Ok(())
}
/// Forwards multiple messages to a chat.
pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
ensure!(!chat_id.is_special(), "can not forward to special chat");
@@ -3344,6 +3440,9 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
Ok(())
}
/// Resends given messages with the same Message-ID.
///
/// This is primarily intended to make existing webxdcs available to new chat members.
pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
let mut chat_id = None;
let mut msgs: Vec<Message> = Vec::new();
@@ -3542,6 +3641,7 @@ pub async fn add_device_msg(
add_device_msg_with_importance(context, label, msg, false).await
}
/// Returns true if device message with a given label was ever added to the device chat.
pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
ensure!(!label.is_empty(), "empty label");
let exists = context
@@ -3682,7 +3782,7 @@ mod tests {
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
use crate::contact::{Contact, ContactAddress};
use crate::receive_imf::receive_imf;
use crate::test_utils::{TestContext, TestContextManager};
use crate::test_utils::TestContext;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_chat_info() {
@@ -4077,45 +4177,6 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync_member_list_on_rejoin() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob_id = Contact::create(&alice, "", "bob@example.net").await?;
let claire_id = Contact::create(&alice, "", "claire@example.de").await?;
let alice_chat_id =
create_group_chat(&alice, ProtectionStatus::Unprotected, "foos").await?;
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
add_contact_to_chat(&alice, alice_chat_id, claire_id).await?;
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
let add = alice.pop_sent_msg().await;
let bob = tcm.bob().await;
bob.recv_msg(&add).await;
let bob_chat_id = bob.get_last_msg().await.chat_id;
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 3);
// remove bob from chat
remove_contact_from_chat(&alice, alice_chat_id, bob_id).await?;
let remove_bob = alice.pop_sent_msg().await;
bob.recv_msg(&remove_bob).await;
// remove any other member
remove_contact_from_chat(&alice, alice_chat_id, claire_id).await?;
alice.pop_sent_msg().await;
// readd bob
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
let add2 = alice.pop_sent_msg().await;
bob.recv_msg(&add2).await;
// number of members in chat should have updated
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 2);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_leave_group() -> Result<()> {
let alice = TestContext::new_alice().await;
@@ -4508,12 +4569,11 @@ mod tests {
format!(
"From: bob@example.net\n\
To: alice@example.org\n\
Message-ID: <{}@example.org>\n\
Message-ID: <{num}@example.org>\n\
Chat-Version: 1.0\n\
Date: Sun, 22 Mar 2022 19:37:57 +0000\n\
\n\
hello\n",
num
hello\n"
)
.as_bytes(),
false,
@@ -4589,14 +4649,13 @@ mod tests {
receive_imf(
t,
format!(
"From: {}@example.net\n\
"From: {name}@example.net\n\
To: alice@example.org\n\
Message-ID: <{}@example.org>\n\
Message-ID: <{num}@example.org>\n\
Chat-Version: 1.0\n\
Date: Sun, 22 Mar 2022 19:37:57 +0000\n\
\n\
hello\n",
name, num
hello\n"
)
.as_bytes(),
false,
@@ -4803,7 +4862,7 @@ mod tests {
let chat_id = ChatId::create_for_contact(&context.ctx, contact1)
.await
.unwrap();
assert!(!chat_id.is_special(), "chat_id too small {}", chat_id);
assert!(!chat_id.is_special(), "chat_id too small {chat_id}");
let chat = Chat::load_from_db(&context.ctx, chat_id).await.unwrap();
let chat2_id = ChatId::create_for_contact(&context.ctx, contact1)
@@ -4955,7 +5014,7 @@ mod tests {
assert!(chat.is_protected());
assert!(chat.is_unpromoted());
let msgs = get_chat_msgs(&t, chat_id, 0).await?;
let msgs = get_chat_msgs(&t, chat_id).await?;
assert_eq!(msgs.len(), 1);
let msg = t.get_last_msg_in(chat_id).await;
@@ -4984,7 +5043,7 @@ mod tests {
assert!(!chat.is_protected());
assert!(!chat.is_unpromoted());
let msgs = get_chat_msgs(&t, chat_id, 0).await?;
let msgs = get_chat_msgs(&t, chat_id).await?;
assert_eq!(msgs.len(), 3);
// enable protection on promoted chat, the info-message is sent via send_msg() this time
@@ -5071,9 +5130,6 @@ mod tests {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
alice.set_config(Config::ShowEmails, Some("2")).await?;
bob.set_config(Config::ShowEmails, Some("2")).await?;
let alice_bob_contact = alice.add_or_lookup_contact(&bob).await;
let contact_id = alice_bob_contact.id;
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
@@ -5082,7 +5138,7 @@ mod tests {
add_contact_to_chat(&alice, alice_chat_id, contact_id).await?;
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 2);
send_text_msg(&alice, alice_chat_id, "hi!".to_string()).await?;
assert_eq!(get_chat_msgs(&alice, alice_chat_id, 0).await?.len(), 1);
assert_eq!(get_chat_msgs(&alice, alice_chat_id).await?.len(), 1);
// Alice has an SMTP-server replacing the `Message-ID:`-header (as done eg. by outlook.com).
let sent_msg = alice.pop_sent_msg().await;
@@ -5115,7 +5171,7 @@ mod tests {
let msg = alice.get_last_msg().await;
assert_eq!(msg.chat_id, alice_chat_id);
assert_eq!(msg.text, Some("ho!".to_string()));
assert_eq!(get_chat_msgs(&alice, alice_chat_id, 0).await?.len(), 2);
assert_eq!(get_chat_msgs(&alice, alice_chat_id).await?.len(), 2);
Ok(())
}
@@ -5143,7 +5199,7 @@ mod tests {
assert_eq!(chat.id.get_fresh_msg_cnt(&t).await?, 1);
assert_eq!(t.get_fresh_msgs().await?.len(), 1);
let msgs = get_chat_msgs(&t, chat.id, 0).await?;
let msgs = get_chat_msgs(&t, chat.id).await?;
assert_eq!(msgs.len(), 1);
let msg_id = match msgs.first().unwrap() {
ChatItem::Message { msg_id } => *msg_id,
@@ -5193,7 +5249,7 @@ mod tests {
.is_contact_request());
assert_eq!(chat_id.get_msg_cnt(&t).await?, 1);
assert_eq!(chat_id.get_fresh_msg_cnt(&t).await?, 1);
let msgs = get_chat_msgs(&t, chat_id, 0).await?;
let msgs = get_chat_msgs(&t, chat_id).await?;
assert_eq!(msgs.len(), 1);
let msg_id = match msgs.first().unwrap() {
ChatItem::Message { msg_id } => *msg_id,
@@ -5258,12 +5314,6 @@ mod tests {
async fn test_classic_email_chat() -> Result<()> {
let alice = TestContext::new_alice().await;
// Alice enables receiving classic emails.
alice
.set_config(Config::ShowEmails, Some("2"))
.await
.unwrap();
// Alice receives a classic (non-chat) message from Bob.
receive_imf(
&alice,
@@ -5281,7 +5331,7 @@ mod tests {
let chat_id = msg.chat_id;
assert_eq!(chat_id.get_fresh_msg_cnt(&alice).await?, 1);
let msgs = get_chat_msgs(&alice, chat_id, 0).await?;
let msgs = get_chat_msgs(&alice, chat_id).await?;
assert_eq!(msgs.len(), 1);
// Alice disables receiving classic emails.
@@ -5293,7 +5343,7 @@ mod tests {
// Already received classic email should still be in the chat.
assert_eq!(chat_id.get_fresh_msg_cnt(&alice).await?, 1);
let msgs = get_chat_msgs(&alice, chat_id, 0).await?;
let msgs = get_chat_msgs(&alice, chat_id).await?;
assert_eq!(msgs.len(), 1);
Ok(())
@@ -5440,7 +5490,7 @@ mod tests {
assert!(msg1.get_text().unwrap().contains("bob@example.net"));
let chat_id2 = ChatId::create_for_contact(&t, bob_id).await?;
assert_eq!(get_chat_msgs(&t, chat_id2, 0).await?.len(), 0);
assert_eq!(get_chat_msgs(&t, chat_id2).await?.len(), 0);
forward_msgs(&t, &[msg1.id], chat_id2).await?;
let msg2 = t.get_last_msg_in(chat_id2).await;
assert!(!msg2.is_info()); // forwarded info-messages lose their info-state
@@ -5609,15 +5659,15 @@ mod tests {
let msg = bob.recv_msg(&sent1).await;
assert_eq!(msg.get_text().unwrap(), "alice->bob");
assert_eq!(get_chat_contacts(&bob, msg.chat_id).await?.len(), 2);
assert_eq!(get_chat_msgs(&bob, msg.chat_id, 0).await?.len(), 1);
assert_eq!(get_chat_msgs(&bob, msg.chat_id).await?.len(), 1);
bob.recv_msg(&sent2).await;
assert_eq!(get_chat_contacts(&bob, msg.chat_id).await?.len(), 3);
assert_eq!(get_chat_msgs(&bob, msg.chat_id, 0).await?.len(), 2);
assert_eq!(get_chat_msgs(&bob, msg.chat_id).await?.len(), 2);
let received = bob.recv_msg_opt(&sent3).await;
// No message should actually be added since we already know this message:
assert!(received.is_none());
assert_eq!(get_chat_contacts(&bob, msg.chat_id).await?.len(), 3);
assert_eq!(get_chat_msgs(&bob, msg.chat_id, 0).await?.len(), 2);
assert_eq!(get_chat_msgs(&bob, msg.chat_id).await?.len(), 2);
// Claire does not receive the first message, however, due to resending, she has a similar view as Alice and Bob
let claire = TestContext::new().await;
@@ -5626,7 +5676,7 @@ mod tests {
let msg = claire.recv_msg(&sent3).await;
assert_eq!(msg.get_text().unwrap(), "alice->bob");
assert_eq!(get_chat_contacts(&claire, msg.chat_id).await?.len(), 3);
assert_eq!(get_chat_msgs(&claire, msg.chat_id, 0).await?.len(), 2);
assert_eq!(get_chat_msgs(&claire, msg.chat_id).await?.len(), 2);
let msg_from = Contact::get_by_id(&claire, msg.get_from_id()).await?;
assert_eq!(msg_from.get_addr(), "alice@example.org");

View File

@@ -1,7 +1,5 @@
//! # Chat list module.
#![allow(missing_docs)]
use anyhow::{ensure, Context as _, Result};
use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility};
@@ -181,7 +179,7 @@ impl Chatlist {
warn!(context, "cannot update special chat names: {:?}", err)
}
let str_like_cmd = format!("%{}%", query);
let str_like_cmd = format!("%{query}%");
context
.sql
.query_map(
@@ -341,10 +339,12 @@ impl Chatlist {
}
}
/// Returns chatlist item position for the given chat ID.
pub fn get_index_for_id(&self, id: ChatId) -> Option<usize> {
self.ids.iter().position(|(chat_id, _)| chat_id == &id)
}
/// An iterator visiting all chatlist items.
pub fn iter(&self) -> impl Iterator<Item = &(ChatId, Option<MsgId>)> {
self.ids.iter()
}

View File

@@ -36,7 +36,7 @@ pub(crate) fn str_to_color(s: &str) -> u32 {
}
pub fn color_int_to_hex_string(color: u32) -> String {
format!("{:#08x}", color).replace("0x", "#")
format!("{color:#08x}").replace("0x", "#")
}
#[cfg(test)]

View File

@@ -1,9 +1,7 @@
//! # Key-value configuration management.
#![allow(missing_docs)]
use anyhow::{ensure, Context as _, Result};
use strum::{EnumProperty as EnumPropertyTrait, IntoEnumIterator};
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use crate::blob::BlobObject;
@@ -32,43 +30,97 @@ use crate::tools::{get_abs_path, improve_single_line_input, EmailAddress};
)]
#[strum(serialize_all = "snake_case")]
pub enum Config {
/// Email address, used in the `From:` field.
Addr,
/// IMAP server hostname.
MailServer,
/// IMAP server username.
MailUser,
/// IMAP server password.
MailPw,
/// IMAP server port.
MailPort,
/// IMAP server security (e.g. TLS, STARTTLS).
MailSecurity,
/// How to check IMAP server TLS certificates.
ImapCertificateChecks,
/// SMTP server hostname.
SendServer,
/// SMTP server username.
SendUser,
/// SMTP server password.
SendPw,
/// SMTP server port.
SendPort,
/// SMTP server security (e.g. TLS, STARTTLS).
SendSecurity,
/// How to check SMTP server TLS certificates.
SmtpCertificateChecks,
/// Whether to use OAuth 2.
///
/// Historically contained other bitflags, which are now deprecated.
/// Should not be extended in the future, create new config keys instead.
ServerFlags,
/// True if SOCKS5 is enabled.
///
/// Can be used to disable SOCKS5 without erasing SOCKS5 configuration.
Socks5Enabled,
/// SOCKS5 proxy server hostname or address.
Socks5Host,
/// SOCKS5 proxy server port.
Socks5Port,
/// SOCKS5 proxy server username.
Socks5User,
/// SOCKS5 proxy server password.
Socks5Password,
/// Own name to use in the `From:` field when sending messages.
Displayname,
/// Own status to display, sent in message footer.
Selfstatus,
/// Own avatar filename.
Selfavatar,
/// Send BCC copy to self.
///
/// Should be enabled for multidevice setups.
#[strum(props(default = "1"))]
BccSelf,
/// True if encryption is preferred according to Autocrypt standard.
#[strum(props(default = "1"))]
E2eeEnabled,
/// True if Message Delivery Notifications (read receipts) should
/// be sent and requested.
#[strum(props(default = "1"))]
MdnsEnabled,
/// True if "Sent" folder should be watched for changes.
#[strum(props(default = "0"))]
SentboxWatch,
/// True if chat messages should be moved to a separate folder.
#[strum(props(default = "1"))]
MvboxMove,
@@ -79,9 +131,11 @@ pub enum Config {
#[strum(props(default = "0"))]
OnlyFetchMvbox,
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
/// Whether to show classic emails or only chat messages.
#[strum(props(default = "2"))] // also change ShowEmails.default() on changes
ShowEmails,
/// Quality of the media files to send.
#[strum(props(default = "0"))] // also change MediaQuality.default() on changes
MediaQuality,
@@ -96,6 +150,7 @@ pub enum Config {
#[strum(props(default = "1"))]
FetchedExistingMsgs,
/// Type of the OpenPGP key to generate.
#[strum(props(default = "0"))]
KeyGenType,
@@ -118,43 +173,86 @@ pub enum Config {
#[strum(props(default = "0"))]
DeleteDeviceAfter,
/// Save raw MIME messages with headers in the database if true.
SaveMimeHeaders,
/// The primary email address. Also see `SecondaryAddrs`.
ConfiguredAddr,
/// Configured IMAP server hostname.
ConfiguredMailServer,
/// Configured IMAP server username.
ConfiguredMailUser,
/// Configured IMAP server password.
ConfiguredMailPw,
/// Configured IMAP server port.
ConfiguredMailPort,
/// Configured IMAP server security (e.g. TLS, STARTTLS).
ConfiguredMailSecurity,
/// How to check IMAP server TLS certificates.
ConfiguredImapCertificateChecks,
/// Configured SMTP server hostname.
ConfiguredSendServer,
/// Configured SMTP server username.
ConfiguredSendUser,
/// Configured SMTP server password.
ConfiguredSendPw,
/// Configured SMTP server port.
ConfiguredSendPort,
/// How to check SMTP server TLS certificates.
ConfiguredSmtpCertificateChecks,
/// Whether OAuth 2 is used with configured provider.
ConfiguredServerFlags,
/// Configured SMTP server security (e.g. TLS, STARTTLS).
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
/// Configured folder for incoming messages.
ConfiguredInboxFolder,
/// Configured folder for chat messages.
ConfiguredMvboxFolder,
/// Configured "Sent" folder.
ConfiguredSentboxFolder,
/// Unix timestamp of the last successful configuration.
ConfiguredTimestamp,
/// ID of the configured provider from the provider database.
ConfiguredProvider,
/// True if account is configured.
Configured,
/// All secondary self addresses separated by spaces
/// (`addr1@example.org addr2@exapmle.org addr3@example.org`)
SecondaryAddrs,
/// Read-only core version string.
#[strum(serialize = "sys.version")]
SysVersion,
/// Maximal recommended attachment size in bytes.
#[strum(serialize = "sys.msgsize_max_recommended")]
SysMsgsizeMaxRecommended,
/// Space separated list of all config keys available.
#[strum(serialize = "sys.config_keys")]
SysConfigKeys,
/// True if it is a bot account.
Bot,
/// Whether we send a warning if the password is wrong (set to false when we send a warning
@@ -192,9 +290,15 @@ pub enum Config {
///
/// See `crate::authres::update_authservid_candidates`.
AuthservIdCandidates,
/// Let the core save all events to the database.
/// This value is used internally to remember the MsgId of the logging xdc
#[strum(props(default = "0"))]
DebugLogging,
}
impl Context {
/// Returns true if configuration value is set for the given key.
pub async fn config_exists(&self, key: Config) -> Result<bool> {
Ok(self.sql.get_raw_config(key.as_ref()).await?.is_some())
}
@@ -207,7 +311,7 @@ impl Context {
rel_path.map(|p| get_abs_path(self, p).to_string_lossy().into_owned())
}
Config::SysVersion => Some((*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)),
Config::SysMsgsizeMaxRecommended => Some(format!("{RECOMMENDED_FILE_SIZE}")),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_raw_config(key.as_ref()).await?,
};
@@ -223,28 +327,33 @@ impl Context {
}
}
/// Returns 32-bit signed integer configuration value for the given key.
pub async fn get_config_int(&self, key: Config) -> Result<i32> {
self.get_config(key)
.await
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()).unwrap_or_default())
}
/// Returns 64-bit signed integer configuration value for the given key.
pub async fn get_config_i64(&self, key: Config) -> Result<i64> {
self.get_config(key)
.await
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()).unwrap_or_default())
}
/// Returns 64-bit unsigned integer configuration value for the given key.
pub async fn get_config_u64(&self, key: Config) -> Result<u64> {
self.get_config(key)
.await
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()).unwrap_or_default())
}
/// Returns boolean configuration value for the given key.
pub async fn get_config_bool(&self, key: Config) -> Result<bool> {
Ok(self.get_config_int(key).await? != 0)
}
/// Returns true if movebox ("DeltaChat" folder) should be watched.
pub(crate) async fn should_watch_mvbox(&self) -> Result<bool> {
Ok(self.get_config_bool(Config::MvboxMove).await?
|| self.get_config_bool(Config::OnlyFetchMvbox).await?)
@@ -325,6 +434,7 @@ impl Context {
Ok(())
}
/// Set the given config to a boolean value.
pub async fn set_config_bool(&self, key: Config, value: bool) -> Result<()> {
self.set_config(key, if value { Some("1") } else { Some("0") })
.await?;
@@ -435,7 +545,7 @@ fn get_config_keys_string() -> String {
acc
});
format!(" {} ", keys)
format!(" {keys} ")
}
#[cfg(test)]

View File

@@ -87,7 +87,7 @@ impl Context {
self,
// We are using Anyhow's .context() and to show the
// inner error, too, we need the {:#}:
&format!("{:#}", err),
&format!("{err:#}"),
)
.await
)
@@ -492,8 +492,7 @@ async fn get_autoconfig(
if let Ok(res) = moz_autoconfigure(
ctx,
&format!(
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
"https://autoconfig.{param_domain}/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
),
param,
)
@@ -586,7 +585,7 @@ async fn try_imap_one_param(
info!(context, "failure: {:#}", err);
return Err(ConfigurationError {
config: inf,
msg: format!("{:#}", err),
msg: format!("{err:#}"),
});
}
Ok(imap) => imap,
@@ -597,7 +596,7 @@ async fn try_imap_one_param(
info!(context, "failure: {:#}", err);
Err(ConfigurationError {
config: inf,
msg: format!("{:#}", err),
msg: format!("{err:#}"),
})
}
Ok(()) => {
@@ -638,7 +637,7 @@ async fn try_smtp_one_param(
info!(context, "failure: {}", err);
Err(ConfigurationError {
config: inf,
msg: format!("{:#}", err),
msg: format!("{err:#}"),
})
} else {
info!(context, "success: {}", inf);

View File

@@ -1,6 +1,7 @@
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 {
@@ -16,7 +17,8 @@ pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
}
pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result<String> {
let client = crate::http::get_client()?;
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

View File

@@ -99,15 +99,6 @@ impl ServerParams {
// Try common secure combinations.
vec![
// Try STARTTLS
Self {
socket: Socket::Starttls,
port: match self.protocol {
Protocol::Imap => 143,
Protocol::Smtp => 587,
},
..self.clone()
},
// Try TLS
Self {
socket: Socket::Ssl,
@@ -115,6 +106,15 @@ impl ServerParams {
Protocol::Imap => 993,
Protocol::Smtp => 465,
},
..self.clone()
},
// Try STARTTLS
Self {
socket: Socket::Starttls,
port: match self.protocol {
Protocol::Imap => 143,
Protocol::Smtp => 587,
},
..self
},
]
@@ -343,5 +343,41 @@ mod tests {
}
],
);
// Test that TLS is preferred to STARTTLS
// when the port and security are not set.
let v = expand_param_vector(
vec![ServerParams {
protocol: Protocol::Smtp,
hostname: "example.net".to_string(),
port: 0,
socket: Socket::Automatic,
username: "foobar".to_string(),
strict_tls: Some(true),
}],
"foobar@example.net",
"example.net",
);
assert_eq!(
v,
vec![
ServerParams {
protocol: Protocol::Smtp,
hostname: "example.net".to_string(),
port: 465,
socket: Socket::Ssl,
username: "foobar".to_string(),
strict_tls: Some(true)
},
ServerParams {
protocol: Protocol::Smtp,
hostname: "example.net".to_string(),
port: 587,
socket: Socket::Starttls,
username: "foobar".to_string(),
strict_tls: Some(true)
},
],
);
}
}

View File

@@ -12,6 +12,7 @@ pub static DC_VERSION_STR: Lazy<String> = Lazy::new(|| env!("CARGO_PKG_VERSION")
#[derive(
Debug,
Default,
Display,
Clone,
Copy,
@@ -26,79 +27,60 @@ pub static DC_VERSION_STR: Lazy<String> = Lazy::new(|| env!("CARGO_PKG_VERSION")
)]
#[repr(i8)]
pub enum Blocked {
#[default]
Not = 0,
Yes = 1,
Request = 2,
}
impl Default for Blocked {
fn default() -> Self {
Blocked::Not
}
}
#[derive(
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[repr(u8)]
pub enum ShowEmails {
Off = 0,
AcceptedContacts = 1,
#[default] // also change Config.ShowEmails props(default) on changes
All = 2,
}
impl Default for ShowEmails {
fn default() -> Self {
ShowEmails::Off // also change Config.ShowEmails props(default) on changes
}
}
#[derive(
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[repr(u8)]
pub enum MediaQuality {
#[default] // also change Config.MediaQuality props(default) on changes
Balanced = 0,
Worse = 1,
}
impl Default for MediaQuality {
fn default() -> Self {
MediaQuality::Balanced // also change Config.MediaQuality props(default) on changes
}
}
/// Type of the key to generate.
#[derive(
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[repr(u8)]
pub enum KeyGenType {
#[default]
Default = 0,
Rsa2048 = 1,
Ed25519 = 2,
}
impl Default for KeyGenType {
fn default() -> Self {
KeyGenType::Default
}
}
/// Video chat URL type.
#[derive(
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[repr(i8)]
pub enum VideochatType {
/// Unknown type.
#[default]
Unknown = 0,
BasicWebrtc = 1,
Jitsi = 2,
}
impl Default for VideochatType {
fn default() -> Self {
VideochatType::Unknown
}
/// [basicWebRTC](https://github.com/cracker0dks/basicwebrtc) instance.
BasicWebrtc = 1,
/// [Jitsi Meet](https://jitsi.org/jitsi-meet/) instance.
Jitsi = 2,
}
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
@@ -112,9 +94,6 @@ pub const DC_GCL_NO_SPECIALS: usize = 0x02;
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
pub const DC_GCL_FOR_FORWARDING: usize = 0x08;
pub const DC_GCM_ADDDAYMARKER: u32 = 0x01;
pub const DC_GCM_INFO_ONLY: u32 = 0x02;
pub const DC_GCL_VERIFIED_ONLY: u32 = 0x01;
pub const DC_GCL_ADD_SELF: u32 = 0x02;
@@ -136,8 +115,10 @@ pub const DC_CHAT_ID_ALLDONE_HINT: ChatId = ChatId::new(7);
/// larger chat IDs are "real" chats, their messages are "real" messages.
pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9);
/// Chat type.
#[derive(
Debug,
Default,
Display,
Clone,
Copy,
@@ -153,17 +134,21 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9);
)]
#[repr(u32)]
pub enum Chattype {
/// Undefined chat type.
#[default]
Undefined = 0,
Single = 100,
Group = 120,
Mailinglist = 140,
Broadcast = 160,
}
impl Default for Chattype {
fn default() -> Self {
Chattype::Undefined
}
/// 1:1 chat.
Single = 100,
/// Group chat.
Group = 120,
/// Mailing list.
Mailinglist = 140,
/// Broadcast list.
Broadcast = 160,
}
pub const DC_MSG_ID_DAYMARKER: u32 = 9;
@@ -247,7 +232,7 @@ mod tests {
#[test]
fn test_showemails_values() {
// values may be written to disk and must not change
assert_eq!(ShowEmails::Off, ShowEmails::default());
assert_eq!(ShowEmails::All, ShowEmails::default());
assert_eq!(ShowEmails::Off, ShowEmails::from_i32(0).unwrap());
assert_eq!(
ShowEmails::AcceptedContacts,

View File

@@ -223,12 +223,24 @@ pub struct Contact {
/// Possible origins of a contact.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
Debug,
Default,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
)]
#[repr(u32)]
pub enum Origin {
/// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
/// about origin of the contact.
#[default]
Unknown = 0,
/// The contact is a mailing list address, needed to unblock mailing lists
@@ -287,12 +299,6 @@ pub enum Origin {
ManuallyCreated = 0x0400_0000,
}
impl Default for Origin {
fn default() -> Self {
Origin::Unknown
}
}
impl Origin {
/// Contacts that are known, i. e. they came in via accepted contacts or
/// themselves an accepted contact. Known contacts are shown in the
@@ -961,7 +967,7 @@ impl Contact {
};
let finger_prints = stock_str::finger_prints(context).await;
ret += &format!("{}.\n{}:", stock_message, finger_prints);
ret += &format!("{stock_message}.\n{finger_prints}:");
let fingerprint_self = SignedPublicKey::load_self(context)
.await?
@@ -1499,7 +1505,7 @@ fn cat_fingerprint(
&& !fingerprint_unverified.is_empty()
&& fingerprint_verified != fingerprint_unverified
{
*ret += &format!("\n\n{} (alternative):\n{}", addr, fingerprint_unverified);
*ret += &format!("\n\n{addr} (alternative):\n{fingerprint_unverified}");
}
}
@@ -1515,7 +1521,6 @@ fn split_address_book(book: &str) -> Vec<(&str, &str)> {
book.lines()
.collect::<Vec<&str>>()
.chunks(2)
.into_iter()
.filter_map(|chunk| {
let name = chunk.first()?;
let addr = chunk.get(1)?;

View File

@@ -1,7 +1,5 @@
//! Context module.
#![allow(missing_docs)]
use std::collections::{BTreeMap, HashMap};
use std::ffi::OsString;
use std::ops::Deref;
@@ -9,20 +7,22 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use anyhow::{ensure, Result};
use anyhow::{bail, ensure, Result};
use async_channel::{self as channel, Receiver, Sender};
use ratelimit::Ratelimit;
use tokio::sync::{Mutex, RwLock};
use tokio::task;
use crate::chat::{get_chat_cnt, ChatId};
use crate::config::Config;
use crate::constants::DC_VERSION_STR;
use crate::contact::Contact;
use crate::debug_logging::DebugEventLogData;
use crate::events::{Event, EventEmitter, EventType, Events};
use crate::key::{DcKey, SignedPublicKey};
use crate::login_param::LoginParam;
use crate::message::{self, MessageState, MsgId};
use crate::quota::QuotaInfo;
use crate::ratelimit::Ratelimit;
use crate::scheduler::Scheduler;
use crate::sql::Sql;
use crate::stock_str::StockStrings;
@@ -147,26 +147,17 @@ impl ContextBuilder {
}
/// Opens the [`Context`].
pub async fn open(self) -> Result<Context, ContextError> {
pub async fn open(self) -> Result<Context> {
let context =
Context::new_closed(&self.dbfile, self.id, self.events, self.stock_strings).await?;
let password = self.password.unwrap_or_default();
match context.open(password).await? {
true => Ok(context),
false => Err(ContextError::DatabaseEncrypted),
false => bail!("database could not be decrypted, incorrect or missing password"),
}
}
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum ContextError {
#[error("database could not be decrypted, incorrect or missing password")]
DatabaseEncrypted,
#[error("failed to open context")]
Other(#[from] anyhow::Error),
}
/// The context for a single DeltaChat account.
///
/// This contains all the state for a single DeltaChat account, including background tasks
@@ -191,6 +182,7 @@ impl Deref for Context {
}
}
/// Actual context, expensive to clone.
#[derive(Debug)]
pub struct InnerContext {
/// Blob directory path
@@ -233,6 +225,20 @@ pub struct InnerContext {
/// If the ui wants to display an error after a failure,
/// `last_error` should be used to avoid races with the event thread.
pub(crate) last_error: std::sync::RwLock<String>,
/// If debug logging is enabled, this contains all neccesary information
pub(crate) debug_logging: RwLock<Option<DebugLogging>>,
}
#[derive(Debug)]
pub(crate) struct DebugLogging {
/// The message containing the logging xdc
pub(crate) msg_id: MsgId,
/// Handle to the background task responisble for sending
pub(crate) loop_handle: task::JoinHandle<()>,
/// Channel that log events should be send to
/// A background loop will receive and handle them
pub(crate) sender: Sender<DebugEventLogData>,
}
/// The state of ongoing process.
@@ -363,6 +369,7 @@ impl Context {
creation_time: std::time::SystemTime::now(),
last_full_folder_scan: Mutex::new(None),
last_error: std::sync::RwLock::new("".to_string()),
debug_logging: RwLock::new(None),
};
let ctx = Context {
@@ -397,7 +404,9 @@ impl Context {
// to terminate on receiving the next event and then call stop_io()
// which will emit the below event(s)
info!(self, "stopping IO");
if let Some(debug_logging) = self.debug_logging.read().await.as_ref() {
debug_logging.loop_handle.abort();
}
if let Some(scheduler) = self.inner.scheduler.write().await.take() {
scheduler.stop(self).await;
}
@@ -434,12 +443,41 @@ impl Context {
/// Emits a single event.
pub fn emit_event(&self, event: EventType) {
if self
.debug_logging
.try_read()
.ok()
.map(|inner| inner.is_some())
== Some(true)
{
self.send_log_event(event.clone()).ok();
};
self.events.emit(Event {
id: self.id,
typ: event,
});
}
pub(crate) fn send_log_event(&self, event: EventType) -> anyhow::Result<()> {
if let Ok(lock) = self.debug_logging.try_read() {
if let Some(DebugLogging {
msg_id: xdc_id,
sender,
..
}) = &*lock
{
let event_data = DebugEventLogData {
time: time(),
msg_id: *xdc_id,
event,
};
sender.try_send(event_data).ok();
}
}
Ok(())
}
/// Emits a generic MsgsChanged event (without chat or message id)
pub fn emit_msgs_changed_without_ids(&self) {
self.emit_event(EventType::MsgsChanged {
@@ -522,6 +560,7 @@ impl Context {
* UI chat/message related API
******************************************************************************/
/// Returns information about the context as key-value pairs.
pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
let unset = "0";
let l = LoginParam::load_candidate_params_unchecked(self).await?;
@@ -560,7 +599,7 @@ impl Context {
.await?;
let fingerprint_str = match SignedPublicKey::load_self(self).await {
Ok(key) => key.fingerprint().hex(),
Err(err) => format!("<key failure: {}>", err),
Err(err) => format!("<key failure: {err}>"),
};
let sentbox_watch = self.get_config_int(Config::SentboxWatch).await?;
@@ -617,7 +656,7 @@ impl Context {
res.insert("used_account_settings", l2.to_string());
if let Some(server_id) = &*self.server_id.read().await {
res.insert("imap_server_id", format!("{:?}", server_id));
res.insert("imap_server_id", format!("{server_id:?}"));
}
res.insert("secondary_addrs", secondary_addrs);
@@ -708,6 +747,11 @@ impl Context {
.unwrap_or_default(),
);
res.insert(
"debug_logging",
self.get_config_int(Config::DebugLogging).await?.to_string(),
);
let elapsed = self.creation_time.elapsed();
res.insert("uptime", duration_to_str(elapsed.unwrap_or_default()));
@@ -762,7 +806,7 @@ impl Context {
if real_query.is_empty() {
return Ok(Vec::new());
}
let str_like_in_text = format!("%{}%", real_query);
let str_like_in_text = format!("%{real_query}%");
let do_query = |query, params| {
self.sql.query_map(
@@ -825,16 +869,19 @@ impl Context {
Ok(list)
}
/// Returns true if given folder name is the name of the inbox.
pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
Ok(inbox.as_deref() == Some(folder_name))
}
/// Returns true if given folder name is the name of the "sent" folder.
pub async fn is_sentbox(&self, folder_name: &str) -> Result<bool> {
let sentbox = self.get_config(Config::ConfiguredSentboxFolder).await?;
Ok(sentbox.as_deref() == Some(folder_name))
}
/// Returns true if given folder name is the name of the "Delta Chat" folder.
pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
Ok(mvbox.as_deref() == Some(folder_name))
@@ -855,6 +902,7 @@ impl Context {
}
}
/// Returns core version as a string.
pub fn get_version_str() -> &'static str {
&DC_VERSION_STR
}
@@ -914,7 +962,7 @@ mod tests {
contact.get_addr(),
create_outgoing_rfc724_mid(None, contact.get_addr())
);
println!("{}", msg);
println!("{msg}");
receive_imf(t, msg.as_bytes(), false).await.unwrap();
}
@@ -928,20 +976,20 @@ mod tests {
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 0);
receive_msg(&t, &bob).await;
assert_eq!(get_chat_msgs(&t, bob.id, 0).await.unwrap().len(), 1);
assert_eq!(get_chat_msgs(&t, bob.id).await.unwrap().len(), 1);
assert_eq!(bob.id.get_fresh_msg_cnt(&t).await.unwrap(), 1);
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 1);
receive_msg(&t, &claire).await;
receive_msg(&t, &claire).await;
assert_eq!(get_chat_msgs(&t, claire.id, 0).await.unwrap().len(), 2);
assert_eq!(get_chat_msgs(&t, claire.id).await.unwrap().len(), 2);
assert_eq!(claire.id.get_fresh_msg_cnt(&t).await.unwrap(), 2);
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 3);
receive_msg(&t, &dave).await;
receive_msg(&t, &dave).await;
receive_msg(&t, &dave).await;
assert_eq!(get_chat_msgs(&t, dave.id, 0).await.unwrap().len(), 3);
assert_eq!(get_chat_msgs(&t, dave.id).await.unwrap().len(), 3);
assert_eq!(dave.id.get_fresh_msg_cnt(&t).await.unwrap(), 3);
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 6);
@@ -956,7 +1004,7 @@ mod tests {
receive_msg(&t, &bob).await;
receive_msg(&t, &claire).await;
receive_msg(&t, &dave).await;
assert_eq!(get_chat_msgs(&t, claire.id, 0).await.unwrap().len(), 3);
assert_eq!(get_chat_msgs(&t, claire.id).await.unwrap().len(), 3);
assert_eq!(claire.id.get_fresh_msg_cnt(&t).await.unwrap(), 3);
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 6); // muted claire is not counted
@@ -973,7 +1021,7 @@ mod tests {
let t = TestContext::new_alice().await;
let bob = t.create_chat_with_contact("", "bob@g.it").await;
receive_msg(&t, &bob).await;
assert_eq!(get_chat_msgs(&t, bob.id, 0).await.unwrap().len(), 1);
assert_eq!(get_chat_msgs(&t, bob.id).await.unwrap().len(), 1);
// chat is unmuted by default, here and in the following assert(),
// we check mainly that the SQL-statements in is_muted() and get_fresh_msgs()
@@ -1141,8 +1189,7 @@ mod tests {
{
assert!(
info.contains_key(&*key),
"'{}' missing in get_info() output",
key
"'{key}' missing in get_info() output"
);
}
}

147
src/debug_logging.rs Normal file
View File

@@ -0,0 +1,147 @@
//! Forward log messages to logging webxdc
use crate::{
chat::ChatId,
config::Config,
context::{Context, DebugLogging},
message::{Message, MsgId, Viewtype},
param::Param,
webxdc::StatusUpdateItem,
Event, EventType,
};
use async_channel::{self as channel, Receiver};
use serde_json::json;
use std::path::PathBuf;
use tokio::task;
/// Store all information needed to log an event to a webxdc.
pub struct DebugEventLogData {
pub time: i64,
pub msg_id: MsgId,
pub event: EventType,
}
/// Creates a loop which forwards all log messages send into the channel to the associated
/// logging xdc.
pub async fn debug_logging_loop(context: &Context, events: Receiver<DebugEventLogData>) {
while let Ok(DebugEventLogData {
time,
msg_id,
event,
}) = events.recv().await
{
match context
.write_status_update_inner(
&msg_id,
StatusUpdateItem {
payload: json!({
"event": event,
"time": time,
}),
info: None,
summary: None,
document: None,
},
)
.await
{
Err(err) => {
eprintln!("Can't log event to webxdc status update: {err:#}");
}
Ok(serial) => {
context.events.emit(Event {
id: context.id,
typ: EventType::WebxdcStatusUpdate {
msg_id,
status_update_serial: serial,
},
});
}
}
}
}
/// Set message as new logging webxdc if filename and chat_id fit
pub async fn maybe_set_logging_xdc(
context: &Context,
msg: &Message,
chat_id: ChatId,
) -> anyhow::Result<()> {
maybe_set_logging_xdc_inner(
context,
msg.get_viewtype(),
chat_id,
msg.param.get_path(Param::File, context).unwrap_or_default(),
msg.get_id(),
)
.await?;
Ok(())
}
/// Set message as new logging webxdc if filename and chat_id fit
pub async fn maybe_set_logging_xdc_inner(
context: &Context,
viewtype: Viewtype,
chat_id: ChatId,
file: Option<PathBuf>,
msg_id: MsgId,
) -> anyhow::Result<()> {
if viewtype == Viewtype::Webxdc {
if let Some(file) = file {
if let Some(file_name) = file.file_name().and_then(|name| name.to_str()) {
if file_name.starts_with("debug_logging")
&& file_name.ends_with(".xdc")
&& chat_id.is_self_talk(context).await?
{
set_debug_logging_xdc(context, Some(msg_id)).await?;
}
}
}
}
Ok(())
}
/// Set the webxdc contained in the msg as the current logging xdc on the context and save it to db
/// If id is a `None` value, disable debug logging
pub(crate) async fn set_debug_logging_xdc(ctx: &Context, id: Option<MsgId>) -> anyhow::Result<()> {
match id {
Some(msg_id) => {
ctx.sql
.set_raw_config(
Config::DebugLogging.as_ref(),
Some(msg_id.to_string().as_ref()),
)
.await?;
let debug_logging = &mut *ctx.debug_logging.write().await;
match debug_logging {
// Switch logging xdc
Some(debug_logging) => debug_logging.msg_id = msg_id,
// Bootstrap background loop for message forwarding
None => {
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 },
)
};
*debug_logging = Some(DebugLogging {
msg_id,
loop_handle,
sender,
});
}
}
info!(ctx, "replacing logging webxdc");
}
// Delete current debug logging
None => {
ctx.sql
.set_raw_config(Config::DebugLogging.as_ref(), None)
.await?;
*ctx.debug_logging.write().await = None;
info!(ctx, "removing logging webxdc");
}
}
Ok(())
}

View File

@@ -99,8 +99,7 @@ pub(crate) async fn prepare_decryption(
from,
autocrypt_header.as_ref(),
message_time,
// Disallowing keychanges is disabled for now:
true, // dkim_results.allow_keychange,
dkim_results.allow_keychange,
)
.await?;

View File

@@ -407,7 +407,7 @@ mod tests {
async fn test_quote_div() {
let input = include_str!("../test-data/message/gmx-quote-body.eml");
let dehtml = dehtml(input).unwrap();
println!("{}", dehtml);
println!("{dehtml}");
let SimplifiedText {
text,
is_forwarded,

View File

@@ -35,6 +35,7 @@ pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
/// Download state of the message.
#[derive(
Debug,
Default,
Display,
Clone,
Copy,
@@ -50,6 +51,7 @@ pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
#[repr(u32)]
pub enum DownloadState {
/// Message is fully downloaded.
#[default]
Done = 0,
/// Message is partially downloaded and can be fully downloaded at request.
@@ -62,12 +64,6 @@ pub enum DownloadState {
InProgress = 1000,
}
impl Default for DownloadState {
fn default() -> Self {
DownloadState::Done
}
}
impl Context {
// Returns validated download limit or `None` for "no limit".
pub(crate) async fn download_limit(&self) -> Result<Option<u32>> {
@@ -246,7 +242,7 @@ impl MimeMessage {
time() + max(delete_server_after, MIN_DELETE_SERVER_AFTER),
)
.await;
text += format!(" [{}]", until).as_str();
text += format!(" [{until}]").as_str();
};
info!(context, "Partial download: {}", text);
@@ -370,7 +366,7 @@ mod tests {
receive_imf_inner(
&t,
"Mr.12345678901@example.com",
format!("{}\n\n100k text...", header).as_bytes(),
format!("{header}\n\n100k text...").as_bytes(),
false,
None,
false,
@@ -450,7 +446,7 @@ mod tests {
.await?;
let msg = bob.get_last_msg().await;
let chat_id = msg.chat_id;
assert_eq!(get_chat_msgs(&bob, chat_id, 0).await?.len(), 1);
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 1);
assert_eq!(msg.download_state(), DownloadState::Available);
// downloading the status update afterwards expands to nothing and moves the placeholder to trash-chat
@@ -464,7 +460,7 @@ mod tests {
false,
)
.await?;
assert_eq!(get_chat_msgs(&bob, chat_id, 0).await?.len(), 0);
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 0);
assert!(Message::load_from_db(&bob, msg.id)
.await?
.chat_id
@@ -517,13 +513,13 @@ mod tests {
.await?;
let msg = bob.get_last_msg().await;
let chat_id = msg.chat_id;
assert_eq!(get_chat_msgs(&bob, chat_id, 0).await?.len(), 1);
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 1);
assert_eq!(msg.download_state(), DownloadState::Available);
// downloading the mdn afterwards expands to nothing and deletes the placeholder directly
// (usually mdn are too small for not being downloaded directly)
receive_imf_inner(&bob, "bar@example.org", raw, false, None, false).await?;
assert_eq!(get_chat_msgs(&bob, chat_id, 0).await?.len(), 0);
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 0);
assert!(Message::load_from_db(&bob, msg.id)
.await?
.chat_id

View File

@@ -78,7 +78,7 @@ impl EncryptHelper {
};
}
None => {
let msg = format!("peerstate for {:?} missing, cannot encrypt", addr);
let msg = format!("peerstate for {addr:?} missing, cannot encrypt");
if e2ee_guaranteed {
return Err(format_err!("{}", msg));
} else {
@@ -112,7 +112,7 @@ impl EncryptHelper {
{
let key = peerstate
.take_key(min_verified)
.with_context(|| format!("proper enc-key for {} missing, cannot encrypt", addr))?;
.with_context(|| format!("proper enc-key for {addr} missing, cannot encrypt"))?;
keyring.add(key);
}
keyring.add(self.public_key.clone());

View File

@@ -62,8 +62,6 @@
//! the database entries which are expired either according to their
//! ephemeral message timers or global `delete_server_after` setting.
#![allow(missing_docs)]
use std::cmp::max;
use std::convert::{TryFrom, TryInto};
use std::num::ParseIntError;
@@ -88,13 +86,25 @@ use crate::sql::{self, params_iter};
use crate::stock_str;
use crate::tools::{duration_to_str, time};
/// Ephemeral timer value.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum Timer {
/// Timer is disabled.
Disabled,
Enabled { duration: u32 },
/// Timer is enabled.
Enabled {
/// Timer duration in seconds.
///
/// The value cannot be 0.
duration: u32,
},
}
impl Timer {
/// Converts epehmeral timer value to integer.
///
/// If the timer is disabled, return 0.
pub fn to_u32(self) -> u32 {
match self {
Self::Disabled => 0,
@@ -102,6 +112,9 @@ impl Timer {
}
}
/// Converts integer to ephemeral timer value.
///
/// 0 value is treated as disabled timer.
pub fn from_u32(duration: u32) -> Self {
if duration == 0 {
Self::Disabled
@@ -1067,7 +1080,7 @@ mod tests {
}
async fn check_msg_is_deleted(t: &TestContext, chat: &Chat, msg_id: MsgId) {
let chat_items = chat::get_chat_msgs(t, chat.id, 0).await.unwrap();
let chat_items = chat::get_chat_msgs(t, chat.id).await.unwrap();
// Check that the chat is empty except for possibly info messages:
for item in &chat_items {
if let ChatItem::Message { msg_id } = item {
@@ -1086,7 +1099,7 @@ mod tests {
.query_get_value("SELECT txt_raw FROM msgs WHERE id=?;", paramsv![msg_id])
.await
.unwrap();
assert!(rawtxt.is_none_or_empty(), "{:?}", rawtxt);
assert!(rawtxt.is_none_or_empty(), "{rawtxt:?}");
}
}

View File

@@ -1,10 +1,9 @@
//! # Events specification.
#![allow(missing_docs)]
use std::path::PathBuf;
use async_channel::{self as channel, Receiver, Sender, TrySendError};
use serde::Serialize;
use crate::chat::ChatId;
use crate::contact::ContactId;
@@ -26,12 +25,14 @@ impl Default for Events {
}
impl Events {
/// Creates a new event channel.
pub fn new() -> Self {
let (sender, receiver) = channel::bounded(1_000);
Self { receiver, sender }
}
/// Emits an event.
pub fn emit(&self, event: Event) {
match self.sender.try_send(event) {
Ok(()) => {}
@@ -108,7 +109,8 @@ pub struct Event {
pub typ: EventType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
/// Event payload.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum EventType {
/// The library-user may write an informational string to the log.
///
@@ -168,17 +170,23 @@ pub enum EventType {
/// - Chats created, deleted or archived
/// - A draft has been set
///
/// `chat_id` is set if only a single chat is affected by the changes, otherwise 0.
/// `msg_id` is set if only a single message is affected by the changes, otherwise 0.
MsgsChanged {
/// Set if only a single chat is affected by the changes, otherwise 0.
chat_id: ChatId,
/// Set if only a single message is affected by the changes, otherwise 0.
msg_id: MsgId,
},
/// Reactions for the message changed.
ReactionsChanged {
/// ID of the chat which the message belongs to.
chat_id: ChatId,
/// ID of the message for which reactions were changed.
msg_id: MsgId,
/// ID of the contact whose reaction set is changed.
contact_id: ContactId,
},
@@ -187,11 +195,16 @@ pub enum EventType {
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
IncomingMsg {
/// ID of the chat where the message is assigned.
chat_id: ChatId,
/// ID of the message.
msg_id: MsgId,
},
/// Downloading a bunch of messages just finished.
IncomingMsgBunch {
/// List of incoming message IDs.
msg_ids: Vec<MsgId>,
},
@@ -202,21 +215,30 @@ pub enum EventType {
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
MsgDelivered {
/// ID of the chat which the message belongs to.
chat_id: ChatId,
/// ID of the message that was successfully sent.
msg_id: MsgId,
},
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
MsgFailed {
/// ID of the chat which the message belongs to.
chat_id: ChatId,
/// ID of the message that could not be sent.
msg_id: MsgId,
},
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
MsgRead {
/// ID of the chat which the message belongs to.
chat_id: ChatId,
/// ID of the message that was read.
msg_id: MsgId,
},
@@ -231,7 +253,10 @@ pub enum EventType {
/// Chat ephemeral timer changed.
ChatEphemeralTimerModified {
/// Chat ID.
chat_id: ChatId,
/// New ephemeral timer value.
timer: EphemeralTimer,
},
@@ -278,15 +303,15 @@ pub enum EventType {
///
/// These events are typically sent after a joiner has scanned the QR code
/// generated by dc_get_securejoin_qr().
///
/// @param data1 (int) ID of the contact that wants to join.
/// @param data2 (int) Progress as:
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
/// 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
SecurejoinInviterProgress {
/// ID of the contact that wants to join.
contact_id: ContactId,
/// Progress as:
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
/// 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
progress: usize,
},
@@ -294,12 +319,13 @@ pub enum EventType {
/// (Bob, the person who scans the QR code).
/// The events are typically sent while dc_join_securejoin(), which
/// may take some time, is executed.
/// @param data1 (int) ID of the inviting contact.
/// @param data2 (int) Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
SecurejoinJoinerProgress {
/// ID of the inviting contact.
contact_id: ContactId,
/// Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
progress: usize,
},
@@ -309,15 +335,21 @@ pub enum EventType {
/// dc_get_connectivity_html() for details.
ConnectivityChanged,
/// The user's avatar changed.
SelfavatarChanged,
/// Webxdc status update received.
WebxdcStatusUpdate {
/// Message ID.
msg_id: MsgId,
/// Status update ID.
status_update_serial: StatusUpdateSerial,
},
/// Inform that a message containing a webxdc instance has been deleted
/// Inform that a message containing a webxdc instance has been deleted.
WebxdcInstanceDeleted {
/// ID of the deleted message.
msg_id: MsgId,
},
}

Some files were not shown because too many files have changed in this diff Show More