Compare commits

...

220 Commits

Author SHA1 Message Date
iequidoo
7c7801b9a9 Update provider/data.rs from the deltachat-provider-db repo 2023-02-21 11:03:49 -03:00
iequidoo
15c9efaa95 Update provider/update.py according to the struct Provider changes 2023-02-21 10:53:15 -03:00
link2xt
e86fbf5855 ci: stop using deprecated Ubuntu 18.04
GitHub Actions fails running 18.04 jobs currently:
<https://github.com/actions/runner-images/issues/6002>
2023-02-21 13:10:12 +00:00
bjoern
38a62d92ba mention speedup of #4065 in CHANGELOG (#4073)
the speedup of #4065 is larger than measured first

running `cargo test`, first we thought there is only a slight speedup
from 13.5 seconds to 12.5 seconds on a m1pro.

however, there is one test that does 11 seconds of sleep() (test_modify_chat_disordered)
as this test is not run very first, this slows down things overall.

skipping this test,
speedup is from 13.5 seconds to 9.5 seconds -
28% faster - and this is something we should mention in the changelog :)

(this pr does not remove or change the slow test.
i think, due to the number of cores, this is not needed until someone
has a machine where the other tests run in 2 seconds or so)
2023-02-21 13:36:36 +01:00
link2xt
c01a2f2c24 Fix missing imports in deltachat_rpc_client 2023-02-21 12:21:17 +00:00
Hocuri
17acbca576 Correctly clear database cache after import (#4067) 2023-02-21 11:23:34 +00:00
link2xt
05a274a5f3 Enable more ruff checks in deltachat-rpc-client 2023-02-21 11:17:10 +00:00
link2xt
f07206bd6c Pin ruff version in deltachat-rpc-client
Latest versions 0.0.248 and 0.0.249 report false positive:
src/deltachat_rpc_client/client.py:105:9: RET503 [*] Missing explicit `return` at the end of function able to return
2023-02-20 19:57:54 +00:00
link2xt
92c7cc40d4 sql: replace .get_conn() interface with .call()
.call() interface is safer because it ensures
that blocking operations on SQL connection
are called within tokio::task::block_in_place().

Previously some code called blocking operations
in async context, e.g. add_parts() in receive_imf module.

The underlying implementation of .call()
can later be replaced with an implementation
that does not require block_in_place(),
e.g. a worker pool,
without changing the code using the .call() interface.
2023-02-20 18:54:29 +00:00
Hocuri
710cec1beb Remove show_emails argument from prefetch_should_download() (#4064)
IIRC, this was written this way back when we didn't have config caching,
in order to save database accesses by getting the config outside the
for loop.
2023-02-20 18:06:43 +00:00
link2xt
56d10f7c42 Use transaction in update_blocked_mailinglist_contacts 2023-02-20 17:17:59 +00:00
Franz Heinzmann
ef03a33b29 JSON-RPC: Add CommonJS build (#4062)
add CommonJS build
2023-02-20 18:10:32 +01:00
iequidoo
3b5227c42a Move strict_tls, max_smtp_rcpt_to from Provider to ProviderOptions 2023-02-20 14:09:27 -03:00
iequidoo
604c4fcb71 Delete messages to the Trash folder for Gmail by default (#3957)
Gmail archives messages marked as `\Deleted` by default if those messages aren't in the Trash. But
if move them to the Trash instead, they will be auto-deleted in 30 days.
2023-02-20 14:09:27 -03:00
link2xt
4790ad0478 Merge switching SQLite connection pool from FIFO to LIFO 2023-02-20 16:43:19 +00:00
link2xt
eaa2ef5a44 sql: start transactions as IMMEDIATE
With the introduction of transactions in Contact::add_or_lookup(),
python tests sometimes fail to create contacts with the folowing error:

Cannot create contact: add_or_lookup: database is locked: Error code 5: The database file is locked

`PRAGMA busy_timeout=60000` does not affect
this case as the error is returned before 60 seconds pass.

DEFERRED transactions with write operations need
to be retried from scratch
if they are rolled back
due to a write operation on another connection.

Using IMMEDIATE transactions for writing
is an attempt to fix this problem
without a retry loop.

If we later need DEFERRED transactions,
e.g. for reading a snapshot without locking the database,
we may introduce another function for this.
2023-02-20 15:05:35 +00:00
link2xt
2eeacb0f8a sql: organize connection pool as a stack rather than a queue
When connection pool is organized as a stack,
it always returns most recently used connection.
Because each connection has its own page cache,
using the connection with fresh cache improves performance.

I commented out `oauth2::tests::test_oauth_from_mx`
because it requires network connection,
turned off the Wi-Fi and ran the tests.

Before the change, with a queue:
```
$ hyperfine "cargo test"
Benchmark 1: cargo test
  Time (mean ± σ):     56.424 s ±  4.515 s    [User: 183.181 s, System: 128.156 s]
  Range (min … max):   52.123 s … 68.193 s    10 runs
```

With a stack:
```
$ hyperfine "cargo test"
Benchmark 1: cargo test
  Time (mean ± σ):     29.887 s ±  1.377 s    [User: 101.226 s, System: 45.573 s]
  Range (min … max):   26.591 s … 31.010 s    10 runs
```

On version 1.107.1:
```
$ hyperfine "cargo test"
Benchmark 1: cargo test
  Time (mean ± σ):     43.658 s ±  1.079 s    [User: 202.582 s, System: 50.723 s]
  Range (min … max):   41.531 s … 45.170 s    10 runs
```
2023-02-20 15:00:37 +00:00
link2xt
840497d356 format mergeable.yml 2023-02-20 11:49:59 +00:00
link2xt
0dd87b0bae ci: format .yml with prettier 2023-02-20 11:48:57 +00:00
link2xt
e2151e26ee ci: pin rustfmt version 2023-02-19 23:40:04 +00:00
link2xt
446214fd7b sql: use transaction in Contact::add_or_lookup() 2023-02-19 23:16:44 +00:00
link2xt
9389e11007 ffi: log create_contact() errors 2023-02-19 23:16:44 +00:00
link2xt
5bdd49b451 Add Unreleased section to changelog 2023-02-19 21:57:36 +00:00
link2xt
42a7e91f05 python: stop using pytest-rerunfailures 2023-02-19 21:55:28 +00:00
link2xt
44953d6bcc Release 1.109.0 2023-02-19 21:40:33 +00:00
link2xt
10066b2bc7 sql: use semaphore to limit access to the connection pool
This ensures that if multiple connections are returned
to the pool at the same time, waiters get them in the order
they were placed in the queue.
2023-02-19 17:06:25 +00:00
Simon Laux
609fc67f0d fix websocket server & add ci test for building it (#4047)
the axum update broke the websocket server, because yerpc still uses a the old 5.9 version.
So I needed to downgrade the dependency until https://github.com/deltachat/yerpc/pull/35 is merged.

I want to retry the kaiOS client experiment, for this I need a working websocket server binary.
2023-02-19 14:57:00 +01:00
Hocuri
641d102aba Add dc_msg_set_subject() C FFI (#4057) 2023-02-19 13:42:39 +00:00
link2xt
f65e1c1587 Sort dependencies in Cargo.toml 2023-02-19 13:18:58 +00:00
link2xt
626ec5e793 Document the meaning of empty Message.subject 2023-02-19 13:05:39 +00:00
link2xt
85517abf58 Derive Debug for Pool 2023-02-19 02:26:26 +00:00
link2xt
75f65b06e8 Run VACUUM on the same connection as sqlcipher_export
Otherwise export_and_import_backup test fails due to SQLITE_SCHEMA error.
2023-02-19 02:26:26 +00:00
link2xt
f2b05ccc29 Reimplement connection pool on top of crossbeam 2023-02-19 02:26:26 +00:00
link2xt
e88f21c010 Remove try_many_times r2d2 bug workaround 2023-02-19 02:26:26 +00:00
link2xt
ed8e2c4818 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-19 02:26:26 +00:00
link2xt
48fee4fc92 python: replace "while 1" with "while True"
This makes pyright recognize that the function never returns implicitly.
2023-02-18 11:31:07 +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
link2xt
fac7b064b4 Refine Python CI
Add lint environment to `deltachat-rpc-client/`
and set line length to 120, same as in `python/`.

Switch from flake8 to ruff.

Fix ruff warnings.
2023-01-20 16:53:21 +00:00
link2xt
ef6f252842 Introduce DNS cache for IMAP connections
DNS cache is used as a fallback if TCP connection
to all IP addresses returned by DNS failed.
If strict TLS checks are disabled,
DNS cache results are stored, but not used.

GitHub Pull Request: <https://github.com/deltachat/deltachat-core-rust/pull/3970>
2023-01-20 10:21:38 +00:00
link2xt
b8da19e49f Upgrade async-smtp to v0.6 2023-01-19 22:10:40 +00:00
link2xt
a483df8b20 Add all resolver results with the same timestamp 2023-01-19 21:29:17 +00:00
link2xt
41ccc13394 Extend lookup_host_with_cache comment 2023-01-19 21:06:31 +00:00
link2xt
0978357c5f Do not cache IP addresses which resolve into themselves 2023-01-19 20:43:53 +00:00
link2xt
7935085e74 Remove port number from DNS cache table 2023-01-19 20:26:11 +00:00
link2xt
7a47c9e38b Adapt nicer_configuration_error to new error message 2023-01-19 18:29:18 +00:00
link2xt
20124bfca0 Add DNS lookup timeout 2023-01-19 17:33:59 +00:00
link2xt
eaeaa297c7 Maximize priority of the cached address on successful connection 2023-01-19 16:55:43 +00:00
link2xt
9adb9ab5f4 Return last connection error from connect_tcp 2023-01-19 16:50:50 +00:00
link2xt
c4c4c977a6 Changelog 2023-01-19 16:50:50 +00:00
link2xt
7d508dcb52 Log DNS resolution errors instead of failing directly 2023-01-19 16:50:50 +00:00
link2xt
773754d74f Introduce DNS cache table
Only used for IMAP connections currently.
2023-01-19 16:50:50 +00:00
link2xt
ed20a23297 Resolve IP addresses explicitly 2023-01-19 16:10:26 +00:00
link2xt
4615c84f31 Automatically group imports using nightly rustfmt 2023-01-19 13:13:25 +00:00
link2xt
677136f4ab Use SMTP pipelining 2023-01-19 01:30:32 +00:00
iequidoo
3c3710420b Don't unpin chat on sending / receiving not fresh messages (#3967)
This bug was introduced by 3cf78749df - there are three visibility
states: Archived, Pinned and Normal - in the database, this "visibility" is named historically
"archived" ... the original code has an AND archived=1 therefore.
2023-01-18 16:57:38 -03:00
link2xt
de268b8225 Terminate recently seen loop if cannot receive interrupts
It seems .abort() does not work on the recently seen loop
in some cases, e.g. if it is busy looping in a separate thread.

In my case after account reconfiguration recently seen loop
kept running and issuing warnings about closed interrupt channel.

Exit from recently seen loop on errors to avoid using 100% CPU
in such cases.
2023-01-18 10:48:54 +00:00
link2xt
42c709e7b1 Fix SOCKS5 usage for IMAP
Connect to SOCKS5 server rather than target server
and send TCP connect command.
2023-01-18 10:13:17 +00:00
link2xt
cf0349acc8 configure: log SOCKS5 configuration for IMAP like we do for SMTP 2023-01-18 09:32:29 +00:00
link2xt
e43b36b61f Peerstate.verifier fixes
Derive Debug, PartialEq and Eq for Peerstate,
so `verifier` is included in Debug output and compared.

Store verifier as empty string
instead of NULL in the database.
2023-01-17 14:22:22 +00:00
Simon Laux
ed24867309 fix verifier-by addr was empty string instead of None (#3961)
fix verifier-by addr was empty string intead of None
2023-01-17 13:06:57 +00:00
iequidoo
3cf78749df Emit DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK when the number of archived chats with
unread messages increases (#3940)
2023-01-16 16:05:47 -03:00
iequidoo
badbf766bb Move event emitting for a new message to a separate function 2023-01-16 16:05:47 -03:00
bjoern
5b265dbc1c remove comma from unit in message-details (#3954)
it shoud read "filename.ext, 123 bytes" and not "filename.ext, 123, bytes"
2023-01-13 18:25:20 +01:00
link2xt
0053e143e7 Do not emit ChatModified event when user avatar is updated 2023-01-12 20:52:47 +00:00
link2xt
1c44135b41 Remove deprecated attach_selfavatar config
According to the comment it was added in Dec 2019
with an intention to remove it "after some time".
2023-01-12 20:52:47 +00:00
iequidoo
5f883a4445 Prepare to remove "vc-contact-confirm-received", "vg-member-added-received" messages from Securejoin
protocol
2023-01-12 15:13:30 -03:00
iequidoo
8dc6ff268d check_verified_properties(): Don't ignore fails of Peerstate::set_verified()
- Return Result from set_verified() so that it can't be missed.
- Pass Fingerprint to set_verified() by value to avoid cloning it inside. This optimises out an
  extra clone() if we already have a value that can be moved at the caller side. However, this may
  add an extra clone() if set_verified() fails, but let's not optimise the fail scenario.
2023-01-12 15:13:30 -03:00
iequidoo
57d7df530b Add a test that a new verified member is seen on the second device going online (#3836)
- Alice has two devices, the second is offline.
- Alice creates a verified group and sends a QR invitation to Bob.
- Bob joins the group and sends a message there. Alice sees it.
- Alice's second devices goes online, but doesn't see Bob in the group.
2023-01-12 15:13:30 -03:00
iequidoo
13b2fe8d30 import_self_keys(): Log set_self_key() error as DC_EVENT_INFO
It isn't an error actually since we just skip the file.
2023-01-12 15:13:30 -03:00
iequidoo
d644988845 Securejoin: Fix adding and handling Autocrypt-Gossip headers (#3836)
- If bcc_self is set, gossip headers must be added despite of the number of group members.
- If another device observes Secure-Join, instead of looking for Secure-Join-Fingerprint in
  "vg-member-added"/"vc-contact-confirm" messages it must use keys from Autocrypt-Gossip headers as
  described in the Countermitm doc
  (https://countermitm.readthedocs.io/en/latest/new.html#joining-a-verified-group-secure-join).
2023-01-12 15:13:30 -03:00
iequidoo
27c6cfc958 Log messages in info!() if DCC_MIME_DEBUG is set
Using println!() leads to reordered output on terminal. Moreover, println!() prints to stdout which
is not for logging.
2023-01-12 15:13:30 -03:00
link2xt
3b9a48ff5f python: remove "data1=0" from INFO/WARNING/ERROR events display 2023-01-12 18:12:05 +00:00
link2xt
a5354ded3f ci: disable fail-fast
This setting is true by default and causes
Windows build to cancel when Linux fails
due to flaky test and vice versa.
Cancelled test then has to be restarted
from scratch even though it was not going
to fail.
2023-01-12 17:51:58 +00:00
Simon Laux
0b07dafe77 add verified-by api to jsonrpc (#3946)
also refactor it so that it is not a static method anymore
(would have resulted in two load-Contact-from-db-calls in jsonrpc)
2023-01-12 16:13:27 +00:00
link2xt
f0e900b885 Cleanup constants module 2023-01-12 13:19:16 +00:00
link2xt
f460043e87 Add missing documentation to webxdc module 2023-01-12 13:18:30 +00:00
link2xt
8c6b804d73 Merge "Add ContactAddress type" (#3947) 2023-01-11 23:19:48 +00:00
link2xt
790512d52a Reduce code indentation 2023-01-11 23:19:07 +00:00
link2xt
89c8d26968 Add ContactAddress type 2023-01-11 23:07:47 +00:00
iequidoo
6d9d31cad1 Add timeouts to HTTP requests (#3908) 2023-01-11 14:29:49 -03:00
link2xt
6642083f52 Clippy fix 2023-01-10 21:17:30 +00:00
link2xt
554090b03e Prepare 1.106.0 2023-01-10 20:57:14 +00:00
link2xt
1cde09c312 Add missing documentation to the message module 2023-01-10 20:52:22 +00:00
link2xt
e215b4d919 Return Option from Contact::add_or_lookup()
This allows to distinguish exceptions,
such as database errors, from invalid user input.
For example, if the From: field of the message
does not look like an email address, the mail
should be ignored. But if there is a database
failure while writing a new contact for the address,
this error should be bubbled up.
2023-01-10 20:43:20 +00:00
link2xt
5ae6c25394 Remove aheader module dependency on mailparse 2023-01-10 14:53:48 +00:00
Hocuri
68cd8dbc3e Only send IncomingMsgBunch if there are more than 0 new messages (#3941) 2023-01-10 13:18:40 +00:00
link2xt
01fe88e337 Save modified .toml if absolute paths were replaced with relative
Previously this required adding or removing an account.
2023-01-10 01:37:54 +00:00
link2xt
2b8923931e Add more context to IMAP errors 2023-01-10 00:00:23 +00:00
Hocuri
8d119713bc Print chats when a test failed (#3937)
* Print chats after a test failed again

E.g.
```
========== Chats of bob: ==========
Single#Chat#10: alice@example.org [alice@example.org]
--------------------------------------------------------------------------------
Msg#10:  (Contact#Contact#10): hellooo [FRESH]
Msg#11:  (Contact#Contact#10): hellooo without mailing list [FRESH]
--------------------------------------------------------------------------------

========== Chats of alice: ==========
Single#Chat#10: bob@example.net [bob@example.net]
--------------------------------------------------------------------------------
Msg#10: Me (Contact#Contact#Self): hellooo  √
Msg#11: Me (Contact#Contact#Self): hellooo without mailing list  √
--------------------------------------------------------------------------------
```

I found this very useful sometimes, so, let's try to re-introduce it (it
was removed in #3449)

* Add failing test for TestContext::drop()

* Do not panic in TestContext::drop() if runtime is dropped

Co-authored-by: link2xt <link2xt@testrun.org>
2023-01-09 22:46:32 +01:00
Simon Laux
07f2e28eca fix: only send contact changed event for recently seen if it is relevant (#3938)
(no events for contacts that are already offline again)

This dramatically speeds up replay after backup import on desktop.
2023-01-09 21:12:33 +00:00
link2xt
0e849609f4 ci: python: do not try to upload source distributions
They are not useful because you still need a statically
linked rust library besides the C FFI module, and
they are not built by tox 4 without changes.
2023-01-09 16:20:56 +00:00
185 changed files with 6444 additions and 3962 deletions

28
.github/mergeable.yml vendored
View File

@@ -5,22 +5,22 @@ mergeable:
validate:
- do: or
validate:
- do: description
must_include:
regex: '#skip-changelog'
- do: and
validate:
- do: dependent
changed:
file: 'src/**'
required: ['CHANGELOG.md']
- do: dependent
changed:
file: 'deltachat-ffi/src/**'
required: ['CHANGELOG.md']
- do: description
must_include:
regex: "#skip-changelog"
- do: and
validate:
- do: dependent
changed:
file: "src/**"
required: ["CHANGELOG.md"]
- do: dependent
changed:
file: "deltachat-ffi/src/**"
required: ["CHANGELOG.md"]
fail:
- do: checks
status: 'action_required'
status: "action_required"
payload:
title: Changelog might need an update
summary: "Check if CHANGELOG.md needs an update or add #skip-changelog to the PR description."

View File

@@ -5,44 +5,26 @@ on:
push:
branches:
- master
- staging
- trying
env:
RUSTFLAGS: -Dwarnings
jobs:
fmt:
name: Rustfmt
lint:
name: Rustfmt and Clippy
runs-on: ubuntu-latest
env:
RUSTUP_TOOLCHAIN: 1.67.1
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- name: Install rustfmt and clippy
run: rustup toolchain install $RUSTUP_TOOLCHAIN --component rustfmt --component clippy
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- run: cargo fmt --all -- --check
run_clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: clippy
override: true
- 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 rustfmt
run: cargo fmt --all -- --check
- name: Run clippy
run: scripts/clippy.sh
docs:
name: Rust doc comments
@@ -52,13 +34,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
@@ -67,6 +42,7 @@ jobs:
build_and_test:
name: Build and test
strategy:
fail-fast: false
matrix:
include:
# Currently used Rust version.
@@ -87,75 +63,73 @@ jobs:
python: 3.7
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@master
- 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: 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
run: cargo vendor
- name: Test cargo vendor
run: cargo vendor
- name: install python
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Install python
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: install tox
if: ${{ matrix.python }}
run: pip install tox
- name: Install tox
if: ${{ matrix.python }}
run: pip install tox
- name: build C library
if: ${{ matrix.python }}
run: cargo build -p deltachat_ffi --features jsonrpc
- name: Build C library
if: ${{ matrix.python }}
run: cargo build -p deltachat_ffi --features jsonrpc
- name: run python tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e lint,mypy,doc,py3
- name: Run python tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e lint,mypy,doc,py3
- name: build deltachat-rpc-server
if: ${{ matrix.python }}
run: cargo build -p 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
if: ${{ matrix.python }}
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_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
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
working-directory: deltachat-rpc-client
run: tox -e py3
- 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
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: 'pypy${{ matrix.python }}'
- name: Install pypy
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: "pypy${{ matrix.python }}"
- name: run pypy tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e pypy3
- name: Run pypy tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e pypy3

View File

@@ -1,25 +1,24 @@
name: 'jsonrpc js client build'
name: "jsonrpc js client build"
on:
pull_request:
push:
tags:
- '*'
- '!py-*'
- "*"
- "!py-*"
jobs:
pack-module:
name: 'Package @deltachat/jsonrpc-client and upload to download.delta.chat'
runs-on: ubuntu-18.04
name: "Package @deltachat/jsonrpc-client and upload to download.delta.chat"
runs-on: ubuntu-20.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
node-version: "16"
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
continue-on-error: true
@@ -38,11 +37,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,13 +64,13 @@ 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:
URL: preview/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MSG_CONTEXT: Download the deltachat-jsonrpc-client.tgz
URL: preview/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MSG_CONTEXT: Download the deltachat-jsonrpc-client.tgz
# Upload to download.delta.chat/node/
- name: Upload deltachat-jsonrpc-client build to download.delta.chat/node/
if: ${{ steps.tag.outputs.tag }}

View File

@@ -35,6 +35,10 @@ jobs:
npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
- name: make sure websocket server version still builds
run: |
cd deltachat-jsonrpc
cargo build --bin deltachat-jsonrpc-server --features webserver
- name: Run linter
run: |
cd deltachat-jsonrpc/typescript

View File

@@ -7,26 +7,25 @@ on:
jobs:
delete:
runs-on: ubuntu-latest
steps:
- name: Get Pullrequest ID
id: getid
run: |
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
- name: Renaming
run: |
# create empty file to copy it over the outdated deliverable on download.delta.chat
echo "This preview build is outdated and has been removed." > empty
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
- name: Replace builds with dummy files
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
host: "download.delta.chat"
port: 22
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
remote: "/var/www/html/download/node/preview/"
- name: Get Pullrequest ID
id: getid
run: |
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
- name: Renaming
run: |
# create empty file to copy it over the outdated deliverable on download.delta.chat
echo "This preview build is outdated and has been removed." > empty
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
- name: Replace builds with dummy files
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
host: "download.delta.chat"
port: 22
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
remote: "/var/www/html/download/node/preview/"

View File

@@ -9,26 +9,26 @@ jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: npm install and generate documentation
run: |
cd node
npm i --ignore-scripts
npx typedoc
mv docs js
- name: npm install and generate documentation
run: |
cd node
npm i --ignore-scripts
npx typedoc
mv docs js
- name: Upload
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
host: "delta.chat"
port: 22
local: "node/js"
remote: "/var/www/html/"
- name: Upload
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
host: "delta.chat"
port: 22
local: "node/js"
remote: "/var/www/html/"

View File

@@ -1,25 +1,24 @@
name: 'node.js build'
name: "node.js build"
on:
pull_request:
push:
tags:
- '*'
- '!py-*'
- "*"
- "!py-*"
jobs:
prebuild:
name: 'prebuild'
name: Prebuild
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04, macos-latest, windows-latest]
os: [ubuntu-20.04, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
node-version: "16"
- name: System info
run: |
rustc -vV
@@ -65,17 +64,17 @@ jobs:
pack-module:
needs: prebuild
name: 'Package deltachat-node and upload to download.delta.chat'
runs-on: ubuntu-18.04
name: Package deltachat-node and upload to download.delta.chat
runs-on: ubuntu-20.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
node-version: "16"
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
continue-on-error: true
@@ -97,43 +96,43 @@ 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: ubuntu-20.04
- 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
- shell: bash
run: |
mkdir node/prebuilds
tar -xvzf ubuntu-18.04/ubuntu-18.04.tar.gz -C node/prebuilds
tar -xvzf ubuntu-20.04/ubuntu-20.04.tar.gz -C node/prebuilds
tar -xvzf macos-latest/macos-latest.tar.gz -C node/prebuilds
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
rm -rf ubuntu-20.04 macos-latest windows-latest
- 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,12 +147,12 @@ 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:
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Upload to download.delta.chat/node/
- name: Upload deltachat-node build to download.delta.chat/node/
if: ${{ steps.tag.outputs.tag }}

View File

@@ -1,4 +1,4 @@
name: 'node.js tests'
name: "node.js tests"
on:
pull_request:
push:
@@ -9,17 +9,17 @@ on:
jobs:
tests:
name: 'tests'
name: Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
node-version: "16"
- name: System info
run: |
rustc -vV
@@ -59,7 +59,7 @@ jobs:
npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: '--force-node-api-uncaught-exceptions-policy=true'
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
- name: Run tests on Windows, except lint
timeout-minutes: 10
if: runner.os == 'Windows'
@@ -68,4 +68,4 @@ jobs:
npm run test:mocha
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: '--force-node-api-uncaught-exceptions-policy=true'
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"

View File

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

View File

@@ -8,20 +8,18 @@ on:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat --no-deps
- name: Upload to rs.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/rs/"
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat --no-deps
- name: Upload to rs.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/rs/"

View File

@@ -8,20 +8,18 @@ on:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat_ffi --no-deps
- name: Upload to cffi.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/cffi/"
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat_ffi --no-deps
- name: Upload to cffi.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/cffi/"

View File

@@ -3,10 +3,107 @@
## Unreleased
### Changes
- use transaction in `Contact::add_or_lookup()` #4059
- Organize the connection pool as a stack rather than a queue to ensure that
connection page cache is reused more often.
This speeds up tests by 28%, real usage will have lower speedup. #4065
- Use transaction in `update_blocked_mailinglist_contacts`. #4058
- Remove `Sql.get_conn()` interface in favor of `.call()` and `.transaction()`. #4055
### Fixes
- Start SQL transactions with IMMEDIATE behaviour rather than default DEFERRED one. #4063
- Fix a problem with Gmail where (auto-)deleted messages would get archived instead of deleted.
Move them to the Trash folder for Gmail which auto-deletes trashed messages in 30 days #3972
- Clear config cache after backup import. This bug sometimes resulted in the import to seemingly work at first. #4067
### API-Changes
## 1.109.0
### Changes
- deltachat-rpc-client: use `dataclass` for `Account`, `Chat`, `Contact` and `Message` #4042
### 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
- Replace `r2d2` connection pool with an own implementation. #4050 #4053 #4043 #4061
This change improves reliability
by closing all database connections immediately when the context is closed.
### 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
- Add `dc_msg_set_subject()`. C-FFI #4057
- Mark python bindings as supporting typing according to PEP 561 #4045
## 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 for IMAP connections #3970
### Fixes
- Securejoin: Fix adding and handling Autocrypt-Gossip headers #3914
- fix verifier-by addr was empty string intead of None #3961
- Emit DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK when the number of archived chats with
unread messages increases #3959
- Fix Peerstate comparison #3962
- Log SOCKS5 configuration for IMAP like already done for SMTP #3964
- Fix SOCKS5 usage for IMAP #3965
- Exit from recently seen loop on interrupt channel errors to avoid busy looping #3966
### API-Changes
- jsonrpc: add verified-by information to `Contact`-Object
- Remove `attach_selfavatar` config #3951
### Changes
- add debug logging support for webxdcs #3296
## 1.106.0
### Changes
- Only send IncomingMsgBunch if there are more than 0 new messages #3941
### Fixes
- fix: only send contact changed event for recently seen if it is relevant (not too old to matter) #3938
- Immediately save `accounts.toml` if it was modified by a migration from absolute paths to relative paths #3943
- Do not treat invalid email addresses as an exception #3942
- Add timeouts to HTTP requests #3948
## 1.105.0

374
Cargo.lock generated
View File

@@ -165,21 +165,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",
]
@@ -242,7 +239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43"
dependencies = [
"async-trait",
"axum-core 0.2.9",
"axum-core",
"base64 0.13.1",
"bitflags",
"bytes",
@@ -251,7 +248,7 @@ dependencies = [
"http-body",
"hyper",
"itoa",
"matchit 0.5.0",
"matchit",
"memchr",
"mime",
"percent-encoding",
@@ -269,42 +266,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "axum"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48"
dependencies = [
"async-trait",
"axum-core 0.3.0",
"base64 0.13.1",
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"itoa",
"matchit 0.7.0",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sha-1",
"sync_wrapper",
"tokio",
"tokio-tungstenite",
"tower",
"tower-http",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.2.9"
@@ -321,23 +282,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]]
name = "backtrace"
version = "0.3.67"
@@ -348,7 +292,7 @@ dependencies = [
"cc",
"cfg-if",
"libc",
"miniz_oxide 0.6.2",
"miniz_oxide",
"object",
"rustc-demangle",
]
@@ -373,9 +317,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"
@@ -450,9 +394,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 +562,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 +834,7 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "deltachat"
version = "1.105.0"
version = "1.109.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -883,12 +844,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 +869,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 +898,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.105.0"
version = "1.109.0"
dependencies = [
"anyhow",
"async-channel",
"axum 0.6.1",
"axum",
"deltachat",
"env_logger 0.10.0",
"futures",
@@ -967,9 +926,24 @@ dependencies = [
"yerpc",
]
[[package]]
name = "deltachat-repl"
version = "1.109.0"
dependencies = [
"ansi_term",
"anyhow",
"deltachat",
"dirs",
"log",
"pretty_env_logger",
"rusqlite",
"rustyline",
"tokio",
]
[[package]]
name = "deltachat-rpc-server"
version = "1.105.0"
version = "1.109.0"
dependencies = [
"anyhow",
"deltachat-jsonrpc",
@@ -992,7 +966,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.105.0"
version = "1.109.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1414,12 +1388,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 +1432,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 +1447,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 +1457,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 +1474,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 +1495,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 +1506,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 +1607,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 +1618,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 +1708,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 +1855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"hashbrown",
]
[[package]]
@@ -2048,9 +2014,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",
@@ -2133,12 +2099,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
[[package]]
name = "matchit"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
[[package]]
name = "md-5"
version = "0.10.5"
@@ -2175,15 +2135,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 +2191,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 +2221,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 +2372,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 +2393,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 +2619,7 @@ dependencies = [
"bitflags",
"crc32fast",
"flate2",
"miniz_oxide 0.6.2",
"miniz_oxide",
]
[[package]]
@@ -2767,27 +2730,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 +2820,10 @@ dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "ratelimit"
version = "1.0.0"
[[package]]
name = "rayon"
version = "1.5.3"
@@ -2924,9 +2870,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 +2896,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 +2973,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 +3033,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 +3095,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"
@@ -3220,10 +3156,10 @@ dependencies = [
]
[[package]]
name = "serde_path_to_error"
version = "0.1.8"
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "184c643044780f7ceb59104cef98a5a6f12cb2288a7bc701ab93a362b49fd47d"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
@@ -3544,9 +3480,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",
@@ -3647,13 +3583,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"
@@ -3861,7 +3831,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 +3896,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,15 +4226,15 @@ 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",
"async-mutex",
"async-trait",
"axum 0.5.17",
"axum",
"futures",
"futures-util",
"log",
@@ -4278,9 +4248,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.105.0"
version = "1.109.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,30 +26,30 @@ 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-channel = "1.8.0"
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.5", default-features = false, features = ["smtp-transport", "socks5", "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
async-smtp = { version = "0.8", default-features = false, features = ["runtime-tokio"] }
async_zip = { version = "0.0.9", default-features = false, features = ["deflate"] }
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"
fast-socks5 = "0.8"
futures = "0.3"
futures-lite = "1.12.0"
hex = "0.4.0"
humansize = "2"
image = { version = "0.24.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
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"
@@ -51,16 +57,16 @@ num-derive = "0.3"
num-traits = "0.2"
once_cell = "1.17.0"
percent-encoding = "2.2"
parking_lot = "0.12"
pgp = { version = "0.9", default-features = false }
pretty_env_logger = { version = "0.4", optional = true }
qrcodegen = "1.7.0"
quick-xml = "0.27"
r2d2 = "0.8"
r2d2_sqlite = "0.20"
rand = "0.8"
regex = "1.7"
rusqlite = { version = "0.27", features = ["sqlcipher"] }
reqwest = { version = "0.11.14", features = ["json"] }
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"] }
@@ -69,21 +75,17 @@ sha2 = "0.10"
smallvec = "1"
strum = "0.24"
strum_macros = "0.24"
thiserror = "1"
toml = "0.5"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
fast-socks5 = "0.8"
humansize = "2"
qrcodegen = "1.7.0"
tagger = "4.3.4"
textwrap = "0.16.0"
async-channel = "1.8.0"
futures-lite = "1.12.0"
tokio-stream = { version = "0.1.11", features = ["fs"] }
thiserror = "1"
tokio-io-timeout = "1.2.0"
reqwest = { version = "0.11.13", features = ["json"] }
async_zip = { version = "0.0.9", default-features = false, features = ["deflate"] }
tokio-stream = { version = "0.1.11", features = ["fs"] }
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
toml = "0.7"
trust-dns-resolver = "0.22"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
[dev-dependencies]
ansi_term = "0.12.0"
@@ -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

@@ -1,6 +1,7 @@
use std::path::PathBuf;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::accounts::Accounts;
use std::path::PathBuf;
use tempfile::tempdir;
async fn create_accounts(n: u32) {

View File

@@ -1,7 +1,6 @@
use std::path::Path;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::chat::{self, ChatId};
use deltachat::chatlist::Chatlist;
use deltachat::context::Context;
@@ -15,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());
}
}

View File

@@ -1,7 +1,6 @@
use std::path::Path;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::chatlist::Chatlist;
use deltachat::context::Context;
use deltachat::stock_str::StockStrings;

View File

@@ -1,8 +1,9 @@
use std::path::Path;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::context::Context;
use deltachat::stock_str::StockStrings;
use deltachat::Events;
use std::path::Path;
async fn search_benchmark(dbfile: impl AsRef<Path>) {
let id = 100;

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.105.0"
version = "1.109.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.
@@ -4327,6 +4327,18 @@ void dc_msg_set_text (dc_msg_t* msg, const char* text);
void dc_msg_set_html (dc_msg_t* msg, const char* html);
/**
* Sets the email's subject. If it's empty, a default subject
* will be used (e.g. `Message from Alice` or `Re: <last subject>`).
* This does not alter any information in the database.
*
* @memberof dc_msg_t
* @param msg The message object.
* @param subject The new subject.
*/
void dc_msg_set_subject (dc_msg_t* msg, const char* subject);
/**
* Set different sender name for a message.
* This overrides the name set by the dc_set_config()-option `displayname`.
@@ -5671,7 +5683,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.
@@ -5811,7 +5823,7 @@ void dc_event_unref(dc_event_t* event);
* @param data2 (int) The 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=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
* 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol.
* 1000=Protocol finished for this contact.
*/
#define DC_EVENT_SECUREJOIN_INVITER_PROGRESS 2060

View File

@@ -23,35 +23,35 @@ use std::sync::Arc;
use std::time::{Duration, SystemTime};
use anyhow::Context as _;
use deltachat::qr_code_generator::get_securejoin_qr_svg;
use num_traits::{FromPrimitive, ToPrimitive};
use once_cell::sync::Lazy;
use rand::Rng;
use tokio::runtime::Runtime;
use tokio::sync::RwLock;
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;
use deltachat::ephemeral::Timer as EphemeralTimer;
use deltachat::key::DcKey;
use deltachat::message::MsgId;
use deltachat::qr_code_generator::get_securejoin_qr_svg;
use deltachat::reaction::{get_msg_reactions, send_reaction, Reactions};
use deltachat::stock_str::StockMessage;
use deltachat::stock_str::StockStrings;
use deltachat::webxdc::StatusUpdateSerial;
use deltachat::*;
use deltachat::{accounts::Accounts, log::LogExt};
use num_traits::{FromPrimitive, ToPrimitive};
use once_cell::sync::Lazy;
use rand::Rng;
use tokio::runtime::Runtime;
use tokio::sync::RwLock;
use tokio::task::JoinHandle;
mod dc_array;
mod lot;
mod string;
use self::string::*;
use deltachat::chatlist::Chatlist;
use self::string::*;
// as C lacks a good and portable error handling,
// in general, the C Interface is forgiving wrt to bad parameters.
// - objects returned by some functions
@@ -60,7 +60,8 @@ use deltachat::chatlist::Chatlist;
// 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(
@@ -1993,12 +2003,10 @@ pub unsafe extern "C" fn dc_create_contact(
let ctx = &*context;
let name = to_string_lossy(name);
block_on(async move {
Contact::create(ctx, &name, &to_string_lossy(addr))
.await
.map(|id| id.to_u32())
.unwrap_or(0)
})
block_on(Contact::create(ctx, &name, &to_string_lossy(addr)))
.log_err(ctx, "Cannot create contact")
.map(|id| id.to_u32())
.unwrap_or(0)
}
#[no_mangle]
@@ -2185,7 +2193,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 +3081,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 +3264,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()
}
}
@@ -3588,6 +3596,16 @@ pub unsafe extern "C" fn dc_msg_set_html(msg: *mut dc_msg_t, html: *const libc::
ffi_msg.message.set_html(to_opt_string_lossy(html))
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_subject(msg: *mut dc_msg_t, subject: *const libc::c_char) {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_get_subject()");
return;
}
let ffi_msg = &mut *msg;
ffi_msg.message.set_subject(to_string_lossy(subject));
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_override_sender_name(
msg: *mut dc_msg_t,
@@ -3975,13 +3993,10 @@ pub unsafe extern "C" fn dc_contact_get_verifier_addr(
}
let ffi_contact = &*contact;
let ctx = &*ffi_contact.context;
block_on(Contact::get_verifier_addr(
ctx,
&ffi_contact.contact.get_id(),
))
.log_err(ctx, "failed to get verifier for contact")
.unwrap_or_default()
.strdup()
block_on(ffi_contact.contact.get_verifier_addr(ctx))
.log_err(ctx, "failed to get verifier for contact")
.unwrap_or_default()
.strdup()
}
#[no_mangle]
@@ -3992,12 +4007,12 @@ pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t)
}
let ffi_contact = &*contact;
let ctx = &*ffi_contact.context;
let contact_id = block_on(Contact::get_verifier_id(ctx, &ffi_contact.contact.get_id()))
let verifier_contact_id = block_on(ffi_contact.contact.get_verifier_id(ctx))
.log_err(ctx, "failed to get verifier")
.unwrap_or_default()
.unwrap_or_default();
contact_id.to_u32()
verifier_contact_id.to_u32()
}
// dc_lot_t
@@ -4312,7 +4327,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()
}
}
@@ -4380,8 +4395,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
}
@@ -4403,10 +4417,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
}
}
@@ -4427,10 +4438,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
}
}
@@ -4455,8 +4463,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
}
@@ -4486,8 +4493,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
}
@@ -4580,11 +4586,12 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
#[cfg(feature = "jsonrpc")]
mod jsonrpc {
use super::*;
use deltachat_jsonrpc::api::CommandApi;
use deltachat_jsonrpc::events::event_to_json_rpc_notification;
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcSession};
use super::*;
pub struct dc_jsonrpc_instance_t {
receiver: OutReceiver,
handle: RpcSession<CommandApi>,

View File

@@ -1,10 +1,12 @@
//! # Legacy generic return values for C API.
use std::borrow::Cow;
use anyhow::Error;
use crate::message::MessageState;
use crate::qr::Qr;
use crate::summary::{Summary, SummaryPrefix};
use anyhow::Error;
use std::borrow::Cow;
/// An object containing a set of values.
/// The meaning of the values is defined by the function returning the object.
@@ -20,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 {
@@ -149,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
@@ -213,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

@@ -287,9 +287,10 @@ fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
#[cfg(test)]
mod tests {
use super::*;
use libc::{free, strcmp};
use super::*;
#[test]
fn test_os_str_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.105.0"
version = "1.109.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.5.9", 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

@@ -1,8 +1,14 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use std::{collections::HashMap, str::FromStr};
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,
@@ -23,21 +29,14 @@ use deltachat::{
webxdc::StatusUpdateSerial,
};
use sanitize_filename::is_sanitized;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::{collections::HashMap, str::FromStr};
use tokio::{fs, sync::RwLock};
use walkdir::WalkDir;
use yerpc::rpc;
pub use deltachat::accounts::Accounts;
pub mod events;
pub mod types;
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
use crate::api::types::qr::QrObject;
use num_traits::FromPrimitive;
use types::account::Account;
use types::chat::FullChat;
use types::chat_list::ChatListEntry;
@@ -46,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,
@@ -53,8 +53,8 @@ use self::types::{
JSONRPCMessageListItem, MessageNotificationInfo, MessageSearchResult, MessageViewtype,
},
};
use num_traits::FromPrimitive;
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
use crate::api::types::qr::QrObject;
#[derive(Clone, Debug)]
pub struct CommandApi {
@@ -86,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
// ---------------------------------------------
@@ -136,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)
@@ -244,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(())
}
@@ -461,7 +466,7 @@ impl CommandApi {
Ok(res) => res,
Err(err) => ChatListItemFetchResult::Error {
id: entry.0,
error: format!("{:?}", err),
error: format!("{err:#}"),
},
},
);
@@ -805,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 {
@@ -880,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 {
@@ -898,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())
@@ -918,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)
@@ -1716,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?;
@@ -1738,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

@@ -20,6 +20,10 @@ pub struct ContactObject {
name_and_addr: String,
is_blocked: bool,
is_verified: bool,
/// the address that verified this contact
verifier_addr: Option<String>,
/// the id of the contact that verified this contact
verifier_id: Option<u32>,
/// the contact's last seen timestamp
last_seen: i64,
was_seen_recently: bool,
@@ -36,6 +40,18 @@ impl ContactObject {
};
let is_verified = contact.is_verified(context).await? == VerifiedStatus::BidirectVerified;
let (verifier_addr, verifier_id) = if is_verified {
(
contact.get_verifier_addr(context).await?,
contact
.get_verifier_id(context)
.await?
.map(|contact_id| contact_id.to_u32()),
)
} else {
(None, None)
};
Ok(ContactObject {
address: contact.get_addr().to_owned(),
color: color_int_to_hex_string(contact.get_color()),
@@ -48,6 +64,8 @@ impl ContactObject {
name_and_addr: contact.get_name_n_addr(),
is_blocked: contact.is_blocked(),
is_verified,
verifier_addr,
verifier_id,
last_seen: contact.last_seen(),
was_seen_recently: contact.was_seen_recently(),
})

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

@@ -4,12 +4,13 @@ pub use yerpc;
#[cfg(test)]
mod tests {
use super::api::{Accounts, CommandApi};
use async_channel::unbounded;
use futures::StreamExt;
use tempfile::TempDir;
use yerpc::{RpcClient, RpcSession};
use super::api::{Accounts, CommandApi};
#[tokio::test(flavor = "multi_thread")]
async fn basic_json_rpc_functionality() -> anyhow::Result<()> {
let tmp_dir = TempDir::new().unwrap().path().into();
@@ -36,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()));
}
{
@@ -44,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

@@ -1,6 +1,7 @@
use axum::{extract::ws::WebSocketUpgrade, response::Response, routing::get, Extension, Router};
use std::net::SocketAddr;
use std::path::PathBuf;
use axum::{extract::ws::WebSocketUpgrade, response::Response, routing::get, Extension, Router};
use yerpc::axum::handle_ws_rpc;
use yerpc::{RpcClient, RpcSession};

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

@@ -14,7 +14,7 @@
"c8": "^7.10.0",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"esbuild": "^0.14.11",
"esbuild": "^0.17.9",
"http-server": "^14.1.1",
"mocha": "^9.1.1",
"node-fetch": "^2.6.1",
@@ -24,13 +24,20 @@
"typescript": "^4.5.5",
"ws": "^8.5.0"
},
"exports": {
".": {
"require": "./dist/deltachat.cjs",
"import": "./dist/deltachat.js"
}
},
"license": "MPL-2.0",
"main": "dist/deltachat.js",
"name": "@deltachat/jsonrpc-client",
"scripts": {
"build": "run-s generate-bindings extract-constants build:tsc build:bundle",
"build": "run-s generate-bindings extract-constants build:tsc build:bundle build:cjs",
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
"build:tsc": "tsc",
"build:cjs": "esbuild --format=cjs --bundle --packages=external dist/deltachat.js --outfile=dist/deltachat.cjs",
"docs": "typedoc --out docs deltachat.ts",
"example": "run-s build example:build example:start",
"example:build": "esbuild --bundle dist/example/example.js --outfile=dist/example.bundle.js",
@@ -38,8 +45,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 +55,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.105.0"
}
"version": "1.109.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.109.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

@@ -27,9 +27,7 @@ async def log_error(event):
@hooks.on(events.MemberListChanged)
async def on_memberlist_changed(event):
logging.info(
"member %s was %s", event.member, "added" if event.member_added else "removed"
)
logging.info("member %s was %s", event.member, "added" if event.member_added else "removed")
@hooks.on(events.GroupImageChanged)
@@ -66,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"
@@ -27,3 +20,41 @@ deltachat_rpc_client = [
[project.entry-points.pytest11]
"deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin"
[tool.black]
line-length = 120
[tool.ruff]
select = [
"E", "W", # pycodestyle
"F", # Pyflakes
"N", # pep8-naming
"I", # isort
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"COM", # flake8-commas
"DTZ", # flake8-datetimez
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"PIE", # flake8-pie
"PT", # flake8-pytest-style
"RET", # flake8-return
"SIM", # flake8-simplify
"TCH", # flake8-type-checking
"TID", # flake8-tidy-imports
"YTT", # flake8-2020
"ERA", # eradicate
"PLC", # Pylint Convention
"PLE", # Pylint Error
"PLW", # Pylint Warning
"RUF006" # asyncio-dangling-task
]
line-length = 120
[tool.isort]
profile = "black"

View File

@@ -8,3 +8,18 @@ from .contact import Contact
from .deltachat import DeltaChat
from .message import Message
from .rpc import Rpc
__all__ = [
"Account",
"AttrDict",
"Bot",
"Chat",
"Client",
"Contact",
"DeltaChat",
"EventType",
"Message",
"Rpc",
"run_bot_cli",
"run_client_cli",
]

View File

@@ -30,12 +30,7 @@ class AttrDict(dict):
"""Dictionary that allows accessing values usin the "dot notation" as attributes."""
def __init__(self, *args, **kwargs) -> None:
super().__init__(
{
_camel_to_snake(key): _to_attrdict(value)
for key, value in dict(*args, **kwargs).items()
}
)
super().__init__({_camel_to_snake(key): _to_attrdict(value) for key, value in dict(*args, **kwargs).items()})
def __getattr__(self, attr):
if attr in self:
@@ -51,7 +46,7 @@ class AttrDict(dict):
async def run_client_cli(
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
argv: Optional[list] = None,
**kwargs
**kwargs,
) -> None:
"""Run a simple command line app, using the given hooks.
@@ -65,7 +60,7 @@ async def run_client_cli(
async def run_bot_cli(
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
argv: Optional[list] = None,
**kwargs
**kwargs,
) -> None:
"""Run a simple bot command line using the given hooks.
@@ -80,7 +75,7 @@ async def _run_cli(
client_type: Type["Client"],
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
argv: Optional[list] = None,
**kwargs
**kwargs,
) -> None:
from .deltachat import DeltaChat
from .rpc import Rpc
@@ -107,12 +102,10 @@ async def _run_cli(
client = client_type(account, hooks)
client.logger.debug("Running deltachat core %s", core_version)
if not await client.is_configured():
assert (
args.email and args.password
), "Account is not configured and email and password must be provided"
asyncio.create_task(
client.configure(email=args.email, password=args.password)
)
assert args.email, "Account is not configured and email must be provided"
assert args.password, "Account is not configured and password must be provided"
# 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,3 +1,4 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
from ._utils import AttrDict
@@ -5,34 +6,23 @@ from .chat import Chat
from .const import ChatlistFlag, ContactFlag, SpecialContactId
from .contact import Contact
from .message import Message
from .rpc import Rpc
if TYPE_CHECKING:
from .deltachat import DeltaChat
from .rpc import Rpc
@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:
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))
@@ -89,9 +79,7 @@ class Account:
"""Configure an account."""
await self._rpc.configure(self.id)
async def create_contact(
self, obj: Union[int, str, Contact], name: Optional[str] = None
) -> Contact:
async def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
"""Create a new Contact or return an existing one.
Calling this method will always result in the same
@@ -120,10 +108,7 @@ class Account:
async def get_blocked_contacts(self) -> List[AttrDict]:
"""Return a list with snapshots of all blocked contacts."""
contacts = await self._rpc.get_blocked_contacts(self.id)
return [
AttrDict(contact=Contact(self, contact["id"]), **contact)
for contact in contacts
]
return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts]
async def get_contacts(
self,
@@ -148,10 +133,7 @@ class Account:
if snapshot:
contacts = await self._rpc.get_contacts(self.id, flags, query)
return [
AttrDict(contact=Contact(self, contact["id"]), **contact)
for contact in contacts
]
return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts]
contacts = await self._rpc.get_contact_ids(self.id, flags, query)
return [Contact(self, contact_id) for contact_id in contacts]
@@ -192,9 +174,7 @@ class Account:
if alldone_hint:
flags |= ChatlistFlag.ADD_ALLDONE_HINT
entries = await self._rpc.get_chatlist_entries(
self.id, flags, query, contact and contact.id
)
entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id)
if not snapshot:
return [Chat(self, entry[0]) for entry in entries]

View File

@@ -1,39 +1,30 @@
import calendar
from datetime import datetime
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
from ._utils import AttrDict
from .const import ChatVisibility
from .contact import Contact
from .message import Message
from .rpc import Rpc
if TYPE_CHECKING:
from datetime import datetime
from .account import Account
from .rpc import Rpc
@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:
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.
@@ -63,7 +54,7 @@ class Chat:
"""
if duration is not None:
assert duration > 0, "Invalid duration"
dur: Union[str, dict] = dict(Until=duration)
dur: Union[str, dict] = {"Until": duration}
else:
dur = "Forever"
await self._rpc.set_chat_mute_duration(self.account.id, self.id, dur)
@@ -74,27 +65,19 @@ class Chat:
async def pin(self) -> None:
"""Pin this chat."""
await self._rpc.set_chat_visibility(
self.account.id, self.id, ChatVisibility.PINNED
)
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.PINNED)
async def unpin(self) -> None:
"""Unpin this chat."""
await self._rpc.set_chat_visibility(
self.account.id, self.id, ChatVisibility.NORMAL
)
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL)
async def archive(self) -> None:
"""Archive this chat."""
await self._rpc.set_chat_visibility(
self.account.id, self.id, ChatVisibility.ARCHIVED
)
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.ARCHIVED)
async def unarchive(self) -> None:
"""Unarchive this chat."""
await self._rpc.set_chat_visibility(
self.account.id, self.id, ChatVisibility.NORMAL
)
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL)
async def set_name(self, name: str) -> None:
"""Set name of this chat."""
@@ -133,9 +116,7 @@ class Chat:
if isinstance(quoted_msg, Message):
quoted_msg = quoted_msg.id
msg_id, _ = await self._rpc.misc_send_msg(
self.account.id, self.id, text, file, location, quoted_msg
)
msg_id, _ = await self._rpc.misc_send_msg(self.account.id, self.id, text, file, location, quoted_msg)
return Message(self.account, msg_id)
async def send_text(self, text: str) -> Message:
@@ -184,9 +165,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:
@@ -237,27 +218,21 @@ class Chat:
async def get_locations(
self,
contact: Optional[Contact] = None,
timestamp_from: Optional[datetime] = None,
timestamp_to: Optional[datetime] = None,
timestamp_from: Optional["datetime"] = None,
timestamp_to: Optional["datetime"] = None,
) -> List[AttrDict]:
"""Get list of location snapshots for the given contact in the given timespan."""
time_from = (
calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0
)
time_from = calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0
time_to = calendar.timegm(timestamp_to.utctimetuple()) if timestamp_to else 0
contact_id = contact.id if contact else 0
result = await self._rpc.get_locations(
self.account.id, self.id, contact_id, time_from, time_to
)
result = await self._rpc.get_locations(self.account.id, self.id, contact_id, time_from, time_to)
locations = []
contacts: Dict[int, Contact] = {}
for loc in result:
loc = AttrDict(loc)
loc["chat"] = self
loc["contact"] = contacts.setdefault(
loc.contact_id, Contact(self.account, loc.contact_id)
)
loc["contact"] = contacts.setdefault(loc.contact_id, Contact(self.account, loc.contact_id))
loc["message"] = Message(self.account, loc.msg_id)
locations.append(loc)
return locations

View File

@@ -2,6 +2,7 @@
import inspect
import logging
from typing import (
TYPE_CHECKING,
Callable,
Coroutine,
Dict,
@@ -13,8 +14,6 @@ from typing import (
Union,
)
from deltachat_rpc_client.account import Account
from ._utils import (
AttrDict,
parse_system_add_remove,
@@ -31,13 +30,16 @@ from .events import (
RawEvent,
)
if TYPE_CHECKING:
from deltachat_rpc_client.account import Account
class Client:
"""Simple Delta Chat client that listen to events of a single account."""
def __init__(
self,
account: Account,
account: "Account",
hooks: Optional[Iterable[Tuple[Callable, Union[type, EventFilter]]]] = None,
logger: Optional[logging.Logger] = None,
) -> None:
@@ -47,15 +49,11 @@ class Client:
self._should_process_messages = 0
self.add_hooks(hooks or [])
def add_hooks(
self, hooks: Iterable[Tuple[Callable, Union[type, EventFilter]]]
) -> None:
def add_hooks(self, hooks: Iterable[Tuple[Callable, Union[type, EventFilter]]]) -> None:
for hook, event in hooks:
self.add_hook(hook, event)
def add_hook(
self, hook: Callable, event: Union[type, EventFilter] = RawEvent
) -> None:
def add_hook(self, hook: Callable, event: Union[type, EventFilter] = RawEvent) -> None:
"""Register hook for the given event filter."""
if isinstance(event, type):
event = event()
@@ -64,7 +62,7 @@ class Client:
isinstance(
event,
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
)
),
)
self._hooks.setdefault(type(event), set()).add((hook, event))
@@ -76,7 +74,7 @@ class Client:
isinstance(
event,
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
)
),
)
self._hooks.get(type(event), set()).remove((hook, event))
@@ -95,9 +93,7 @@ class Client:
"""Process events forever."""
await self.run_until(lambda _: False)
async def run_until(
self, func: Callable[[AttrDict], Union[bool, Coroutine]]
) -> AttrDict:
async def run_until(self, func: Callable[[AttrDict], Union[bool, Coroutine]]) -> AttrDict:
"""Process events until the given callable evaluates to True.
The callable should accept an AttrDict object representing the
@@ -122,9 +118,7 @@ class Client:
if stop:
return event
async def _on_event(
self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent
) -> None:
async def _on_event(self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent) -> None:
for hook, evfilter in self._hooks.get(filter_type, []):
if await evfilter.filter(event):
try:
@@ -133,11 +127,7 @@ class Client:
self.logger.exception(ex)
async def _parse_command(self, event: AttrDict) -> None:
cmds = [
hook[1].command
for hook in self._hooks.get(NewMessage, [])
if hook[1].command
]
cmds = [hook[1].command for hook in self._hooks.get(NewMessage, []) if hook[1].command]
parts = event.message_snapshot.text.split(maxsplit=1)
payload = parts[1] if len(parts) > 1 else ""
cmd = parts.pop(0)
@@ -202,11 +192,7 @@ class Client:
for message in await self.account.get_fresh_messages_in_arrival_order():
snapshot = await message.get_snapshot()
await self._on_new_msg(snapshot)
if (
snapshot.is_info
and snapshot.system_message_type
!= SystemMessageType.WEBXDC_INFO_MESSAGE
):
if snapshot.is_info and snapshot.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE:
await self._handle_info_msg(snapshot)
await snapshot.message.mark_seen()

View File

@@ -1,13 +1,15 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING
from ._utils import AttrDict
from .rpc import Rpc
if TYPE_CHECKING:
from .account import Account
from .chat import Chat
from .rpc import Rpc
@dataclass
class Contact:
"""
Contact API.
@@ -15,23 +17,11 @@ 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:
def _rpc(self) -> "Rpc":
return self.account._rpc
async def block(self) -> None:

View File

@@ -1,8 +1,10 @@
from typing import Dict, List
from typing import TYPE_CHECKING, Dict, List
from ._utils import AttrDict
from .account import Account
from .rpc import Rpc
if TYPE_CHECKING:
from .rpc import Rpc
class DeltaChat:
@@ -11,7 +13,7 @@ class DeltaChat:
This is the root of the object oriented API.
"""
def __init__(self, rpc: Rpc) -> None:
def __init__(self, rpc: "Rpc") -> None:
self.rpc = rpc
async def add_account(self) -> Account:

View File

@@ -2,15 +2,17 @@
import inspect
import re
from abc import ABC, abstractmethod
from typing import Callable, Iterable, Iterator, Optional, Set, Tuple, Union
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Optional, Set, Tuple, Union
from ._utils import AttrDict
from .const import EventType
if TYPE_CHECKING:
from ._utils import AttrDict
def _tuple_of(obj, type_: type) -> tuple:
if not obj:
return tuple()
return ()
if isinstance(obj, type_):
obj = (obj,)
@@ -39,7 +41,7 @@ class EventFilter(ABC):
"""Return True if two event filters are equal."""
def __ne__(self, other):
return not self.__eq__(other)
return not self == other
async def _call_func(self, event) -> bool:
if not self.func:
@@ -65,9 +67,7 @@ class RawEvent(EventFilter):
should be dispatched or not.
"""
def __init__(
self, types: Union[None, EventType, Iterable[EventType]] = None, **kwargs
):
def __init__(self, types: Union[None, EventType, Iterable[EventType]] = None, **kwargs):
super().__init__(**kwargs)
try:
self.types = _tuple_of(types, EventType)
@@ -82,7 +82,7 @@ class RawEvent(EventFilter):
return (self.types, self.func) == (other.types, other.func)
return False
async def filter(self, event: AttrDict) -> bool:
async def filter(self, event: "AttrDict") -> bool:
if self.types and event.type not in self.types:
return False
return await self._call_func(event)
@@ -98,6 +98,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.
@@ -115,10 +118,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,
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")
@@ -135,19 +140,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:
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:
@@ -187,7 +201,7 @@ class MemberListChanged(EventFilter):
return (self.added, self.func) == (other.added, other.func)
return False
async def filter(self, event: AttrDict) -> bool:
async def filter(self, event: "AttrDict") -> bool:
if self.added is not None and self.added != event.member_added:
return False
return await self._call_func(event)
@@ -219,7 +233,7 @@ class GroupImageChanged(EventFilter):
return (self.deleted, self.func) == (other.deleted, other.func)
return False
async def filter(self, event: AttrDict) -> bool:
async def filter(self, event: "AttrDict") -> bool:
if self.deleted is not None and self.deleted != event.image_deleted:
return False
return await self._call_func(event)
@@ -244,7 +258,7 @@ class GroupNameChanged(EventFilter):
return self.func == other.func
return False
async def filter(self, event: AttrDict) -> bool:
async def filter(self, event: "AttrDict") -> bool:
return await self._call_func(event)

View File

@@ -1,34 +1,24 @@
import json
from dataclasses import dataclass
from typing import TYPE_CHECKING, Union
from ._utils import AttrDict
from .contact import Contact
from .rpc import Rpc
if TYPE_CHECKING:
from .account import Account
from .rpc import Rpc
@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:
def _rpc(self) -> "Rpc":
return self.account._rpc
async def send_reaction(self, *reaction: str):
@@ -49,22 +39,14 @@ class Message:
"""Mark the message as seen."""
await self._rpc.markseen_msgs(self.account.id, [self.id])
async def send_webxdc_status_update(
self, update: Union[dict, str], description: str
) -> None:
async def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None:
"""Send a webxdc status update. This message must be a webxdc."""
if not isinstance(update, str):
update = json.dumps(update)
await self._rpc.send_webxdc_status_update(
self.account.id, self.id, update, description
)
await self._rpc.send_webxdc_status_update(self.account.id, self.id, update, description)
async def get_webxdc_status_updates(self, last_known_serial: int = 0) -> list:
return json.loads(
await self._rpc.get_webxdc_status_updates(
self.account.id, self.id, last_known_serial
)
)
return json.loads(await self._rpc.get_webxdc_status_updates(self.account.id, self.id, last_known_serial))
async def get_webxdc_info(self) -> dict:
return await self._rpc.get_webxdc_info(self.account.id, self.id)

View File

@@ -1,3 +1,4 @@
import asyncio
import json
import os
from typing import AsyncGenerator, List, Optional
@@ -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,
@@ -67,9 +70,7 @@ class ACFactory:
) -> Message:
if not from_account:
from_account = (await self.get_online_accounts(1))[0]
to_contact = await from_account.create_contact(
await to_account.get_config("addr")
)
to_contact = await from_account.create_contact(await to_account.get_config("addr"))
if group:
to_chat = await from_account.create_group(group)
await to_chat.add_contact(to_contact)

View File

@@ -30,7 +30,7 @@ class Rpc:
"deltachat-rpc-server",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
**self._kwargs
**self._kwargs,
)
self.id = 0
self.event_queues = {}
@@ -46,7 +46,7 @@ class Rpc:
await self.start()
return self
async def __aexit__(self, exc_type, exc, tb):
async def __aexit__(self, _exc_type, _exc, _tb):
await self.close()
async def reader_loop(self) -> None:
@@ -97,5 +97,6 @@ class Rpc:
raise JsonRpcError(response["error"])
if "result" in response:
return response["result"]
return None
return method

View File

@@ -1,19 +1,30 @@
import asyncio
from unittest.mock import MagicMock
import pytest
from deltachat_rpc_client import EventType, events
from deltachat_rpc_client.rpc import JsonRpcError
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_system_info(rpc) -> None:
system_info = await rpc.get_system_info()
assert "arch" in system_info
assert "deltachat_core_version" in system_info
@pytest.mark.asyncio
@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 = [
"email@example.com",
@@ -27,7 +38,7 @@ async def test_email_address_validity(rpc) -> None:
assert not await rpc.check_email_validity(addr)
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_acfactory(acfactory) -> None:
account = await acfactory.new_configured_account()
while True:
@@ -41,17 +52,18 @@ async def test_acfactory(acfactory) -> None:
print("Successful configuration")
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_configure_starttls(acfactory) -> None:
account = await acfactory.new_preconfigured_account()
# Use STARTTLS
await account.set_config("mail_security", "2")
await account.set_config("send_security", "2")
await account.configure()
assert await account.is_configured()
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_account(acfactory) -> None:
alice, bob = await acfactory.get_online_accounts(2)
@@ -111,7 +123,7 @@ async def test_account(acfactory) -> None:
await alice.stop_io()
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_chat(acfactory) -> None:
alice, bob = await acfactory.get_online_accounts(2)
@@ -177,7 +189,7 @@ async def test_chat(acfactory) -> None:
await group.get_locations()
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_contact(acfactory) -> None:
alice, bob = await acfactory.get_online_accounts(2)
@@ -195,7 +207,7 @@ async def test_contact(acfactory) -> None:
await alice_contact_bob.create_chat()
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_message(acfactory) -> None:
alice, bob = await acfactory.get_online_accounts(2)
@@ -215,6 +227,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,44 +239,67 @@ async def test_message(acfactory) -> None:
await message.send_reaction("😎")
@pytest.mark.asyncio
@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!"
)
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)
track = lambda e: mock.hook(e.message_snapshot.id)
def track(e):
mock.hook(e.message_snapshot.id)
mock.hook.reset_mock()
hook = track, events.NewMessage(r"hello")
bot.add_hook(*hook)
bot.add_hook(track, events.NewMessage(command="/help"))
event = await acfactory.process_message(
from_account=user, to_client=bot, text="hello"
)
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello")
mock.hook.assert_called_with(event.msg_id)
event = await acfactory.process_message(
from_account=user, to_client=bot, text="hello!"
)
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)
mock.hook.reset_mock()
await acfactory.process_message(from_account=user, to_client=bot, text="hello")
event = await acfactory.process_message(
from_account=user, to_client=bot, text="/help"
)
event = await acfactory.process_message(from_account=user, to_client=bot, text="/help")
mock.hook.assert_called_once_with(event.msg_id)

View File

@@ -1,18 +1,15 @@
import pytest
from deltachat_rpc_client import EventType
@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_webxdc(acfactory) -> None:
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()
await alice_chat_bob.send_message(
text="Let's play chess!", file="../test-data/webxdc/chess.xdc"
)
await alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
while True:
event = await bob.wait_for_event()

View File

@@ -2,6 +2,7 @@
isolated_build = true
envlist =
py3
lint
[testenv]
commands =
@@ -16,3 +17,13 @@ deps =
pytest-asyncio
aiohttp
aiodns
[testenv:lint]
skipsdist = True
skip_install = True
deps =
ruff==0.0.247
black
commands =
black --check --diff src/ examples/ tests/
ruff src/ examples/ tests/

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.105.0"
version = "1.109.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

@@ -1,9 +1,10 @@
#![recursion_limit = "128"]
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use crate::proc_macro::TokenStream;
// For now, assume (not check) that these macroses are applied to enum without
// data. If this assumption is violated, compiler error will point to
// generated code, which is not very user-friendly.

View File

@@ -1,5 +1,3 @@
use tempfile::tempdir;
use deltachat::chat::{self, ChatId};
use deltachat::chatlist::*;
use deltachat::config;
@@ -8,6 +6,7 @@ use deltachat::context::*;
use deltachat::message::Message;
use deltachat::stock_str::StockStrings;
use deltachat::{EventType, Events};
use tempfile::tempdir;
fn cb(event: EventType) {
match event {
@@ -29,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();
@@ -75,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.105.0"
"version": "1.109.0"
}

View File

@@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
import sys
import os
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

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,24 +28,28 @@ 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):
print(
"ac_member_added {} to chat {} from {}".format(
contact.addr, chat.id, actor or message.get_sender_contact().addr
)
contact.addr,
chat.id,
actor or message.get_sender_contact().addr,
),
)
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):
print(
"ac_member_removed {} from chat {} by {}".format(
contact.addr, chat.id, actor or message.get_sender_contact().addr
)
contact.addr,
chat.id,
actor or message.get_sender_contact().addr,
),
)

View File

@@ -13,8 +13,8 @@ def datadir():
datadir = path.join("test-data")
if datadir.isdir():
return datadir
else:
pytest.skip("test-data directory not found")
pytest.skip("test-data directory not found")
return None
def test_echo_quit_plugin(acfactory, lp):
@@ -47,7 +47,7 @@ def test_group_tracking_plugin(acfactory, lp):
botproc.fnmatch_lines(
"""
*ac_configure_completed*
"""
""",
)
ac1.add_account_plugin(FFIEventLogger(ac1))
ac2.add_account_plugin(FFIEventLogger(ac2))
@@ -61,7 +61,7 @@ def test_group_tracking_plugin(acfactory, lp):
botproc.fnmatch_lines(
"""
*ac_chat_modified*bot test group*
"""
""",
)
lp.sec("adding third member {}".format(ac2.get_config("addr")))
@@ -76,8 +76,9 @@ def test_group_tracking_plugin(acfactory, lp):
"""
*ac_member_added {}*from*{}*
""".format(
contact3.addr, ac1.get_config("addr")
)
contact3.addr,
ac1.get_config("addr"),
),
)
lp.sec("contact successfully added, now removing")
@@ -86,6 +87,7 @@ def test_group_tracking_plugin(acfactory, lp):
"""
*ac_member_removed {}*from*{}*
""".format(
contact3.addr, ac1.get_config("addr")
)
contact3.addr,
ac1.get_config("addr"),
),
)

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>.*)?$'
@@ -44,5 +49,9 @@ git_describe_command = "git describe --dirty --tags --long --match py-*.*"
[tool.black]
line-length = 120
[tool.ruff]
select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP031", "UP032"]
line-length = 120
[tool.isort]
profile = "black"
profile = "black"

View File

@@ -36,7 +36,8 @@ register_global_plugin(events)
def run_cmdline(argv=None, account_plugins=None):
"""Run a simple default command line app, registering the specified
account plugins."""
account plugins.
"""
import argparse
if argv is None:
@@ -54,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

@@ -102,8 +102,8 @@ def find_header(flags):
printf("%s", _dc_header_file_location());
return 0;
}
"""
)
""",
),
)
cwd = os.getcwd()
try:
@@ -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():
@@ -198,7 +198,7 @@ def ffibuilder():
typedef int... time_t;
void free(void *ptr);
extern int dc_event_has_string_data(int);
"""
""",
)
function_defs = extract_functions(flags)
defines = extract_defines(flags)

View File

@@ -1,13 +1,12 @@
""" Account class implementation. """
"""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."""
@@ -39,7 +40,7 @@ def get_core_info():
ffi.gc(
lib.dc_context_new(as_dc_charpointer(""), as_dc_charpointer(path.name), ffi.NULL),
lib.dc_context_unref,
)
),
)
@@ -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
@@ -172,10 +178,7 @@ class Account(object):
namebytes = name.encode("utf8")
if isinstance(value, (int, bool)):
value = str(int(value))
if value is not None:
valuebytes = value.encode("utf8")
else:
valuebytes = ffi.NULL
valuebytes = value.encode("utf8") if value is not None else ffi.NULL
lib.dc_set_config(self._dc_context, namebytes, valuebytes)
def get_config(self, name: str) -> str:
@@ -189,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:
@@ -225,9 +228,10 @@ class Account(object):
return bool(lib.dc_is_configured(self._dc_context))
def is_open(self) -> bool:
"""Determine if account is open
"""Determine if account is open.
:returns True if account is open."""
:returns True if account is open.
"""
return bool(lib.dc_context_is_open(self._dc_context))
def set_avatar(self, img_path: Optional[str]) -> None:
@@ -298,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
@@ -370,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."""
@@ -415,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.
@@ -430,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)
@@ -543,11 +547,11 @@ class Account(object):
return from_dc_charpointer(res)
def check_qr(self, qr):
"""check qr code and return :class:`ScannedQRCode` instance representing the result"""
"""check qr code and return :class:`ScannedQRCode` instance representing the result."""
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):
@@ -598,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
@@ -618,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()
@@ -662,7 +666,7 @@ class Account(object):
return lib.dc_all_work_done(self._dc_context)
def start_io(self):
"""start this account's IO scheduling (Rust-core async scheduler)
"""start this account's IO scheduling (Rust-core async scheduler).
If this account is not configured an Exception is raised.
You need to call account.configure() and account.wait_configure_finish()
@@ -705,12 +709,10 @@ class Account(object):
"""
lib.dc_maybe_network(self._dc_context)
def configure(self, reconfigure: bool = False) -> ConfigureTracker:
def configure(self) -> ConfigureTracker:
"""Start configuration process and return a Configtracker instance
on which you can block with wait_finish() to get a True/False success
value for the configuration process.
:param reconfigure: deprecated, doesn't need to be checked anymore.
"""
if not self.get_config("addr") or not self.get_config("mail_pw"):
raise MissingCredentials("addr or mail_pwd not set in config")
@@ -733,7 +735,8 @@ class Account(object):
def shutdown(self) -> None:
"""shutdown and destroy account (stop callback thread, close and remove
underlying dc_context)."""
underlying dc_context).
"""
if self._dc_context is None:
return
@@ -748,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

@@ -1,4 +1,4 @@
""" Chat and Location related API. """
"""Chat and Location related API."""
import calendar
import json
@@ -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`.
@@ -37,10 +37,10 @@ class Chat(object):
return self.id == getattr(other, "id", None) and self.account._dc_context == other.account._dc_context
def __ne__(self, other) -> bool:
return not (self == other)
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):
@@ -74,19 +74,19 @@ class Chat(object):
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP
def is_single(self) -> bool:
"""Return True if this chat is a single/direct chat, False otherwise"""
"""Return True if this chat is a single/direct chat, False otherwise."""
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_SINGLE
def is_mailinglist(self) -> bool:
"""Return True if this chat is a mailing list, False otherwise"""
"""Return True if this chat is a mailing list, False otherwise."""
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_MAILINGLIST
def is_broadcast(self) -> bool:
"""Return True if this chat is a broadcast list, False otherwise"""
"""Return True if this chat is a broadcast list, False otherwise."""
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_BROADCAST
def is_multiuser(self) -> bool:
"""Return True if this chat is a multi-user chat (group, mailing list or broadcast), False otherwise"""
"""Return True if this chat is a multi-user chat (group, mailing list or broadcast), False otherwise."""
return lib.dc_chat_get_type(self._dc_chat) in (
const.DC_CHAT_TYPE_GROUP,
const.DC_CHAT_TYPE_MAILINGLIST,
@@ -94,11 +94,11 @@ class Chat(object):
)
def is_self_talk(self) -> bool:
"""Return True if this chat is the self-chat (a.k.a. "Saved Messages"), False otherwise"""
"""Return True if this chat is the self-chat (a.k.a. "Saved Messages"), False otherwise."""
return bool(lib.dc_chat_is_self_talk(self._dc_chat))
def is_device_talk(self) -> bool:
"""Returns True if this chat is the "Device Messages" chat, False otherwise"""
"""Returns True if this chat is the "Device Messages" chat, False otherwise."""
return bool(lib.dc_chat_is_device_talk(self._dc_chat))
def is_muted(self) -> bool:
@@ -109,12 +109,12 @@ class Chat(object):
return bool(lib.dc_chat_is_muted(self._dc_chat))
def is_pinned(self) -> bool:
"""Return True if this chat is pinned, False otherwise"""
"""Return True if this chat is pinned, False otherwise."""
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_PINNED
def is_archived(self) -> bool:
"""Return True if this chat is archived, False otherwise.
:returns: True if archived, False otherwise
:returns: True if archived, False otherwise.
"""
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED
@@ -136,7 +136,7 @@ class Chat(object):
def can_send(self) -> bool:
"""Check if messages can be sent to a give chat.
This is not true eg. for the contact requests or for the device-talk
This is not true eg. for the contact requests or for the device-talk.
:returns: True if the chat is writable, False otherwise
"""
@@ -167,7 +167,7 @@ class Chat(object):
def get_color(self):
"""return the color of the chat.
:returns: color as 0x00rrggbb
:returns: color as 0x00rrggbb.
"""
return lib.dc_chat_get_color(self._dc_chat)
@@ -178,21 +178,18 @@ class Chat(object):
return json.loads(s)
def mute(self, duration: Optional[int] = None) -> None:
"""mutes the chat
"""mutes the chat.
:param duration: Number of seconds to mute the chat for. None to mute until unmuted again.
:returns: None
"""
if duration is None:
mute_duration = -1
else:
mute_duration = duration
mute_duration = -1 if duration is None else duration
ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, mute_duration)
if not bool(ret):
raise ValueError("Call to dc_set_chat_mute_duration failed")
def unmute(self) -> None:
"""unmutes the chat
"""unmutes the chat.
:returns: None
"""
@@ -252,7 +249,8 @@ class Chat(object):
def get_encryption_info(self) -> Optional[str]:
"""Return encryption info for this chat.
:returns: a string with encryption preferences of all chat members"""
:returns: a string with encryption preferences of all chat members
"""
res = lib.dc_get_chat_encrinfo(self.account._dc_context, self.id)
return from_dc_charpointer(res)
@@ -284,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):
@@ -446,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):
@@ -459,11 +465,11 @@ 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.
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
:returns: list of :class:`deltachat.contact.Contact` objects for this chat.
"""
from .contact import Contact
@@ -495,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.
@@ -547,19 +553,10 @@ class Chat(object):
:param timespan_to: a datetime object or None (indicating up till now)
:returns: list of :class:`deltachat.chat.Location` objects.
"""
if timestamp_from is None:
time_from = 0
else:
time_from = calendar.timegm(timestamp_from.utctimetuple())
if timestamp_to is None:
time_to = 0
else:
time_to = calendar.timegm(timestamp_to.utctimetuple())
time_from = 0 if timestamp_from is None else calendar.timegm(timestamp_from.utctimetuple())
time_to = 0 if timestamp_to is None else calendar.timegm(timestamp_to.utctimetuple())
if contact is None:
contact_id = 0
else:
contact_id = contact.id
contact_id = 0 if contact is None else contact.id
dc_array = lib.dc_get_locations(self.account._dc_context, self.id, contact_id, time_from, time_to)
return [

View File

@@ -1,4 +1,4 @@
""" Contact object. """
"""Contact object."""
from datetime import date, datetime, timezone
from typing import Optional
@@ -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`.
@@ -28,10 +28,10 @@ class Contact(object):
return self.account._dc_context == other.account._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
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):
@@ -76,7 +76,7 @@ class Contact(object):
return lib.dc_contact_is_verified(self._dc_contact)
def get_verifier(self, contact):
"""Return the address of the contact that verified the contact"""
"""Return the address of the contact that verified the contact."""
return from_dc_charpointer(lib.dc_contact_get_verifier_addr(contact._dc_contact))
def get_profile_image(self) -> Optional[str]:

View File

@@ -79,15 +79,17 @@ class DirectImap:
def select_config_folder(self, config_name: str):
"""Return info about selected folder if it is
configured, otherwise None."""
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)
return None
def list_folders(self) -> List[str]:
"""return list of all existing folder names"""
"""return list of all existing folder names."""
assert not self._idling
return [folder.name for folder in self.conn.folder.list()]
@@ -103,7 +105,7 @@ class DirectImap:
def get_all_messages(self) -> List[MailMessage]:
assert not self._idling
return [mail for mail in self.conn.fetch()]
return list(self.conn.fetch())
def get_unread_messages(self) -> List[str]:
assert not self._idling
@@ -201,18 +203,18 @@ 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:
while 1:
while True:
for item in self.check(timeout=timeout):
if b"EXISTS" in item or b"RECENT" in item:
return item
def wait_for_seen(self, timeout=None) -> int:
"""Return first message with SEEN flag from a running idle-stream."""
while 1:
while True:
for item in self.check(timeout=timeout):
if FETCH in item:
self.log(str(item))
@@ -221,5 +223,4 @@ class IdleManager:
def done(self):
"""send idle-done to server if we are currently in idle mode."""
res = self.direct_imap.conn.idle.stop()
return res
return self.direct_imap.conn.idle.stop()

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={}):
@@ -30,6 +31,12 @@ class FFIEvent:
self.data2 = data2
def __str__(self):
if self.name == "DC_EVENT_INFO":
return f"INFO {self.data2}"
if self.name == "DC_EVENT_WARNING":
return f"WARNING {self.data2}"
if self.name == "DC_EVENT_ERROR":
return f"ERROR {self.data2}"
return "{name} data1={data1} data2={data2}".format(**self.__dict__)
@@ -62,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"
@@ -97,29 +104,29 @@ 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):
while 1:
while True:
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
def get_info_contains(self, regex: str) -> FFIEvent:
rex = re.compile(regex)
while 1:
while True:
ev = self.get_matching("DC_EVENT_INFO")
if rex.search(ev.data2):
return ev
def get_info_regex_groups(self, regex, check_error=True):
rex = re.compile(regex)
while 1:
while True:
ev = self.get_matching("DC_EVENT_INFO", check_error=check_error)
m = rex.match(ev.data2)
if m is not None:
@@ -128,46 +135,48 @@ class FFIEventTracker:
def wait_for_connectivity(self, connectivity):
"""Wait for the specified connectivity.
This only works reliably if the connectivity doesn't change
again too quickly, otherwise we might miss it."""
while 1:
again too quickly, otherwise we might miss it.
"""
while True:
if self.account.get_connectivity() == connectivity:
return
self.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
def wait_for_connectivity_change(self, previous, expected_next):
"""Wait until the connectivity changes to `expected_next`.
Fails the test if it changes to something else."""
while 1:
Fails the test if it changes to something else.
"""
while True:
current = self.account.get_connectivity()
if current == expected_next:
return
elif current != previous:
if current != previous:
raise Exception("Expected connectivity " + str(expected_next) + " but got " + str(current))
self.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
def wait_for_all_work_done(self):
while 1:
while True:
if self.account.all_work_done():
return
self.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
def ensure_event_not_queued(self, event_name_regex):
__tracebackhide__ = True
rex = re.compile("(?:{}).*".format(event_name_regex))
while 1:
rex = re.compile(f"(?:{event_name_regex}).*")
while True:
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:
while True:
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):
@@ -176,7 +185,8 @@ class FFIEventTracker:
- ac1 and ac2 are created
- ac1 sends a message to ac2
- ac2 is still running FetchExsistingMsgs job and thinks it's an existing, old message
- therefore no DC_EVENT_INCOMING_MSG is sent"""
- therefore no DC_EVENT_INCOMING_MSG is sent
"""
self.get_info_contains("INBOX: Idle entering")
def wait_next_incoming_message(self):
@@ -186,14 +196,15 @@ class FFIEventTracker:
def wait_next_messages_changed(self):
"""wait for and return next message-changed message or None
if the event contains no msgid"""
if the event contains no msgid
"""
ev = self.get_matching("DC_EVENT_MSGS_CHANGED")
if ev.data2 > 0:
return self.account.get_message_by_id(ev.data2)
return None
def wait_next_reactions_changed(self):
"""wait for and return next reactions-changed message"""
"""wait for and return next reactions-changed message."""
ev = self.get_matching("DC_EVENT_REACTIONS_CHANGED")
assert ev.data1 > 0
return self.account.get_message_by_id(ev.data2)
@@ -211,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
@@ -237,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):
@@ -275,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
@@ -285,30 +297,32 @@ class EventThread(threading.Thread):
if data1 == 0 or data1 == 1000:
success = data1 == 1000
comment = ffi_event.data2
yield "ac_configure_completed", dict(success=success, comment=comment)
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", dict(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", dict(message=msg)
elif msg.is_in_fresh():
yield map_system_message(msg) or (
"ac_incoming_message",
dict(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)
yield "ac_reactions_changed", dict(message=msg)
yield "ac_reactions_changed", {"message": msg}
elif name == "DC_EVENT_MSG_DELIVERED":
msg = account.get_message_by_id(ffi_event.data2)
yield "ac_message_delivered", dict(message=msg)
yield "ac_message_delivered", {"message": msg}
elif name == "DC_EVENT_CHAT_MODIFIED":
chat = account.get_chat_by_id(ffi_event.data1)
yield "ac_chat_modified", dict(chat=chat)
yield "ac_chat_modified", {"chat": chat}

View File

@@ -1,4 +1,4 @@
""" Hooks for Python bindings to Delta Chat Core Rust CFFI"""
"""Hooks for Python bindings to Delta Chat Core Rust CFFI."""
import pluggy

View File

@@ -1,4 +1,4 @@
""" The Message object. """
"""The Message object."""
import json
import os
@@ -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):
@@ -59,10 +59,7 @@ class Message(object):
:param view_type: the message type code or one of the strings:
"text", "audio", "video", "file", "sticker", "videochat", "webxdc"
"""
if isinstance(view_type, int):
view_type_code = view_type
else:
view_type_code = get_viewtype_code_from_name(view_type)
view_type_code = view_type if isinstance(view_type, int) else get_viewtype_code_from_name(view_type)
return Message(
account,
ffi.gc(lib.dc_msg_new(account._dc_context, view_type_code), lib.dc_msg_unref),
@@ -118,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
@@ -129,7 +126,7 @@ class Message(object):
@props.with_doc
def filemime(self) -> str:
"""mime type of the file (if it exists)"""
"""mime type of the file (if it exists)."""
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
def get_status_updates(self, serial: int = 0) -> list:
@@ -141,7 +138,7 @@ class Message(object):
:param serial: The last known serial. Pass 0 if there are no known serials to receive all updates.
"""
return json.loads(
from_dc_charpointer(lib.dc_get_webxdc_status_updates(self.account._dc_context, self.id, serial))
from_dc_charpointer(lib.dc_get_webxdc_status_updates(self.account._dc_context, self.id, serial)),
)
def send_status_update(self, json_data: Union[str, dict], description: str) -> bool:
@@ -158,8 +155,11 @@ class Message(object):
json_data = json.dumps(json_data, default=str)
return bool(
lib.dc_send_webxdc_status_update(
self.account._dc_context, self.id, as_dc_charpointer(json_data), as_dc_charpointer(description)
)
self.account._dc_context,
self.id,
as_dc_charpointer(json_data),
as_dc_charpointer(description),
),
)
def send_reaction(self, reaction: str):
@@ -232,16 +232,18 @@ class Message(object):
ts = lib.dc_msg_get_received_timestamp(self._dc_msg)
if ts:
return datetime.fromtimestamp(ts, timezone.utc)
return None
@props.with_doc
def ephemeral_timer(self):
"""Ephemeral timer in seconds
"""Ephemeral timer in seconds.
:returns: timer in seconds or None if there is no timer
"""
timer = lib.dc_msg_get_ephemeral_timer(self._dc_msg)
if timer:
return timer
return None
@props.with_doc
def ephemeral_timestamp(self):
@@ -255,23 +257,25 @@ class Message(object):
@property
def quoted_text(self) -> Optional[str]:
"""Text inside the quote
"""Text inside the quote.
:returns: Quoted text"""
:returns: Quoted text
"""
return from_optional_dc_charpointer(lib.dc_msg_get_quoted_text(self._dc_msg))
@property
def quote(self):
"""Quote getter
"""Quote getter.
:returns: Quoted message, if found in the database"""
:returns: Quoted message, if found in the database
"""
msg = lib.dc_msg_get_quoted_msg(self._dc_msg)
if msg:
return Message(self.account, ffi.gc(msg, lib.dc_msg_unref))
@quote.setter
def quote(self, quoted_message):
"""Quote setter"""
"""Quote setter."""
lib.dc_msg_set_quote(self._dc_msg, quoted_message._dc_msg)
def force_plaintext(self) -> None:
@@ -286,7 +290,7 @@ class Message(object):
:returns: email-mime message object (with headers only, no body).
"""
import email.parser
import email
mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id)
if mime_headers:
@@ -297,7 +301,7 @@ class Message(object):
@property
def error(self) -> Optional[str]:
"""Error message"""
"""Error message."""
return from_optional_dc_charpointer(lib.dc_msg_get_error(self._dc_msg))
@property
@@ -493,7 +497,8 @@ def get_viewtype_code_from_name(view_type_name):
if code is not None:
return code
raise ValueError(
"message typecode not found for {!r}, " "available {!r}".format(view_type_name, list(_view_type_mapping.keys()))
"message typecode not found for {!r}, "
"available {!r}".format(view_type_name, list(_view_type_mapping.keys())),
)
@@ -506,14 +511,11 @@ def map_system_message(msg):
if msg.is_system_message():
res = parse_system_add_remove(msg.text)
if not res:
return
return None
action, affected, actor = res
affected = msg.account.get_contact_by_addr(affected)
if actor == "me":
actor = None
else:
actor = msg.account.get_contact_by_addr(actor)
d = dict(chat=msg.chat, contact=affected, actor=actor, message=msg)
actor = None if actor == "me" else msg.account.get_contact_by_addr(actor)
d = {"chat": msg.chat, "contact": affected, "actor": actor, "message": msg}
return "ac_member_" + res[0], d
@@ -528,8 +530,8 @@ def extract_addr(text):
def parse_system_add_remove(text):
"""return add/remove info from parsing the given system message text.
returns a (action, affected, actor) triple"""
returns a (action, affected, actor) triple
"""
# You removed member a@b.
# You added member a@b.
# Member Me (x@y) removed by a@b.

View File

@@ -8,7 +8,7 @@ def with_doc(f):
# copied over unmodified from
# https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py
def cached(f):
"""returns a cached property that is calculated by function f"""
"""returns a cached property that is calculated by function f."""
def get(self):
try:
@@ -17,8 +17,9 @@ def cached(f):
self._property_cache = {}
except KeyError:
pass
x = self._property_cache[f] = f(self)
return x
res = f(self)
self._property_cache[f] = res
return res
def set(self, val):
propcache = self.__dict__.setdefault("_property_cache", {})

View File

@@ -8,8 +8,9 @@ class ProviderNotFoundError(Exception):
"""The provider information was not found."""
class Provider(object):
"""Provider information.
class Provider:
"""
Provider information.
:param domain: The email to get the provider info for.
"""

View File

View File

@@ -1,10 +1,10 @@
""" The Reactions object. """
"""The Reactions object."""
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
@@ -29,7 +27,7 @@ def pytest_addoption(parser):
"--liveconfig",
action="store",
default=None,
help="a file with >=2 lines where each line " "contains NAME=VALUE config settings for one account",
help="a file with >=2 lines where each line contains NAME=VALUE config settings for one account",
)
group.addoption(
"--ignored",
@@ -124,16 +122,16 @@ def pytest_report_header(config, startdir):
info["deltachat_core_version"],
info["sqlite_version"],
info["journal_mode"],
)
),
]
cfg = config.option.liveconfig
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 = dict(addr=d["email"], mail_pw=d["password"])
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)
@@ -229,7 +227,7 @@ def write_dict_to_dir(dic, target_dir):
path.write_bytes(content)
@pytest.fixture
@pytest.fixture()
def data(request):
class Data:
def __init__(self) -> None:
@@ -252,7 +250,8 @@ 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"):
fn = self.get_path(bn)
@@ -264,8 +263,11 @@ def data(request):
class ACSetup:
"""accounts setup helper to deal with multiple configure-process
and io & imap initialization phases. From tests, use the higher level
"""
Accounts setup helper to deal with multiple configure-process
and io & imap initialization phases.
From tests, use the higher level
public ACFactory methods instead of its private helper class.
"""
@@ -273,6 +275,8 @@ class ACSetup:
CONFIGURED = "CONFIGURED"
IDLEREADY = "IDLEREADY"
_configured_events: Queue
def __init__(self, testprocess, init_time):
self._configured_events = Queue()
self._account2state = {}
@@ -281,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."""
@@ -289,7 +293,7 @@ class ACSetup:
self._account2state[account] = self.CONFIGURED
self.log("added already configured account", account, account.get_config("addr"))
def start_configure(self, account, reconfigure=False):
def start_configure(self, account):
"""add an account and start its configure process."""
class PendingTracker:
@@ -299,13 +303,13 @@ class ACSetup:
account.add_account_plugin(PendingTracker(), name="pending_tracker")
self._account2state[account] = self.CONFIGURING
account.configure(reconfigure=reconfigure)
account.configure()
self.log("started configure on", account)
def wait_one_configured(self, account):
"""wait until this account has successfully configured."""
if self._account2state[account] == self.CONFIGURING:
while 1:
while True:
acc = self._pop_config_success()
if acc == account:
break
@@ -335,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
@@ -369,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()
@@ -393,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:
@@ -411,7 +420,8 @@ class ACFactory:
acc.disable_logging()
def get_next_liveconfig(self):
"""Base function to get functional online configurations
"""
Base function to get functional online configurations
where we can make valid SMTP and IMAP connections with.
"""
configdict = next(self._liveconfig_producer).copy()
@@ -426,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:
@@ -447,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):
@@ -460,13 +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
else:
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
@@ -474,32 +484,32 @@ class ACFactory:
if passphrase:
ac.open(passphrase)
acname = ac._logid
addr = "{}@offline.org".format(acname)
addr = f"{acname}@offline.org"
ac.update_config(
dict(
addr=addr,
displayname=acname,
mail_pw="123",
configured_addr=addr,
configured_mail_pw="123",
configured="1",
)
{
"addr": addr,
"displayname": acname,
"mail_pw": "123",
"configured_addr": addr,
"configured_mail_pw": "123",
"configured": "1",
},
)
self._preconfigure_key(ac, addr)
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:
# XXX we might want to transfer the key to the new account
configdict = dict(
addr=cloned_from.get_config("addr"),
mail_pw=cloned_from.get_config("mail_pw"),
imap_certificate_checks=cloned_from.get_config("imap_certificate_checks"),
smtp_certificate_checks=cloned_from.get_config("smtp_certificate_checks"),
)
configdict = {
"addr": cloned_from.get_config("addr"),
"mail_pw": cloned_from.get_config("mail_pw"),
"imap_certificate_checks": cloned_from.get_config("imap_certificate_checks"),
"smtp_certificate_checks": cloned_from.get_config("smtp_certificate_checks"),
}
configdict.update(kwargs)
ac = self._get_cached_account(addr=configdict["addr"]) if cache else None
if ac is not None:
@@ -511,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)
@@ -521,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")
@@ -600,7 +610,7 @@ class ACFactory:
acc._evtracker.wait_next_incoming_message()
@pytest.fixture
@pytest.fixture()
def acfactory(request, tmpdir, testprocess, data):
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testprocess, data=data)
yield am
@@ -628,7 +638,7 @@ class BotProcess:
def _run_stdout_thread(self) -> None:
try:
while 1:
while True:
line = self.popen.stdout.readline()
if not line:
break
@@ -649,7 +659,7 @@ class BotProcess:
for next_pattern in patterns:
print("+++FNMATCH:", next_pattern)
ignored = []
while 1:
while True:
line = self.stdout_queue.get()
if line is None:
if ignored:
@@ -665,12 +675,12 @@ class BotProcess:
ignored.append(line)
@pytest.fixture
@pytest.fixture()
def tmp_db_path(tmpdir):
return tmpdir.join("test.db").strpath
@pytest.fixture
@pytest.fixture()
def lp():
class Printer:
def sec(self, msg: str) -> None:

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
@@ -77,21 +77,22 @@ class ConfigureTracker:
self.account.remove_account_plugin(self)
def wait_smtp_connected(self):
"""wait until smtp is configured."""
"""Wait until SMTP is configured."""
self._smtp_finished.wait()
def wait_imap_connected(self):
"""wait until smtp is configured."""
"""Wait until IMAP is configured."""
self._imap_finished.wait()
def wait_progress(self, data1=None):
while 1:
while True:
evdata = self._progress.get()
if data1 is None or evdata == data1:
break
def wait_finish(self, timeout=None):
"""wait until configure is completed.
"""
Wait until configure is completed.
Raise Exception if Configure failed
"""

View File

@@ -15,5 +15,5 @@ if __name__ == "__main__":
p,
"-w",
workspacedir,
]
],
)

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

@@ -216,7 +216,7 @@ def test_fetch_existing(acfactory, lp, mvbox_move):
# would also find the "Sent" folder, but it would be too late:
# The sentbox thread, started by `start_io()`, would have seen that there is no
# ConfiguredSentboxFolder and do nothing.
acfactory._acsetup.start_configure(ac1, reconfigure=True)
acfactory._acsetup.start_configure(ac1)
acfactory.bring_accounts_online()
assert_folders_configured(ac1)
@@ -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())
@@ -492,3 +492,99 @@ def test_multidevice_sync_seen(acfactory, lp):
assert ac1_clone_message.is_in_seen
# Test that the timer is started on the second device after synchronizing the seen status.
assert "Expires: " in ac1_clone_message.get_message_info()
def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
"""The test for the bug #3836:
- Alice has two devices, the second is offline.
- Alice creates a verified group and sends a QR invitation to Bob.
- Bob joins the group and sends a message there. Alice sees it.
- Alice's second devices goes online, but doesn't see Bob in the group.
"""
ac1, ac2 = acfactory.get_online_accounts(2)
ac2_addr = ac2.get_config("addr")
ac1_offl = acfactory.new_online_configuring_account(cloned_from=ac1)
for ac in [ac1, ac1_offl]:
ac.set_config("bcc_self", "1")
acfactory.bring_accounts_online()
dir = tmpdir.mkdir("exportdir")
ac1.export_self_keys(dir.strpath)
ac1_offl.import_self_keys(dir.strpath)
ac1_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")
chat2 = ac2.qr_join_chat(qr)
ac1._evtracker.wait_securejoin_inviter_progress(1000)
lp.sec("ac2: sending message")
# Message can be sent only after a receipt of "vg-member-added" message. Just wait for
# "Member Me (<addr>) added by <addr>." message.
ac2._evtracker.wait_next_incoming_message()
msg_out = chat2.send_text("hello")
lp.sec("ac1: receiving message")
msg_in = ac1._evtracker.wait_next_incoming_message()
assert msg_in.text == msg_out.text
assert msg_in.get_sender_contact().addr == ac2_addr
lp.sec("ac1_offl: going online, receiving message")
ac1_offl.start_io()
ac1_offl._evtracker.wait_securejoin_inviter_progress(1000)
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

@@ -32,7 +32,7 @@ def test_basic_imap_api(acfactory, tmpdir):
imap2.shutdown()
@pytest.mark.ignored
@pytest.mark.ignored()
def test_configure_generate_key(acfactory, lp):
# A slow test which will generate new keys.
acfactory.remove_preconfigured_keys()
@@ -88,7 +88,7 @@ def test_export_import_self_keys(acfactory, tmpdir, lp):
lp.indent(dir.strpath + os.sep + name)
lp.sec("importing into existing account")
ac2.import_self_keys(dir.strpath)
(key_id2,) = ac2._evtracker.get_info_regex_groups(r".*stored.*KeyId\((.*)\).*", check_error=False)
(key_id2,) = ac2._evtracker.get_info_regex_groups(r".*stored.*KeyId\((.*)\).*")
assert key_id2 == key_id
@@ -510,7 +510,7 @@ def test_send_and_receive_message_markseen(acfactory, lp):
idle2.wait_for_seen()
lp.step("1")
for i in range(2):
for _i in range(2):
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL
@@ -529,7 +529,7 @@ def test_send_and_receive_message_markseen(acfactory, lp):
pass # mark_seen_messages() has generated events before it returns
def test_moved_markseen(acfactory, lp):
def test_moved_markseen(acfactory):
"""Test that message already moved to DeltaChat folder is marked as seen."""
ac1 = acfactory.new_online_configuring_account()
ac2 = acfactory.new_online_configuring_account(mvbox_move=True)
@@ -553,7 +553,7 @@ def test_moved_markseen(acfactory, lp):
ac2.mark_seen_messages([msg])
uid = idle2.wait_for_seen()
assert len([a for a in ac2.direct_imap.conn.fetch(AND(seen=True, uid=U(uid, "*")))]) == 1
assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True, uid=U(uid, "*"))))) == 1
def test_message_override_sender_name(acfactory, lp):
@@ -832,7 +832,7 @@ def test_send_first_message_as_long_unicode_with_cr(acfactory, lp):
lp.sec("sending multi-line non-unicode message from ac1 to ac2")
text1 = (
"hello\nworld\nthis is a very long message that should be"
+ " wrapped using format=flowed and unwrapped on the receiver"
" wrapped using format=flowed and unwrapped on the receiver"
)
msg_out = chat.send_text(text1)
assert not msg_out.is_encrypted()
@@ -894,7 +894,7 @@ def test_dont_show_emails(acfactory, lp):
message in Drafts that is moved to Sent later
""".format(
ac1.get_config("configured_addr")
ac1.get_config("configured_addr"),
),
)
ac1.direct_imap.append(
@@ -908,7 +908,7 @@ def test_dont_show_emails(acfactory, lp):
message in Sent
""".format(
ac1.get_config("configured_addr")
ac1.get_config("configured_addr"),
),
)
ac1.direct_imap.append(
@@ -922,7 +922,7 @@ def test_dont_show_emails(acfactory, lp):
Unknown message in Spam
""".format(
ac1.get_config("configured_addr")
ac1.get_config("configured_addr"),
),
)
ac1.direct_imap.append(
@@ -936,7 +936,21 @@ def test_dont_show_emails(acfactory, lp):
Unknown & malformed message in Spam
""".format(
ac1.get_config("configured_addr")
ac1.get_config("configured_addr"),
),
)
ac1.direct_imap.append(
"Spam",
"""
From: delta<address: inbox@nhroy.com>
Subject: subj
To: {}
Message-ID: <spam.message99@junk.org>
Content-Type: text/plain; charset=utf-8
Unknown & malformed message in Spam
""".format(
ac1.get_config("configured_addr"),
),
)
ac1.direct_imap.append(
@@ -950,7 +964,7 @@ def test_dont_show_emails(acfactory, lp):
Actually interesting message in Spam
""".format(
ac1.get_config("configured_addr")
ac1.get_config("configured_addr"),
),
)
ac1.direct_imap.append(
@@ -964,7 +978,7 @@ def test_dont_show_emails(acfactory, lp):
Unknown message in Junk
""".format(
ac1.get_config("configured_addr")
ac1.get_config("configured_addr"),
),
)
@@ -1235,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():
@@ -1423,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)
@@ -1461,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)
@@ -1696,7 +1709,7 @@ def test_system_group_msg_from_blocked_user(acfactory, lp):
assert contact.is_blocked()
chat_on_ac2.remove_contact(ac1)
ac1._evtracker.get_matching("DC_EVENT_CHAT_MODIFIED")
assert not ac1.get_self_contact() in chat_on_ac1.get_contacts()
assert ac1.get_self_contact() not in chat_on_ac1.get_contacts()
def test_set_get_group_image(acfactory, data, lp):
@@ -1770,7 +1783,7 @@ def test_connectivity(acfactory, lp):
lp.sec(
"Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, "
+ "all messages are fetched"
"all messages are fetched",
)
ac1.direct_imap.select_config_folder("inbox")
@@ -1941,6 +1954,7 @@ def test_immediate_autodelete(acfactory, lp):
assert msg.text == "hello"
lp.sec("ac2: wait for close/expunge on autodelete")
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evtracker.get_info_contains("close/expunge succeeded")
lp.sec("ac2: check that message was autodeleted on server")
@@ -1982,6 +1996,34 @@ def test_delete_multiple_messages(acfactory, lp):
assert len(ac2.direct_imap.get_all_messages()) == 1
def test_trash_multiple_messages(acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
ac2.set_config("delete_to_trash", "1")
chat12 = acfactory.get_accepted_chat(ac1, ac2)
lp.sec("ac1: sending 3 messages")
texts = ["first", "second", "third"]
for text in texts:
chat12.send_text(text)
lp.sec("ac2: waiting for all messages on the other side")
to_delete = []
for text in texts:
msg = ac2._evtracker.wait_next_incoming_message()
assert msg.text in texts
if text != "second":
to_delete.append(msg)
lp.sec("ac2: deleting all messages except second")
assert len(to_delete) == len(texts) - 1
ac2.delete_messages(to_delete)
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
lp.sec("ac2: test that only one message is left")
ac2.direct_imap.select_config_folder("inbox")
assert len(ac2.direct_imap.get_all_messages()) == 1
def test_configure_error_msgs_wrong_pw(acfactory):
configdict = acfactory.get_next_liveconfig()
ac1 = acfactory.get_unconfigured_account()
@@ -2132,7 +2174,7 @@ def test_group_quote(acfactory, lp):
@pytest.mark.parametrize(
"folder,move,expected_destination,",
("folder", "move", "expected_destination"),
[
(
"xyz",
@@ -2247,11 +2289,44 @@ def test_aeap_flow_verified(acfactory, lp):
assert ac1new.get_config("addr") in [contact.addr for contact in msg_in_2.chat.get_contacts()]
def test_archived_muted_chat(acfactory, lp):
"""If an archived and muted chat receives a new message, DC_EVENT_MSGS_CHANGED for
DC_CHAT_ID_ARCHIVED_LINK must be generated if the chat had only seen messages previously.
"""
ac1, ac2 = acfactory.get_online_accounts(2)
chat = acfactory.get_accepted_chat(ac1, ac2)
lp.sec("ac1: send message to ac2")
chat.send_text("message0")
lp.sec("wait for ac2 to receive message")
msg2 = ac2._evtracker.wait_next_incoming_message()
assert msg2.text == "message0"
msg2.mark_seen()
chat2 = msg2.chat
chat2.archive()
chat2.mute()
lp.sec("ac1: send another message to ac2")
chat.send_text("message1")
lp.sec("wait for ac2 to receive DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK")
while 1:
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
if ev.data1 == const.DC_CHAT_ID_ARCHIVED_LINK:
assert ev.data2 == 0
archive = ac2.get_chat_by_id(const.DC_CHAT_ID_ARCHIVED_LINK)
assert archive.count_fresh_messages() == 1
assert chat2.count_fresh_messages() == 1
break
class TestOnlineConfigureFails:
def test_invalid_password(self, acfactory):
configdict = acfactory.get_next_liveconfig()
ac1 = acfactory.get_unconfigured_account()
ac1.update_config(dict(addr=configdict["addr"], mail_pw="123"))
ac1.update_config({"addr": configdict["addr"], "mail_pw": "123"})
configtracker = ac1.configure()
configtracker.wait_progress(500)
configtracker.wait_progress(0)

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
@@ -15,7 +13,7 @@ from deltachat.tracker import ImexFailed
@pytest.mark.parametrize(
"msgtext,res",
("msgtext", "res"),
[
(
"Member Me (tmp1@x.org) removed by tmp2@x.org.",
@@ -108,7 +106,7 @@ class TestOfflineAccountBasic:
def test_update_config(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
ac1.update_config(dict(mvbox_move=False))
ac1.update_config({"mvbox_move": False})
assert ac1.get_config("mvbox_move") == "0"
def test_has_savemime(self, acfactory):
@@ -229,11 +227,11 @@ class TestOfflineContact:
class TestOfflineChat:
@pytest.fixture
@pytest.fixture()
def ac1(self, acfactory):
return acfactory.get_pseudo_configured_account()
@pytest.fixture
@pytest.fixture()
def chat1(self, ac1):
return ac1.create_contact("some1@example.org", name="some1").create_chat()
@@ -257,7 +255,7 @@ class TestOfflineChat:
assert chat2.id == chat1.id
assert chat2.get_name() == chat1.get_name()
assert chat1 == chat2
assert not (chat1 != chat2)
assert not chat1.__ne__(chat2)
assert chat1 != chat3
for ichat in ac1.get_chats():
@@ -450,7 +448,7 @@ class TestOfflineChat:
assert msg.filemime == "image/png"
@pytest.mark.parametrize(
"fn,typein,typeout",
("fn", "typein", "typeout"),
[
("r", None, "application/octet-stream"),
("r.txt", None, "text/plain"),
@@ -458,7 +456,7 @@ class TestOfflineChat:
("r.txt", "image/png", "image/png"),
],
)
def test_message_file(self, ac1, chat1, data, lp, fn, typein, typeout):
def test_message_file(self, chat1, data, lp, fn, typein, typeout):
lp.sec("sending file")
fp = data.get_path(fn)
msg = chat1.send_file(fp, typein)
@@ -694,7 +692,7 @@ class TestOfflineChat:
chat1.set_draft(None)
assert chat1.get_draft() is None
def test_qr_setup_contact(self, acfactory, lp):
def test_qr_setup_contact(self, acfactory):
ac1 = acfactory.get_pseudo_configured_account()
ac2 = acfactory.get_pseudo_configured_account()
qr = ac1.get_setup_contact_qr()
@@ -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

@@ -93,7 +93,7 @@ def test_empty_context():
capi.lib.dc_context_unref(ctx)
def test_dc_close_events(tmpdir, acfactory):
def test_dc_close_events(acfactory):
ac1 = acfactory.get_unconfigured_account()
# register after_shutdown function

View File

@@ -8,7 +8,7 @@ envlist =
[testenv]
commands =
pytest -n6 --extra-info --reruns 2 --reruns-delay 5 -v -rsXx --ignored --strict-tls {posargs: tests examples}
pytest -n6 --extra-info -v -rsXx --ignored --strict-tls {posargs: tests examples}
pip wheel . -w {toxworkdir}/wheelhouse --no-deps
setenv =
# Avoid stack overflow when Rust core is built without optimizations.
@@ -21,7 +21,6 @@ passenv =
RUSTC_WRAPPER
deps =
pytest
pytest-rerunfailures
pytest-timeout
pytest-xdist
pdbpp
@@ -50,18 +49,14 @@ commands =
skipsdist = True
skip_install = True
deps =
flake8
# isort 5.11.0 is broken: https://github.com/PyCQA/isort/issues/2031
isort<5.11.0
ruff
black
# pygments required by rst-lint
pygments
restructuredtext_lint
commands =
isort --check setup.py install_python_bindings.py src/deltachat examples/ tests/
black --check setup.py install_python_bindings.py src/deltachat examples/ tests/
flake8 src/deltachat
flake8 tests/ examples/
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
[testenv:mypy]
@@ -102,7 +97,3 @@ timeout = 150
timeout_func_only = True
markers =
ignored: ignore this test in default test runs, use --ignored to run.
[flake8]
max-line-length = 120
ignore = E203, E266, E501, W503

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

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