Compare commits

..

119 Commits

Author SHA1 Message Date
Alexander Krotov
f7e00364bf Change the order of job processing 2019-11-29 15:07:55 +01:00
Alexander Krotov
6306c82dfe Try jobs with zero retries on network probe 2019-11-28 23:54:57 +01:00
Floris Bruynooghe
f0486eb820 Skip bad jobs in the database
Be more defensive: if somehow we got corrupt jobs in the database skip
over them rather than fail to do anything.

This only modifies the query_map() call, the rest is only split off
into it's own function to make it testable.  Smaller functions are
good anyway.
2019-11-28 23:41:47 +01:00
Alexander Krotov
e06ac87c0d Convert the number of retries to u32 2019-11-28 21:44:16 +01:00
Alexander Krotov
0c19fcd79f Use Rust for instead of while 2019-11-28 21:44:16 +01:00
Alexander Krotov
02fe3d1b99 Process PerformJobsNeeded::AvoidDos case explicitly 2019-11-28 21:44:16 +01:00
Alexander Krotov
95b90a59dc Add PerformJobsNeeded enum (DC_JOBS_NEEDED_* in core) 2019-11-28 21:44:16 +01:00
Alexander Krotov
fe7852b64e Restore JOB_RETRIES constant 2019-11-28 21:44:16 +01:00
holger krekel
f87cb4231c rename jobs to make "rg Markseen" and other searches for the job enum produce all places dealing with the enum 2019-11-28 19:42:24 +01:00
Alexander Krotov
430d4e5f6e Replace DC_CREATE_MVBOX with boolean 2019-11-28 18:24:29 +01:00
holger krekel
6f6791c1b5 (flub/r10s/hpk42) remove MoveState and related functions because we don't actually need it anymore 2019-11-28 15:57:26 +01:00
holger krekel
bc11ae7245 add a test for bcc-self sent messages getting moved 2019-11-28 15:57:26 +01:00
B. Petersen
de228bdb4b also move messages sent to ourselves via bcc_self to the DeltaChat folder (other messages are moved in receive_imf()) 2019-11-28 15:57:26 +01:00
holger krekel
25fb199ba0 update to current async-imap master which should fix a crash 2019-11-28 13:04:37 +01:00
Alexander Krotov
d0795f5770 Automatically fix some clippy warnings with "cargo fix" 2019-11-27 21:43:18 +01:00
holger krekel
fbb8c8e80c be less verbose when generating python docs 2019-11-27 13:41:08 +01:00
holger krekel
76610f1e72 rename to more specific name, also not using the difficult to type "succeeded" word :) 2019-11-27 13:02:07 +01:00
holger krekel
618d74cd67 safer interruptability of fake-idle -- reusing the same skip_next_idle pattern as with idle 2019-11-27 05:52:14 +01:00
Alexander Krotov
59700cb477 Restore peerstate constants from C core 2019-11-27 05:41:54 +01:00
Floris Bruynooghe
fc1a136448 Remove unused (async)Arc
This identical naming of sync and async arcs is not confusing at all
btw.
2019-11-27 05:39:36 +01:00
holger krekel
42ef43bdf6 test the new event 2019-11-27 00:43:50 +01:00
B. Petersen
f53b3c2e7b add DC_EVENT_SECUREJOIN_SUCCEEDED 2019-11-27 00:43:50 +01:00
Alexander Krotov
22a0e3fe9c job: try_again refactoring
Introduce TryAgain type with C core constants
Make try_again field and method private.
Remove try_again == 2 processing, it was never used.
2019-11-26 22:14:34 +01:00
Floris Bruynooghe
e0601bab3d Leave the avatar in place if it already is in the blobdir 2019-11-25 12:59:55 +01:00
Floris Bruynooghe
69369b02ea Copy Selfavatar to blobdir and store as proper blob
When a new Selfavatar is set the file needs to be copied into the
blobdir and as a proper blob it should be stored in the database as
$BLOBDIR/$filename.

This should fix #868.
2019-11-25 12:59:55 +01:00
B. Petersen
966b9fac49 fix cast to libc::c_char, signed i8 fails eg. on android 2019-11-24 18:12:42 +01:00
holger krekel
e2a86dd803 prepare beta8 2019-11-24 14:41:25 +01:00
holger krekel
1b88cfd053 as recent fixes are merged to async-imap, let's use that cc @link2xt 2019-11-24 16:30:49 +03:00
Alexander Krotov
6412adcfa7 Use forked version of async-imap and imap-proto
This attempts to fix fetching when the inbox contains mail with non-utf8 subjects.
2019-11-24 12:05:30 +01:00
holger krekel
76de8f55f2 make ssh fail directly if a password is asked 2019-11-24 01:05:33 +01:00
holger krekel
ae43b83162 random change to test PR 2019-11-24 01:05:33 +01:00
Alexander Krotov
8d81c8c1e0 Do not crash on messages without message-id 2019-11-23 22:52:16 +01:00
Alexander Krotov
1e173524b5 Upgrade to hex 0.4
rpgp depends on hex 0.4. This prevents building two versions of the same library.
2019-11-23 22:51:27 +01:00
B. Petersen
490418d359 add icon source files 2019-11-22 16:27:54 +01:00
B. Petersen
51ef4d82f9 use flatter design and higher resolution for icons 2019-11-22 16:27:54 +01:00
holger krekel
e522920d49 fix the check for not installing bindings (used from CI), and refined rerun policy 2019-11-22 13:54:30 +01:00
holger krekel
47be554aff adapt README, comment previous python tests out 2019-11-22 12:28:19 +01:00
holger krekel
f44b2a63b0 revert back to split doc and lint 2019-11-22 12:28:19 +01:00
holger krekel
8d4b893658 fix missing renames and tox dep 2019-11-22 12:28:19 +01:00
holger krekel
7e3c61eb41 address @dignifiedquire err.to_string() comment and avoid extra virtualenv building 2019-11-22 12:28:19 +01:00
holger krekel
bb396685ab some comments fix imap->inbox naming in example 2019-11-22 12:28:19 +01:00
holger krekel
8ef0ea8aea simplify double-fetching 2019-11-22 12:28:19 +01:00
holger krekel
34c766dc2b merge JobThread::connect_to_imap with Imap::connect_configured for simplicity 2019-11-22 12:28:19 +01:00
holger krekel
a30fa710ad resultify fetch and simplify fake_idle 2019-11-22 12:28:19 +01:00
holger krekel
fa01884350 proper handling of IdleResponse codes 2019-11-22 12:28:19 +01:00
holger krekel
eae9ad6f8b remove context.inbox in favour of a context.inbox_thread following the mvbox_thread and sentbox_thread patterns. Also some streamlining of shutdown logic. 2019-11-22 12:28:19 +01:00
holger krekel
be533fa66a resultify some imap operations 2019-11-22 12:28:19 +01:00
holger krekel
254b061921 update docs and add a simple manual script to run python/rust tests 2019-11-22 12:28:19 +01:00
holger krekel
cefa03f45b run doxygen for core docs again 2019-11-22 12:28:19 +01:00
holger krekel
b67203b421 use the reponame in dir builddir to we can distinguish from desktop/android etc 2019-11-22 12:28:19 +01:00
holger krekel
e14c4d0683 re-enable cross, some streamlining of docs 2019-11-22 12:28:19 +01:00
holger krekel
4ed96b16f4 simplify/speedup python tests 2019-11-22 12:28:19 +01:00
holger krekel
932c86bb3b various cleanups, better parallelism and build-dir structure 2019-11-22 12:28:19 +01:00
holger krekel
590fd53dd4 don't invoke py36/py35 anymore here. 2019-11-22 12:28:19 +01:00
holger krekel
8e7dc5e86f reconfigure running of rust and python tests 2019-11-22 12:28:19 +01:00
holger krekel
e13ce3140b introduce a trigger_reconnect helper 2019-11-22 12:28:19 +01:00
holger krekel
2ebb43b613 update deps, in particular async-imap to 0.1.1 which contains
the fix by @dignifiedquire that is expected to fix #829
2019-11-22 12:28:19 +01:00
holger krekel
863a70b8fc new clean try to get circle-ci to work, disable and move gh actions to ci_scripts folder 2019-11-22 12:28:19 +01:00
dignifiedquire
89a4b6fee5 update github actions 2019-11-22 12:28:19 +01:00
holger krekel
0405c945e2 shortcut fetch/idle on mvbox/sentbox if we don't know the folder and prevent busy-looping 2019-11-22 12:28:19 +01:00
holger krekel
5293ea70ae steramline some teardown decision code, and add webpki_roots for cert-checking 2019-11-22 12:28:19 +01:00
holger krekel
b5cbc97333 also make smtp respect CertificateChecks setting roughly 2019-11-22 12:28:19 +01:00
holger krekel
a867452927 rough integration of async-tls CertChecks (strict and automatic but not more finegrained work) 2019-11-22 12:28:19 +01:00
dignifiedquire
b13ca0d077 update to released versions 2019-11-22 12:28:19 +01:00
holger krekel
c6f4d6d8bd * fix interrupt_idle by signalling "skip_next_idle_wait" to the potentially concurrently "fn idle" function
* fixes double-export issue
2019-11-22 12:28:19 +01:00
holger krekel
8723aa097e make select_folder return ImapActionResult's and early-return from idle if there is no selected folder 2019-11-22 12:28:19 +01:00
dignifiedquire
97e6bc2be3 bust ci cache, update deps, use a different rust version, remove rustup install 2019-11-22 12:28:19 +01:00
dignifiedquire
2c2555fad9 refactor: drop native-tls 2019-11-22 12:28:19 +01:00
dignifiedquire
f4f69a030a update docker image 2019-11-22 12:28:19 +01:00
dignifiedquire
86f66f4d78 cleanup imap impl 2019-11-22 12:28:19 +01:00
dignifiedquire
b4e2b69086 update async-imap 2019-11-22 12:28:19 +01:00
dignifiedquire
1a1a59a14e implement idle again 2019-11-22 12:28:19 +01:00
dignifiedquire
1687e8d26f it compiles with async-imap, remove local dependency 2019-11-22 12:28:19 +01:00
Alexander Krotov
4b8252e001 Implement public key selection
First, try to use subkeys, because they are usually
short-term encryption keys. If none of the subkeys
are encryption keys, try to use the primary key.

rPGP is updated to the master branch because the
latest release does not have .is_encryption_key() yet.
2019-11-22 10:20:40 +01:00
Floris Bruynooghe
f505ff03e4 Do not break double file extensions
Double extensions are sometimes used to identify files correctly,
e.g. .tar.gz or .html.zip.  Breaking those extensions is not very
nice.

This fixes #865.
2019-11-21 22:18:31 +01:00
björn petersen
ff8e282c43 Merge pull request #862 from deltachat/fix-bcc-self
mark messages as sent
2019-11-20 15:28:40 +01:00
B. Petersen
e39cb160f9 make messages as sent in groups with only SELF and bcc_self disabled 2019-11-20 02:48:43 +01:00
björn petersen
76ce0c3540 Merge pull request #856 from deltachat/fix-time-smearing
fix time smearing
2019-11-19 23:25:11 +01:00
B. Petersen
f7047bbf51 add detailed comments about time-smearing 2019-11-19 22:52:24 +01:00
B. Petersen
c3a53cefb0 fix time smearing
if two messages have the same time,
this results i all kinds of sorting failures,
esp. in non-delta-muas and on forwarding messages.
so, no two messages sent out from delta have the same timestamp.
normally, this is no problem, but when things are sent too fast,
eg. on forwarding, we lend us some time from the future.

however, all this did not work
because we forgot to write back the modified time,
this is fixed by this commit, as well as some cleanup.
2019-11-19 22:23:01 +01:00
björn petersen
a88153954e Merge pull request #854 from deltachat/tweak-device-msg
tweak device messages
2019-11-19 22:19:42 +01:00
Alexander Krotov
4732085421 Fix some clippy warnings 2019-11-19 19:52:13 +01:00
B. Petersen
e4e6a00fe4 rename has_device_msg() to was_device_msg_ever_added() what describes better what the function is doing 2019-11-19 13:21:02 +01:00
B. Petersen
700e10bc0e use dc_add_device_msg() also to add labels-only, this is clearer as having a function with 'skip' in the name 2019-11-19 13:05:01 +01:00
B. Petersen
2a4c193601 simplify check for existing device-message 2019-11-19 11:56:35 +01:00
B. Petersen
d3fa289f27 streamline dc_add_device_msg_once|unlabelled() to one function 2019-11-19 11:49:02 +01:00
B. Petersen
1534a07ded add dc_has_device_msg() 2019-11-18 17:31:03 +01:00
B. Petersen
a4e92694ba name unlabelled device-messages as such 2019-11-18 16:47:18 +01:00
B. Petersen
035da9e5d7 add dc_skip_device_msg() and belonging tests 2019-11-18 16:47:18 +01:00
B. Petersen
fed6f4ab8a do not re-add device-messages deleted by the user 2019-11-18 16:47:18 +01:00
Nico de Haen
d7c42f3c98 Use coalesce for optional columns
resolves #851
2019-11-17 20:57:50 +01:00
B. Petersen
ad852161f3 higher resolution for device-icon 2019-11-17 20:56:42 +01:00
B. Petersen
e9b2d6a594 adapt tests to saved-messages chat 2019-11-17 20:56:07 +01:00
B. Petersen
f9d2fe710d rename me-chat to saved-messages, use an appropriate icon 2019-11-17 20:56:07 +01:00
björn petersen
a4a1cd42db Merge pull request #844 from deltachat/move-location-to-trash
Delete msg related POI when message is deleted
2019-11-16 17:40:53 +01:00
Alexander Krotov
69e14dcb2d Replace some magic numbers with constants 2019-11-16 16:08:08 +00:00
Nico de Haen
e2673894b4 Delete message only if its a POI 2019-11-16 16:55:13 +01:00
B. Petersen
852b16c972 target comment of @flub 2019-11-16 13:55:59 +01:00
B. Petersen
5796c28391 test contact adding and modifying 2019-11-16 13:55:59 +01:00
holger krekel
b6095e29d7 Update src/pgp.rs 2019-11-16 13:53:47 +01:00
Alexander Krotov
f778957caf Improve documentation and comments 2019-11-16 13:53:47 +01:00
Alexander Krotov
47f8da6532 Expand tabs into spaces 2019-11-16 12:32:32 +01:00
Alexander Krotov
0b3f2a55df Do not use wildcard match in msgtype_has_file
This way there will be a compiler error notifying that the function needs to be updated when a new message type is added.
2019-11-16 12:31:13 +01:00
Nico de Haen
13ff2bd8c4 Fix wrong indents 2019-11-16 11:28:43 +01:00
Nico de Haen
c690a64462 Update msg related location when message is deleted
resolves #843
2019-11-16 11:04:56 +01:00
B. Petersen
d808bfe400 add some tests for me-chat and deaddrop 2019-11-13 22:31:18 +01:00
B. Petersen
1b30078c09 test archive functions 2019-11-13 10:16:57 +01:00
B. Petersen
0278875e03 add device-chat tests 2019-11-13 00:13:32 +01:00
B. Petersen
d0ccf28678 do not add random label for unlabelled device-messages 2019-11-13 00:13:32 +01:00
B. Petersen
5023255ebc fix accidentally called maybe_add_bcc_self_device_msg() 2019-11-13 00:13:32 +01:00
holger krekel
545376875a fix python tests 2019-11-12 16:10:48 +01:00
B. Petersen
a9fe77b62e use separate column for device-msg-labels 2019-11-12 16:10:48 +01:00
B. Petersen
b42d8799b4 add dc_add_device_msg_once() to fii 2019-11-12 16:10:48 +01:00
B. Petersen
a42e197634 add device-message only once 2019-11-12 16:10:48 +01:00
B. Petersen
fc32c85608 add device-msg 'consider enabling bcc_self' if bcc_self is disabled and an autocrypt-setup-message is received 2019-11-12 16:10:48 +01:00
B. Petersen
dabd431b1f change default for bcc_self, preserve values for existing installations 2019-11-12 16:10:48 +01:00
B. Petersen
85b4817a1e restore some comments 2019-11-12 16:10:48 +01:00
B. Petersen
84c6113271 use enum for show_emails 2019-11-12 16:10:48 +01:00
B. Petersen
9506f8c38e use bool for exists_before_update 2019-11-12 16:10:48 +01:00
70 changed files with 2533 additions and 1167 deletions

View File

@@ -128,32 +128,47 @@ jobs:
paths:
- c-docs
build_test_docs_wheel:
docker:
- image: deltachat/coredeps
environment:
TESTS: 1
DOCS: 1
working_directory: /mnt/crate
# build_test_docs_wheel:
# docker:
# - image: deltachat/coredeps
# environment:
# TESTS: 1
# DOCS: 1
# working_directory: /mnt/crate
# steps:
# - *restore-workspace
# - *restore-cache
# - run:
# name: build docs, run tests and build wheels
# command: ci_scripts/run-python.sh
# - run:
# name: copying docs and wheels to workspace
# command: |
# mkdir -p workspace/python
# # cp -av docs workspace/c-docs
# cp -av python/.docker-tox/wheelhouse workspace/
# cp -av python/doc/_build/ workspace/py-docs
# - persist_to_workspace:
# root: workspace
# paths:
# # - c-docs
# - py-docs
# - wheelhouse
remote_tests_rust:
machine: true
steps:
- *restore-workspace
- *restore-cache
- run:
name: build docs, run tests and build wheels
command: ci_scripts/run-python.sh
- run:
name: copying docs and wheels to workspace
command: |
mkdir -p workspace/python
# cp -av docs workspace/c-docs
cp -av python/.docker-tox/wheelhouse workspace/
cp -av python/doc/_build/ workspace/py-docs
- persist_to_workspace:
root: workspace
paths:
# - c-docs
- py-docs
- wheelhouse
- checkout
- run: ci_scripts/remote_tests_rust.sh
remote_tests_python:
machine: true
steps:
- checkout
#- attach_workspace:
# at: workspace
- run: ci_scripts/remote_tests_python.sh
# workspace/py-docs workspace/wheelhouse workspace/c-docs
upload_docs_wheels:
machine: true
@@ -181,15 +196,15 @@ workflows:
test:
jobs:
- cargo_fetch
- build_doxygen
- build_test_docs_wheel:
requires:
- cargo_fetch
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_doxygen
- remote_tests_rust
- remote_tests_python
# - upload_docs_wheels:
# requires:
# - build_test_docs_wheel
# - build_doxygen
- rustfmt:
requires:
- cargo_fetch
@@ -197,10 +212,12 @@ workflows:
requires:
- cargo_fetch
- build_doxygen
# Linux Desktop 64bit
- test_x86_64-unknown-linux-gnu:
requires:
- cargo_fetch
# - test_x86_64-unknown-linux-gnu:
# requires:
# - cargo_fetch
# Linux Desktop 32bit
# - test_i686-unknown-linux-gnu:

View File

@@ -1,30 +0,0 @@
name: CI
on:
pull_request:
push:
env:
RUSTFLAGS: -Dwarnings
jobs:
build:
name: 3.7 python tests against core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- name: Setup python
uses: actions/setup-python@v1
with:
python-version: 3.x
architecture: x64
- run: bash ci_scripts/run-python.sh

View File

@@ -1,5 +1,37 @@
# Changelog
## 1.0.0-beta.8
- now uses async-email/async-imap as the new base
which makes imap-idle interruptible and thus fixes
several issues around the imap thread being in zombie state .
thanks @dignifiedquire, @hpk42 and @link2xt.
- fixes imap-protocol parsing bugs that lead to infinitely
repeated crashing while trying to receive messages with
a subjec that contained non-utf8. thanks @link2xt
- fixed logic to find encryption subkey -- previously
delta chat would use the primary key for encryption
(which works with RSA but not ECC). thanks @link2xt
- introduce a new device chat where core and UIs can
add "device" messages. Android uses it for an initial
welcome message. thanks @r10s
- fix time smearing (when two message are virtually send
in the same second, there would be misbehaviour because
we didn't persist smeared time). thanks @r10s
- fix double-dotted extensions like .html.zip or .tar.gz
to not mangle them when creating blobfiles. thanks @flub
- fix backup/exports where the wrong sql file would be modified,
leading to problems when exporting twice. thanks @hpk42
- several other little fixes and improvements
## 1.0.0-beta.7
- fix location-streaming #782

336
Cargo.lock generated
View File

@@ -52,7 +52,7 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.19"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -89,81 +89,55 @@ dependencies = [
[[package]]
name = "async-imap"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.1.1"
source = "git+https://github.com/async-email/async-imap#377d40837028b454c6365ff13e3f35cc341a908e"
dependencies = [
"async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"async-std 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-std 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-tls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures_codec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"imap-proto 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"imap-proto 0.9.1 (git+https://github.com/djc/tokio-imap)",
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rental 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"stop-token 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "async-macros"
version = "1.0.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "async-std"
version = "0.99.12"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"async-macros 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-macros 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"broadcaster 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-timer 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.20 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
"once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project-lite 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "async-std"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"async-macros 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"broadcaster 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-timer 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project-lite 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -207,7 +181,7 @@ version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -417,7 +391,7 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "0.1.10"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -436,7 +410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -502,15 +476,15 @@ name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-channel"
version = "0.3.9"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -528,7 +502,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -548,7 +522,7 @@ name = "crossbeam-utils"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -558,7 +532,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -634,10 +608,10 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-beta.7"
version = "1.0.0-beta.8"
dependencies = [
"async-imap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-std 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap)",
"async-std 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-tls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -650,18 +624,18 @@ dependencies = [
"escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (git+https://github.com/deltachat/lettre?branch=feat/rustls)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"mmime 0.1.2",
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pgp 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"pgp 0.3.2 (git+https://github.com/rpgp/rpgp)",
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -679,7 +653,7 @@ dependencies = [
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"stop-token 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -709,13 +683,13 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.0.0-beta.7"
version = "1.0.0-beta.8"
dependencies = [
"deltachat 1.0.0-beta.7",
"deltachat 1.0.0-beta.8",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -813,7 +787,7 @@ name = "encoding_rs"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -821,14 +795,6 @@ name = "entities"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "enum_primitive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "env_logger"
version = "0.6.2"
@@ -875,7 +841,7 @@ dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -906,7 +872,7 @@ name = "flate2"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -989,7 +955,7 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1035,12 +1001,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-timer"
version = "1.0.3"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-util"
@@ -1094,7 +1056,7 @@ name = "getrandom"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1142,11 +1104,6 @@ dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hex"
version = "0.4.0"
@@ -1268,7 +1225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1278,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1294,7 +1251,7 @@ dependencies = [
[[package]]
name = "imap-proto"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
source = "git+https://github.com/djc/tokio-imap#a26056915f1d715f97935da1f0c97c6d0174f292"
dependencies = [
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1317,7 +1274,7 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1335,10 +1292,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "js-sys"
version = "0.3.31"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"wasm-bindgen 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1396,7 +1353,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1440,7 +1397,7 @@ name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1512,7 +1469,7 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1525,9 +1482,10 @@ dependencies = [
[[package]]
name = "mio"
version = "0.6.19"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1547,7 +1505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.20 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1567,7 +1525,7 @@ version = "0.1.2"
dependencies = [
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1579,7 +1537,7 @@ name = "net2"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1591,7 +1549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1631,7 +1589,7 @@ dependencies = [
"libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1654,7 +1612,7 @@ version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1664,20 +1622,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.8"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1685,7 +1635,7 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1723,7 +1673,7 @@ name = "packed_simd"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1741,7 +1691,7 @@ name = "parking_lot_core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1762,8 +1712,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pgp"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.3.2"
source = "git+https://github.com/rpgp/rpgp#4cc60a1e45a781ea6e7f394ae2583844ac75d214"
dependencies = [
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1777,12 +1727,12 @@ dependencies = [
"cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)",
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1793,7 +1743,7 @@ dependencies = [
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint-dig 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rsa 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1809,7 +1759,7 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -1888,7 +1838,7 @@ dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2233,7 +2183,7 @@ dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"web-sys 0.3.31 (registry+https://github.com/rust-lang/crates.io-index)",
"web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2258,7 +2208,7 @@ dependencies = [
"num-bigint-dig 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2524,6 +2474,11 @@ dependencies = [
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "smallvec"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "sourcefile"
version = "0.1.4"
@@ -2546,11 +2501,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "stop-token"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"async-std 0.99.12 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project-lite 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"async-std 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2645,7 +2600,7 @@ dependencies = [
[[package]]
name = "synstructure"
version = "0.12.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2668,7 +2623,7 @@ name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2725,8 +2680,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.20 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2783,8 +2738,8 @@ dependencies = [
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.20 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2822,7 +2777,7 @@ dependencies = [
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.20 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2838,7 +2793,7 @@ dependencies = [
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2877,7 +2832,7 @@ name = "try_from"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2897,10 +2852,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicase"
version = "2.5.1"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2913,10 +2868,10 @@ dependencies = [
[[package]]
name = "unicode-normalization"
version = "0.1.9"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2987,7 +2942,7 @@ name = "uuid"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -3009,6 +2964,11 @@ name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
@@ -3049,16 +3009,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasm-bindgen"
version = "0.2.54"
version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.54"
version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3067,60 +3027,60 @@ dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.54"
version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro-support 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.54"
version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.54"
version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasm-bindgen-webidl"
version = "0.2.54"
version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"anyhow 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
"weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "web-sys"
version = "0.3.31"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"anyhow 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"js-sys 0.3.31 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
"js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
"sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-webidl 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -3269,7 +3229,7 @@ dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
@@ -3279,16 +3239,15 @@ dependencies = [
"checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100"
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum anyhow 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "57114fc2a6cc374bce195d3482057c846e706d252ff3604363449695684d7a0d"
"checksum anyhow 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "6f1072d8f55592084072d2d3cb23a4b680a8543c00f10d446118e85ad3718142"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
"checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
"checksum async-imap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11beae75596af5c2c44f08dc1ef666bc0982e65646fd59f70af800709a6028a8"
"checksum async-macros 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e421d59b24c1feea2496e409b3e0a8de23e5fc130a2ddc0b012e551f3b272bba"
"checksum async-std 0.99.12 (registry+https://github.com/rust-lang/crates.io-index)" = "44501a9f7961bb539b67be0c428b3694e26557046a52759ca7eaf790030a64cc"
"checksum async-std 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80fd70c8b0cbd985275dc70448d5b6b76338ead5350d1774d81585ab8f826673"
"checksum async-imap 0.1.1 (git+https://github.com/async-email/async-imap)" = "<none>"
"checksum async-macros 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "644a5a8de80f2085a1e7e57cd1544a2a7438f6e003c0790999bd43b92a77cdb2"
"checksum async-std 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56933da6903b273923d13f4746d829f66ff9b444173f6743d831e80f4da15446"
"checksum async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de6bd58f7b9cc49032559422595c81cbfcf04db2f2133592f70af19e258a1ced"
"checksum async-tls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce6977f57fa68da77ffe5542950d47e9c23d65f5bc7cb0a9f8700996913eec7"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
@@ -3320,7 +3279,7 @@ dependencies = [
"checksum cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6"
"checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8"
"checksum cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "190e7b55d3a27cf8879becf61035a141cbc783f3258a41d16d1706719f991345"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73"
"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
"checksum circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea"
@@ -3331,7 +3290,7 @@ dependencies = [
"checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c"
"checksum crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0"
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa"
"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
@@ -3357,7 +3316,6 @@ dependencies = [
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9"
"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
"checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9"
"checksum escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39da344028c2227132b2dfa7c186e2104ecc153467583d00ed9c398f9ff693b0"
@@ -3385,7 +3343,7 @@ dependencies = [
"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
"checksum futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "86f148ef6b69f75bb610d4f9a2336d4fc88c4b5b67129d1a340dd0fd362efeec"
"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
"checksum futures-timer 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7946248e9429ff093345d3e8fdf4eb0f9b2d79091611c9c14f744971a6f8be45"
"checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6"
"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
"checksum futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "5ce968633c17e5f97936bd2797b6e38fb56cf16a7422319f7ec2e30d3c470e8d"
"checksum futures_codec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8efb8edac092482013e203af800eef158349c8bbf461edff59c5dd2d3c00e87"
@@ -3396,7 +3354,6 @@ dependencies = [
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
"checksum http 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "d7e06e336150b178206af098a055e3621e8336027e2b4d126bda0bc64824baaf"
@@ -3410,13 +3367,13 @@ dependencies = [
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
"checksum image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b00861cbbb254a627d8acc0cec786b484297d896ab8f20fdc8e28536a3e918ef"
"checksum imap-proto 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e0690d689f8afe8111dfe1daedd9ef0d232868c7819da3c1f9252fd260aff7f7"
"checksum imap-proto 0.9.1 (git+https://github.com/djc/tokio-imap)" = "<none>"
"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "87fa75c9dea7b07be3138c49abbb83fd4bea199b5cdc76f9804458edc5da0d6e"
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5f25cca2463cb19dbb1061eb3bd38a8b5e4ce1cc5a5a9fc0e02de486d92b9b05"
"checksum js-sys 0.3.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d8657b7ca06a6044ece477f6900bf7670f8b5fd0cce177a1d7094eef51e0adf4"
"checksum js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "1c840fdb2167497b0bd0db43d6dfe61e91637fa72f9d061f8bd17ddc44ba6414"
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb"
@@ -3440,7 +3397,7 @@ dependencies = [
"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf"
"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599"
"checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625"
"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
"checksum mio 0.6.20 (registry+https://github.com/rust-lang/crates.io-index)" = "72f4261ee7ab03cd36dc99eea4db8be6e83e4164da470e0c84f6726d6c605855"
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
@@ -3452,9 +3409,8 @@ dependencies = [
"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "155394f924cdddf08149da25bfb932d226b4a593ca7468b08191ff6335941af5"
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
"checksum once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed"
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
"checksum os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7edc011af0ae98b7f88cf7e4a83b70a54a75d2b8cb013d6efd02e5956207e9eb"
@@ -3464,8 +3420,8 @@ dependencies = [
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pgp 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a14471cfc3855455f5bbb639e367cedd69fbce71b32bfb83aff20c3deafce36e"
"checksum pin-project-lite 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f1f61fedcfa3b402e157c23e235b1ab6c30d52c219492c63504059e2bdd3408"
"checksum pgp 0.3.2 (git+https://github.com/rpgp/rpgp)" = "<none>"
"checksum pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0af6cbca0e6e3ce8692ee19fb8d734b641899e07b68eb73e9bbbd32f1703991"
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
@@ -3540,11 +3496,12 @@ dependencies = [
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7"
"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86"
"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
"checksum stop-token 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cffea6d106a0027e1375954eb1d82fbec7b9d7a60e9390722a593043461e8120"
"checksum stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "06855fb7c94d3be9b3a57c4d82dfc8a43bb658fbb3b1dda79de89e748d9eb9dd"
"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
@@ -3556,7 +3513,7 @@ dependencies = [
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "575be94ccb86e8da37efb894a87e2b660be299b41d8ef347f9d6d79fbe61b1ba"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83"
@@ -3581,9 +3538,9 @@ dependencies = [
"checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b"
"checksum twofish 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d261e83e727c8e2dbb75dacac67c36e35db36a958ee504f2164fc052434e1"
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
"checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150"
"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf"
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
@@ -3598,18 +3555,19 @@ dependencies = [
"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
"checksum wasm-bindgen 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c4568ae1b4e07ca907b1a4de41174eaa3e5be4066c024475586b7842725f69a9"
"checksum wasm-bindgen-backend 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5a00cfdce37367770062065fd3abb9278cbae86a0d918cacd0978a7acd51b481"
"checksum wasm-bindgen-macro 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "7c568f4d3cf6d7c1d72b165daf778fb0d6e09a24f96ac14fc8c4f66a96e86b72"
"checksum wasm-bindgen-macro-support 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "430d12539ae324d16097b399e9d07a6d5ce0173b2a61a2d02346ca7c198daffe"
"checksum wasm-bindgen-shared 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "8ae7167f0bbffd7fac2b12da0fa1f834c1d84671a1ae3c93ac8bde2e97179c39"
"checksum wasm-bindgen-webidl 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "3021567c515a746a64ad0b269d120d46e687c0c95702a4750623db935ae6b5e7"
"checksum web-sys 0.3.31 (registry+https://github.com/rust-lang/crates.io-index)" = "ce8e893e021539beb87de8f06e77bdb390a3ab0db4cfeb569c4e377b55ed20de"
"checksum wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "29ae32af33bacd663a9a28241abecf01f2be64e6a185c6139b04f18b6385c5f2"
"checksum wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "1845584bd3593442dc0de6e6d9f84454a59a057722f36f005e44665d6ab19d85"
"checksum wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "87fcc747e6b73c93d22c947a6334644d22cfec5abd8b66238484dc2b0aeb9fe4"
"checksum wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3dc4b3f2c4078c8c4a5f363b92fcf62604c5913cbd16c6ff5aaf0f74ec03f570"
"checksum wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ca0b78d6d3be8589b95d1d49cdc0794728ca734adf36d7c9f07e6459508bb53d"
"checksum wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3126356474ceb717c8fb5549ae387c9fbf4872818454f4d87708bee997214bb5"
"checksum web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "98405c0a2e722ed3db341b4c5b70eb9fe0021621f7350bab76df93b09b649bbf"
"checksum webpki 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4"
"checksum webpki-roots 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a262ae37dd9d60f60dd473d1158f9fbebf110ba7b6a5051c8160460f6043718b"
"checksum webpki-roots 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-beta.7"
version = "1.0.0-beta.8"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
@@ -10,8 +10,8 @@ deltachat_derive = { path = "./deltachat_derive" }
mmime = { version = "0.1.2", path = "./mmime" }
libc = "0.2.51"
pgp = { version = "0.2.3", default-features = false }
hex = "0.3.2"
pgp = { git = "https://github.com/rpgp/rpgp", branch = "master", default-features = false }
hex = "0.4.0"
sha2 = "0.8.0"
rand = "0.6.5"
smallvec = "0.6.9"
@@ -19,7 +19,7 @@ reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tl
num-derive = "0.2.5"
num-traits = "0.2.6"
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/rustls" }
async-imap = "0.1"
async-imap = { git = "https://github.com/async-email/async-imap", branch="master" }
async-tls = "0.6"
async-std = { version = "1.0", features = ["unstable"] }
base64 = "0.10"

View File

@@ -1,11 +1,9 @@
# Delta Chat Rust
> Project porting deltachat-core to rust
> Deltachat-core written in Rust
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor]
Current commit on deltachat/deltachat-core: `12ef73c8e76185f9b78e844ea673025f56a959ab`.
## Installing Rust and Cargo
To download and install the official compiler for the Rust programming language, and the Cargo package manager, run the command in your user environment:
@@ -16,7 +14,7 @@ curl https://sh.rustup.rs -sSf | sh
## Using the CLI client
Compile and run Delta Chat Core using `cargo`:
Compile and run Delta Chat Core command line utility, using `cargo`:
```
cargo run --example repl -- /path/to/db

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 22 KiB

110
assets/icon-device.svg Normal file
View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)"
sodipodi:docname="icon-device.svg"
id="svg4344"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 45 45"
height="60"
width="60"
version="1.0"
inkscape:export-filename="/Users/bpetersen/Documents/deltachat/icons/device4.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
id="defs4348">
<linearGradient
inkscape:collect="always"
id="linearGradient865">
<stop
style="stop-color:#364e59;stop-opacity:1;"
offset="0"
id="stop861" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop863" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient865"
id="linearGradient867"
x1="10.227795"
y1="43.5238"
x2="-25.458139"
y2="-7.4257693"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
inkscape:current-layer="svg4344"
inkscape:window-maximized="0"
inkscape:window-y="23"
inkscape:window-x="45"
inkscape:cy="28.471578"
inkscape:cx="41.455164"
inkscape:zoom="5.6"
units="px"
showgrid="false"
id="namedview4346"
inkscape:window-height="1035"
inkscape:window-width="1600"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
inkscape:document-rotation="0"
bordercolor="#666666"
pagecolor="#ffffff" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<circle
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.731071"
id="path859"
cx="22.5"
cy="22.5"
r="22.5" />
<circle
r="22.5"
cy="22.5"
cx="22.5"
id="path4917"
style="fill:url(#linearGradient867);fill-opacity:1;stroke-width:0.588554" />
<g
id="g4342"
stroke="none"
fill="#000000"
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
style="fill:#ffffff;fill-opacity:1">
<path
inkscape:connector-curvature="0"
id="path4338"
d="m 8175,12765 c -703,-114 -1248,-608 -1387,-1258 -17,-82 -21,-136 -22,-277 0,-202 15,-307 70,-470 149,-446 499,-733 1009,-828 142,-26 465,-23 619,6 691,131 1201,609 1328,1244 31,158 31,417 0,565 -114,533 -482,889 -1038,1004 -133,27 -448,35 -579,14 z"
style="fill:#ffffff;fill-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path4340"
d="m 7070,9203 c -212,-20 -275,-27 -397,-48 -691,-117 -1400,-444 -2038,-940 -182,-142 -328,-270 -585,-517 -595,-571 -911,-974 -927,-1181 -6,-76 11,-120 69,-184 75,-80 159,-108 245,-79 109,37 263,181 632,595 539,606 774,826 1035,969 135,75 231,105 341,106 82,1 94,-2 138,-27 116,-68 161,-209 122,-376 -9,-36 -349,-868 -757,-1850 -407,-982 -785,-1892 -838,-2021 -287,-694 -513,-1389 -615,-1889 -70,-342 -90,-683 -52,-874 88,-440 381,-703 882,-792 124,-23 401,-30 562,-16 783,69 1674,461 2561,1125 796,596 1492,1354 1607,1751 43,146 -33,308 -168,360 -61,23 -100,15 -173,-36 -105,-74 -202,-170 -539,-529 -515,-551 -762,-783 -982,-927 -251,-164 -437,-186 -543,-65 -56,64 -74,131 -67,247 13,179 91,434 249,815 135,324 1588,4102 1646,4280 106,325 151,561 159,826 9,281 -22,463 -112,652 -58,122 -114,199 -211,292 -245,233 -582,343 -1044,338 -91,-1 -181,-3 -200,-5 z"
style="fill:#ffffff;fill-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="204.8"
inkscape:export-xdpi="204.8"
inkscape:export-filename="/Users/bpetersen/Documents/deltachat/icons/saved-messages.png"
version="1.0"
width="60"
height="60"
viewBox="0 0 45 45"
preserveAspectRatio="xMidYMid meet"
id="svg4344"
sodipodi:docname="saved-messages.svg"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
<defs
id="defs4348">
<linearGradient
inkscape:collect="always"
id="linearGradient906">
<stop
style="stop-color:#046cdb;stop-opacity:1"
offset="0"
id="stop902" />
<stop
id="stop845"
offset="1"
style="stop-color:#ffffff;stop-opacity:1" />
</linearGradient>
<linearGradient
y2="-11.049108"
x2="-3.1473193"
y1="31.473215"
x1="36.160713"
gradientUnits="userSpaceOnUse"
id="linearGradient847"
xlink:href="#linearGradient906"
inkscape:collect="always" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
inkscape:document-rotation="0"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="1035"
id="namedview4346"
showgrid="false"
units="px"
inkscape:zoom="2.8"
inkscape:cx="48.214285"
inkscape:cy="55"
inkscape:window-x="45"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg4344"
inkscape:lockguides="false" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<circle
r="22.5"
cy="22.5"
cx="22.5"
id="path859"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.731071" />
<circle
style="fill:url(#linearGradient847);fill-opacity:1;stroke-width:0.588554"
id="path4917"
cx="22.5"
cy="22.5"
r="22.5" />
<path
id="rect846"
style="fill:#ffffff;stroke-width:0.84832"
d="M 17 9 L 17 55 L 17.125 55 L 30.048828 40.9375 L 42.974609 55 L 43 55 L 43 9 L 17 9 z M 30.005859 15 L 32.720703 22.121094 L 40 22.646484 L 34.396484 27.558594 L 36.169922 35 L 29.992188 30.916016 L 23.808594 34.988281 L 25.595703 27.550781 L 20 22.626953 L 27.28125 22.115234 L 30.005859 15 z "
transform="scale(0.75)" />
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,52 +1,46 @@
# Continuous Integration Scripts for Delta Chat
Continuous Integration is run through CircleCI
but is largely independent of it.
Continuous Integration, run through CircleCI and an own build machine.
## Description of scripts
- `../.circleci/config.yml` describing the build jobs that are run
by Circle-CI
- `remote_tests_python.sh` rsyncs to a build machine and runs
`run-python-test.sh` remotely on the build machine.
- `remote_tests_rust.sh` rsyncs to the build machine and runs
`run-rust-test.sh` remotely on the build machine.
- `doxygen/Dockerfile` specifies an image that contains
the doxygen tool which is used by `run-doxygen.sh`
to generate C-docs which are then uploaded
via `ci_upload.sh` to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
(and the master branch is linked to https://c.delta.chat proper).
## Generating docker containers for performing build step work
## Triggering runs on the build machine locally (fast!)
All tests, docs and wheel building is run in docker containers:
There is experimental support for triggering a remote Python or Rust test run
from your local checkout/branch. You will need to be authorized to login to
the build machine (ask your friendly sysadmin on #deltachat freenode) to type::
- **coredeps/Dockerfile** specifies an image that contains all
of Delta Chat's core dependencies as linkable libraries.
It also serves to run python tests and build wheels
(binary packages for Python).
ci_scripts/manual_remote_tests.sh rust
ci_scripts/manual_remote_tests.sh python
- **doxygen/Dockerfile** specifies an image that contains
the doxygen tool which is used to generate C-docs.
This will **rsync** your current checkout to the remote build machine
(no need to commit before) and then run either rust or python tests.
To run tests locally you can pull existing images from "docker.io",
the hub for sharing Docker images::
# Outdated files (for later re-use)
docker pull deltachat/coredeps
docker pull deltachat/doxygen
`coredeps/Dockerfile` specifies an image that contains all
of Delta Chat's core dependencies. It used to run
python tests and build wheels (binary packages for Python)
or you can build the docker images yourself locally
You can build the docker images yourself locally
to avoid the relatively large download::
cd ci_scripts # where all CI things are
docker build -t deltachat/coredeps docker-coredeps
docker build -t deltachat/doxygen docker-doxygen
## ci_run.sh (main entrypoint called by circle-ci)
Once you have the docker images available
you can run python testing, documentation generation
and building binary wheels::
sh DOCS=1 TESTS=1 ci_scripts/ci_run.sh
## ci_upload.sh (uploading artifacts on success)
- python docs to `https://py.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
- doxygen docs to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
- python wheels to `https://m.devpi.net/dc/<BRANCH>`
so that you install fully self-contained wheels like this:
`pip install -U -i https://m.devpi.net/dc/<BRANCH> deltachat`

View File

@@ -14,20 +14,24 @@ DOXYDOCDIR=${3:?directory where doxygen docs to be found}
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
# python docs to py.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$PYDOCDIR/html/" \
delta@py.delta.chat:build/${BRANCH}
# DISABLED: python docs to py.delta.chat
#ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
#rsync -avz \
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
# "$PYDOCDIR/html/" \
# delta@py.delta.chat:build/${BRANCH}
# C docs to c.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$DOXYDOCDIR/html/" \
delta@c.delta.chat:build-c/${BRANCH}
exit 0
# OUTDATED -- for re-use from python release-scripts
echo -----------------------
echo upload wheels
echo -----------------------

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -xe
export CIRCLE_JOB=remote_tests_${1:?need to specify 'rust' or 'python'}
export CIRCLE_BUILD_NUM=$USER
export CIRCLE_BRANCH=`git branch | grep \* | cut -d ' ' -f2`
export CIRCLE_PROJECT_REPONAME=$(basename `git rev-parse --show-toplevel`)
time bash ci_scripts/$CIRCLE_JOB.sh

View File

@@ -0,0 +1,77 @@
name: CI
on:
pull_request:
push:
env:
RUSTFLAGS: -Dwarnings
jobs:
build_and_test:
name: Build and test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
rust: [nightly]
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.rust }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: check
uses: actions-rs/cargo@v1
if: matrix.rust == 'nightly'
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all
- name: tests ignored
uses: actions-rs/cargo@v1
with:
command: test
args: --all --release -- --ignored
check_fmt:
name: Checking fmt and docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- name: fmt
run: cargo fmt --all -- --check
# clippy_check:
# name: Clippy check
# runs-on: ubuntu-latest
#
# steps:
# - uses: actions/checkout@v1
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: nightly
# override: true
# components: clippy
#
# - name: clippy
# run: cargo clippy --all

60
ci_scripts/old/run-python.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/bin/bash
#
# Build the Delta Chat C/Rust library typically run in a docker
# container that contains all library deps but should also work
# outside if you have the dependencies installed on your system.
set -e -x
# Perform clean build of core and install.
export TOXWORKDIR=.docker-tox
# install core lib
export PATH=/root/.cargo/bin:$PATH
cargo build --release -p deltachat_ffi
# cargo test --all --all-features
# Statically link against libdeltachat.a.
export DCC_RS_DEV=$(pwd)
# Configure access to a base python and to several python interpreters
# needed by tox below.
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd /bin
ln -s /opt/python/cp27-cp27m/bin/python2.7
ln -s /opt/python/cp36-cp36m/bin/python3.6
ln -s /opt/python/cp37-cp37m/bin/python3.7
popd
if [ -n "$TESTS" ]; then
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels
popd
fi
# if [ -n "$DOCS" ]; then
# echo -----------------------
# echo generating python docs
# echo -----------------------
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
# fi

View File

@@ -0,0 +1,46 @@
#!/bin/bash
export BRANCH=${CIRCLE_BRANCH:?branch to build}
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
export SSHTARGET=ci@b1.delta.chat
# we construct the BUILDDIR such that we can easily share the
# CARGO_TARGET_DIR between runs ("..")
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
set -xe
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
git ls-files >.rsynclist
# we seem to need .git for setuptools_scm versioning
find .git >>.rsynclist
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
set +x
echo "--- Running $CIRCLE_JOB remotely"
ssh $SSHTARGET <<_HERE
set +x -e
cd $BUILDDIR
# let's share the target dir with our last run on this branch/job-type
# cargo will make sure to block/unblock us properly
export CARGO_TARGET_DIR=\`pwd\`/../target
export TARGET=release
export DCC_PY_LIVECONFIG=$DCC_PY_LIVECONFIG
#we rely on tox/virtualenv being available in the host
#rm -rf virtualenv venv
#virtualenv -q -p python3.7 venv
#source venv/bin/activate
#pip install -q tox virtualenv
set -x
which python
source \$HOME/venv/bin/activate
which python
bash ci_scripts/run-python-test.sh
_HERE

32
ci_scripts/remote_tests_rust.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
export BRANCH=${CIRCLE_BRANCH:?branch to build}
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
export SSHTARGET=ci@b1.delta.chat
# we construct the BUILDDIR such that we can easily share the
# CARGO_TARGET_DIR between runs ("..")
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
set -e
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
git ls-files >.rsynclist
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
echo "--- Running $CIRCLE_JOB remotely"
ssh $SSHTARGET <<_HERE
set +x -e
cd $BUILDDIR
# let's share the target dir with our last run on this branch/job-type
# cargo will make sure to block/unblock us properly
export CARGO_TARGET_DIR=\`pwd\`/../target
export TARGET=x86_64-unknown-linux-gnu
export RUSTC_WRAPPER=sccache
bash ci_scripts/run-rust-test.sh
_HERE

24
ci_scripts/run-python-test.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
#
# Run functional tests for Delta Chat core using the python bindings
# and tox/pytest.
set -e -x
# for core-building and python install step
export DCC_RS_TARGET=release
export DCC_RS_DEV=`pwd`
cd python
python install_python_bindings.py onlybuild
# remove and inhibit writing PYC files
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run python tests (tox invokes pytest to run tests in python/tests)
#TOX_PARALLEL_NO_SPINNER=1 tox -e lint,doc
tox -e lint
tox -e doc,py37

View File

@@ -1,50 +0,0 @@
#!/bin/bash
#
# Build the Delta Chat C/Rust library typically run in a docker
# container that contains all library deps but should also work
# outside if you have the dependencies installed on your system.
set -e -x
# Perform clean build of core and install.
export TOXWORKDIR=.docker-tox
# install core lib
export PATH=/root/.cargo/bin:$PATH
cargo build --release -p deltachat_ffi
# Statically link against libdeltachat.a.
export DCC_RS_DEV=$(pwd)
# Configure access to a base python and to several python interpreters
# needed by tox below.
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
#tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
#tox --workdir "$TOXWORKDIR" -e auditwheels
popd
# if [ -n "$DOCS" ]; then
# echo -----------------------
# echo generating python docs
# echo -----------------------
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
# fi

View File

@@ -2,7 +2,7 @@
set -ex
export RUST_TEST_THREADS=1
#export RUST_TEST_THREADS=1
export RUST_BACKTRACE=1
export RUSTFLAGS='--deny warnings'
export OPT="--target=$TARGET"
@@ -37,7 +37,9 @@ else
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
fi
# Run all the test configurations:
# Run all the test configurations
# RUSTC_WRAPPER=SCCACHE seems to destroy parallelism / prolong the test
unset RUSTC_WRAPPER
$CARGO_CMD $CARGO_SUBCMD $OPT
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.7"
version = "1.0.0-beta.8"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"

View File

@@ -1096,9 +1096,9 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
* Add a message to the device-chat.
* Device-messages usually contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* Device-messages may be added from the core,
* however, with this function, this can be done from the ui as well.
* The device-message may be defined by a label;
* if a message with the same label was added or skipped before,
* the message is not added again, even if the message was deleted in between.
* If needed, the device-chat is created before.
*
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
@@ -1106,11 +1106,50 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param label A unique name for the message to add.
* The label is typically not displayed to the user and
* must be created from the characters `A-Z`, `a-z`, `0-9`, `_` or `-`.
* If you pass NULL here, the message is added unconditionally.
* @param msg Message to be added to the device-chat.
* The message appears to the user as an incoming message.
* @return The ID of the added message.
* If you pass NULL here, only the given label will be added
* and block adding messages with that label in the future.
* @return The ID of the just added message,
* if the message was already added or no message to add is given, 0 is returned.
*
* Example:
* ~~~
* dc_msg_t* welcome_msg = dc_msg_new(DC_MSG_TEXT);
* dc_msg_set_text(welcome_msg, "great that you give this app a try!");
*
* dc_msg_t* changelog_msg = dc_msg_new(DC_MSG_TEXT);
* dc_msg_set_text(changelog_msg, "we have added 3 new emojis :)");
*
* if (dc_add_device_msg(context, "welcome", welcome_msg)) {
* // do not add the changelog on a new installations -
* // not now and not when this code is executed again
* dc_add_device_msg(context, "update-123", NULL);
* } else {
* // welcome message was not added now, this is an oder installation,
* // add a changelog
* dc_add_device_msg(context, "update-123", changelog_msg);
* }
* ~~~
*/
uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg);
uint32_t dc_add_device_msg (dc_context_t* context, const char* label, dc_msg_t* msg);
/**
* Check if a device-message with a given label was ever added.
* Device-messages can be added dc_add_device_msg().
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param label Label of the message to check.
* @return 1=A message with this label was added at some point,
* 0=A message with this label was never added.
*/
int dc_was_device_msg_ever_added (dc_context_t* context, const char* label);
/**
@@ -2738,9 +2777,7 @@ int dc_chat_is_self_talk (const dc_chat_t* chat);
* From the ui view, device-talks are not very special,
* the user can delete and forward messages, archive the chat, set notifications etc.
*
* Messages may be added from the core to the device chat,
* so the chat just pops up as usual.
* However, if needed the ui can also add messages using dc_add_device_msg()
* Messages can be added to the device-talk using dc_add_device_msg()
*
* @memberof dc_chat_t
* @param chat The chat object.
@@ -4350,6 +4387,16 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
/**
* This event is sent out to the inviter when a joiner successfully joined a group.
*
* @param data1 (int) chat_id
* @param data2 (int) contact_id
* @return 0
*/
#define DC_EVENT_SECUREJOIN_MEMBER_ADDED 2062
/**
* @}
*/

View File

@@ -175,6 +175,15 @@ impl ContextWrapper {
contact_id as uintptr_t,
progress as uintptr_t,
),
Event::SecurejoinMemberAdded {
chat_id,
contact_id,
} => ffi_cb(
self,
event_id,
chat_id as uintptr_t,
contact_id as uintptr_t,
),
}
}
None => 0,
@@ -461,7 +470,7 @@ pub unsafe extern "C" fn dc_perform_imap_jobs(context: *mut dc_context_t) {
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::perform_imap_jobs(ctx))
.with_inner(|ctx| job::perform_inbox_jobs(ctx))
.unwrap_or(())
}
@@ -473,19 +482,20 @@ pub unsafe extern "C" fn dc_perform_imap_fetch(context: *mut dc_context_t) {
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::perform_imap_fetch(ctx))
.with_inner(|ctx| job::perform_inbox_fetch(ctx))
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_perform_imap_idle(context: *mut dc_context_t) {
// TODO rename function in co-ordination with UIs
if context.is_null() {
eprintln!("ignoring careless call to dc_perform_imap_idle()");
return;
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::perform_imap_idle(ctx))
.with_inner(|ctx| job::perform_inbox_idle(ctx))
.unwrap_or(())
}
@@ -497,7 +507,7 @@ pub unsafe extern "C" fn dc_interrupt_imap_idle(context: *mut dc_context_t) {
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::interrupt_imap_idle(ctx))
.with_inner(|ctx| job::interrupt_inbox_idle(ctx, true))
.unwrap_or(())
}
@@ -812,22 +822,53 @@ pub unsafe extern "C" fn dc_set_draft(
}
#[no_mangle]
pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut dc_msg_t) -> u32 {
if context.is_null() || msg.is_null() {
pub unsafe extern "C" fn dc_add_device_msg(
context: *mut dc_context_t,
label: *const libc::c_char,
msg: *mut dc_msg_t,
) -> u32 {
if context.is_null() || (label.is_null() && msg.is_null()) {
eprintln!("ignoring careless call to dc_add_device_msg()");
return 0;
}
let ffi_context = &mut *context;
let ffi_msg = &mut *msg;
let msg = if msg.is_null() {
None
} else {
let ffi_msg: &mut MessageWrapper = &mut *msg;
Some(&mut ffi_msg.message)
};
ffi_context
.with_inner(|ctx| {
chat::add_device_msg(ctx, &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to add device message")
chat::add_device_msg(
ctx,
to_opt_string_lossy(label).as_ref().map(|x| x.as_str()),
msg,
)
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_was_device_msg_ever_added(
context: *mut dc_context_t,
label: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || label.is_null() {
eprintln!("ignoring careless call to dc_was_device_msg_ever_added()");
return 0;
}
let ffi_context = &mut *context;
ffi_context
.with_inner(|ctx| {
chat::was_device_msg_ever_added(ctx, &to_string_lossy(label)).unwrap_or(false)
as libc::c_int
})
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t {
if context.is_null() {

View File

@@ -132,10 +132,8 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
real_spec = rs.unwrap();
}
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
if suffix == "eml" {
if dc_poke_eml_file(context, &real_spec).is_ok() {
read_cnt += 1
}
if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() {
read_cnt += 1
}
} else {
/* import a directory */
@@ -496,7 +494,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{:#?}", context.get_info());
}
"interrupt" => {
interrupt_imap_idle(context);
interrupt_inbox_idle(context, true);
}
"maybenetwork" => {
maybe_network(context);
@@ -588,7 +586,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let members = chat::get_chat_contacts(context, sel_chat.id);
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
let contact = Contact::get_by_id(context, members[0])?;
contact.get_addr().to_string()
} else {
@@ -837,7 +835,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(arg1.to_string()));
chat::add_device_msg(context, &mut msg)?;
chat::add_device_msg(context, None, Some(&mut msg))?;
}
"listmedia" => {
ensure!(sel_chat.is_some(), "No chat selected.");
@@ -862,11 +860,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"archive" | "unarchive" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = arg1.parse()?;
chat::archive(
context,
chat_id,
if arg0 == "archive" { true } else { false },
)?;
chat::archive(context, chat_id, arg0 == "archive")?;
}
"delchat" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");

View File

@@ -150,11 +150,11 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_imap = std::thread::spawn(move || loop {
while_running!({
perform_imap_jobs(&ctx.read().unwrap());
perform_imap_fetch(&ctx.read().unwrap());
perform_inbox_jobs(&ctx.read().unwrap());
perform_inbox_fetch(&ctx.read().unwrap());
while_running!({
let context = ctx.read().unwrap();
perform_imap_idle(&context);
perform_inbox_idle(&context);
});
});
});
@@ -202,7 +202,7 @@ fn stop_threads(context: &Context) {
println!("Stopping threads");
IS_RUNNING.store(false, Ordering::Relaxed);
interrupt_imap_idle(context);
interrupt_inbox_idle(context, true);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
interrupt_smtp_idle(context);
@@ -235,7 +235,7 @@ impl Completer for DcHelper {
}
}
const IMEX_COMMANDS: [&'static str; 12] = [
const IMEX_COMMANDS: [&str; 12] = [
"initiate-key-transfer",
"get-setupcodebegin",
"continue-key-transfer",
@@ -250,7 +250,7 @@ const IMEX_COMMANDS: [&'static str; 12] = [
"stop",
];
const DB_COMMANDS: [&'static str; 11] = [
const DB_COMMANDS: [&str; 11] = [
"info",
"open",
"close",
@@ -264,7 +264,7 @@ const DB_COMMANDS: [&'static str; 11] = [
"housekeeping",
];
const CHAT_COMMANDS: [&'static str; 24] = [
const CHAT_COMMANDS: [&str; 24] = [
"listchats",
"listarchived",
"chat",
@@ -290,7 +290,7 @@ const CHAT_COMMANDS: [&'static str; 24] = [
"unarchive",
"delchat",
];
const MESSAGE_COMMANDS: [&'static str; 8] = [
const MESSAGE_COMMANDS: [&str; 8] = [
"listmsgs",
"msginfo",
"listfresh",
@@ -300,7 +300,7 @@ const MESSAGE_COMMANDS: [&'static str; 8] = [
"unstar",
"delmsg",
];
const CONTACT_COMMANDS: [&'static str; 6] = [
const CONTACT_COMMANDS: [&str; 6] = [
"listcontacts",
"listverified",
"addcontact",
@@ -308,7 +308,7 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
"delcontact",
"cleanupcontacts",
];
const MISC_COMMANDS: [&'static str; 9] = [
const MISC_COMMANDS: [&str; 9] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
];
@@ -334,8 +334,8 @@ impl Hinter for DcHelper {
}
}
static COLORED_PROMPT: &'static str = "\x1b[1;32m> \x1b[0m";
static PROMPT: &'static str = "> ";
static COLORED_PROMPT: &str = "\x1b[1;32m> \x1b[0m";
static PROMPT: &str = "> ";
impl Highlighter for DcHelper {
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
@@ -455,9 +455,9 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
}
"imap-jobs" => {
if HANDLE.clone().lock().unwrap().is_some() {
println!("imap-jobs are already running in a thread.");
println!("inbox-jobs are already running in a thread.");
} else {
perform_imap_jobs(&ctx.read().unwrap());
perform_inbox_jobs(&ctx.read().unwrap());
}
}
"configure" => {

View File

@@ -11,7 +11,8 @@ use deltachat::configure::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::job::{
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
perform_inbox_fetch, perform_inbox_idle, perform_inbox_jobs, perform_smtp_idle,
perform_smtp_jobs,
};
use deltachat::Event;
@@ -50,12 +51,12 @@ fn main() {
let r1 = running.clone();
let t1 = thread::spawn(move || {
while *r1.read().unwrap() {
perform_imap_jobs(&ctx1);
perform_inbox_jobs(&ctx1);
if *r1.read().unwrap() {
perform_imap_fetch(&ctx1);
perform_inbox_fetch(&ctx1);
if *r1.read().unwrap() {
perform_imap_idle(&ctx1);
perform_inbox_idle(&ctx1);
}
}
}
@@ -112,8 +113,8 @@ fn main() {
println!("stopping threads");
*running.clone().write().unwrap() = false;
deltachat::job::interrupt_imap_idle(&ctx);
*running.write().unwrap() = false;
deltachat::job::interrupt_inbox_idle(&ctx, true);
deltachat::job::interrupt_smtp_idle(&ctx);
println!("joining");

View File

@@ -20,4 +20,4 @@ memmap = "0.7.0"
lazy_static = "1.3.0"
rand = "0.6.5"
chrono = "0.4.6"
hex = "0.3.2"
hex = "0.4.0"

View File

@@ -9,17 +9,23 @@ import subprocess
import sys
if __name__ == "__main__":
os.environ["DCC_RS_TARGET"] = target = "release"
target = os.environ.get("DCC_RS_TARGET")
if target is None:
os.environ["DCC_RS_TARGET"] = target = "release"
if "DCC_RS_DEV" not in os.environ:
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.environ["DCC_RS_DEV"] = dn
# build the core library in release + debug mode because
# as of Nov 2019 rPGP generates RSA keys which take
# prohibitively long for non-release installs
os.environ["RUSTFLAGS"] = "-g"
subprocess.check_call([
"cargo", "build", "-p", "deltachat_ffi", "--" + target
])
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-e", "."
])
if len(sys.argv) <= 1 or sys.argv[1] != "onlybuild":
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-e", "."
])

View File

@@ -29,7 +29,10 @@ def ffibuilder():
extra_link_args = []
else:
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
objs = [os.path.join(projdir, 'target', target, 'libdeltachat.a')]
target_dir = os.environ.get("CARGO_TARGET_DIR")
if target_dir is None:
target_dir = os.path.join(projdir, 'target')
objs = [os.path.join(target_dir, target, 'libdeltachat.a')]
assert os.path.exists(objs[0]), objs
incs = [os.path.join(projdir, 'deltachat-ffi')]
else:

View File

@@ -573,8 +573,10 @@ class IOThreads:
self._log_event("py-bindings-info", 0, "INBOX THREAD START")
while not self._thread_quitflag:
lib.dc_perform_imap_jobs(self._dc_context)
lib.dc_perform_imap_fetch(self._dc_context)
lib.dc_perform_imap_idle(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_imap_fetch(self._dc_context)
if not self._thread_quitflag:
lib.dc_perform_imap_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED")
def mvbox_thread_run(self):

View File

@@ -98,6 +98,7 @@ DC_EVENT_IMEX_PROGRESS = 2051
DC_EVENT_IMEX_FILE_WRITTEN = 2052
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
DC_EVENT_SECUREJOIN_MEMBER_ADDED = 2062
DC_EVENT_FILE_COPIED = 2055
DC_EVENT_IS_OFFLINE = 2081
DC_EVENT_GET_STRING = 2091
@@ -150,7 +151,8 @@ DC_STR_MSGLOCATIONENABLED = 64
DC_STR_MSGLOCATIONDISABLED = 65
DC_STR_LOCATION = 66
DC_STR_STICKER = 67
DC_STR_COUNT = 67
DC_STR_DEVICE_MESSAGES = 68
DC_STR_COUNT = 68
# end const generated

View File

@@ -21,7 +21,7 @@ class TestOfflineAccountBasic:
d = ac1.get_info()
assert d["arch"]
assert d["number_of_chats"] == "0"
assert d["bcc_self"] == "1"
assert d["bcc_self"] == "0"
def test_is_not_configured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
@@ -43,7 +43,7 @@ class TestOfflineAccountBasic:
def test_has_bccself(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
assert ac1.get_config("bcc_self") == "1"
assert ac1.get_config("bcc_self") == "0"
def test_selfcontact_if_unconfigured(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
@@ -405,6 +405,9 @@ class TestOnlineAccount:
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
lp.sec("ac1: setting bcc_self=1")
ac1.set_config("bcc_self", "1")
lp.sec("send out message with bcc to ourselves")
msg_out = chat.send_text("message2")
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -427,15 +430,25 @@ class TestOnlineAccount:
assert self_addr not in ev[2]
ev = ac1._evlogger.get_matching("DC_EVENT_DELETED_BLOB_FILE")
def test_mvbox_sentbox_threads(self, acfactory):
def test_mvbox_sentbox_threads(self, acfactory, lp):
lp.sec("ac1: start with mvbox thread")
ac1 = acfactory.get_online_configuring_account(mvbox=True, sentbox=True)
lp.sec("ac2: start without mvbox/sentbox threads")
ac2 = acfactory.get_online_configuring_account()
lp.sec("ac2: waiting for configuration")
wait_configuration_progress(ac2, 1000)
lp.sec("ac1: waiting for configuration")
wait_configuration_progress(ac1, 1000)
lp.sec("ac1: send message and wait for ac2 to receive it")
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
lp.sec("test finished")
def test_move_works(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
@@ -448,6 +461,20 @@ class TestOnlineAccount:
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
def test_move_works_on_self_sent(self, acfactory):
ac1 = acfactory.get_online_configuring_account(mvbox=True)
ac1.set_config("bcc_self", "1")
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
chat.send_text("message2")
chat.send_text("message3")
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
def test_forward_messages(self, acfactory):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
@@ -717,6 +744,7 @@ class TestOnlineAccount:
ac2._evlogger.set_timeout(30)
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("trigger ac setup message but ignore")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
ac1.initiate_key_transfer()
@@ -728,6 +756,7 @@ class TestOnlineAccount:
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
assert msg.get_setupcodebegin() == setup_code2[:2]
lp.sec("process second setup message")
msg.continue_key_transfer(setup_code2)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
@@ -753,6 +782,7 @@ class TestOnlineAccount:
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
wait_securejoin_inviter_progress(ac1, 1000)
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
def test_qr_verified_group_and_chatting(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()

View File

@@ -41,7 +41,7 @@ def test_dc_close_events(tmpdir):
else:
print("skipping event", *ev)
find("disconnecting INBOX-watch")
find("disconnecting inbox-thread")
find("disconnecting sentbox-thread")
find("disconnecting mvbox-thread")
find("disconnecting SMTP")

View File

@@ -1,19 +1,25 @@
[tox]
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
py35
py37
lint
auditwheels
[testenv]
commands =
pytest -v -rsXx {posargs:tests}
python tests/package_wheels.py {toxworkdir}/wheelhouse
# (some qr tests are pretty heavy in terms of send/received
# messages and async-imap's likely has concurrency problems,
# eg https://github.com/async-email/async-imap/issues/4 )
pytest -n6 --reruns 3 --reruns-delay 5 -v -rsXx -k "not qr" {posargs:tests}
pytest -n6 --reruns 5 --reruns-delay 5 -v -rsXx -k "qr" {posargs:tests}
# python tests/package_wheels.py {toxworkdir}/wheelhouse
passenv =
TRAVIS
DCC_RS_DEV
DCC_RS_TARGET
DCC_PY_LIVECONFIG
CARGO_TARGET_DIR
RUSTC_WRAPPER
deps =
pytest
pytest-rerunfailures
@@ -28,7 +34,6 @@ deps = auditwheel
commands =
python tests/auditwheels.py {toxworkdir}/wheelhouse
[testenv:lint]
skipsdist = True
usedevelop = True
@@ -43,18 +48,28 @@ commands =
rst-lint --encoding 'utf-8' README.rst
[testenv:doc]
basepython = python3.5
changedir=doc
deps =
sphinx==2.2.0
breathe
changedir = doc
commands =
sphinx-build -w docker-toxdoc-warnings.log -b html . _build/html
sphinx-build -Q -w toxdoc-warnings.log -b html . _build/html
[testenv:lintdoc]
skipsdist = True
usedevelop = True
deps =
{[testenv:lint]deps}
{[testenv:doc]deps}
commands =
{[testenv:lint]commands}
{[testenv:doc]commands}
[pytest]
addopts = -v -rs --reruns 3 --reruns-delay 2
addopts = -v -rs
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true

View File

@@ -1,3 +1,7 @@
//! # Autocrypt header module
//!
//! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
use std::collections::BTreeMap;
use std::ffi::CStr;
use std::str::FromStr;
@@ -46,7 +50,7 @@ impl str::FromStr for EncryptPreference {
}
}
/// Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
/// Autocrypt header
#[derive(Debug)]
pub struct Aheader {
pub addr: String,
@@ -55,6 +59,7 @@ pub struct Aheader {
}
impl Aheader {
/// Creates new autocrypt header
pub fn new(addr: String, public_key: Key, prefer_encrypt: EncryptPreference) -> Self {
Aheader {
addr,

View File

@@ -1,3 +1,5 @@
//! # Blob directory management
use std::ffi::OsStr;
use std::fmt;
use std::fs;
@@ -138,9 +140,10 @@ impl<'a> BlobObject<'a> {
context: &Context,
src: impl AsRef<Path>,
) -> std::result::Result<BlobObject, BlobError> {
match src.as_ref().starts_with(context.get_blobdir()) {
true => BlobObject::from_path(context, src),
false => BlobObject::create_and_copy(context, src),
if src.as_ref().starts_with(context.get_blobdir()) {
BlobObject::from_path(context, src)
} else {
BlobObject::create_and_copy(context, src)
}
}
@@ -262,13 +265,13 @@ impl<'a> BlobObject<'a> {
fn sanitise_name(name: &str) -> (String, String) {
let mut name = name.to_string();
for part in name.rsplit('/') {
if part.len() > 0 {
if !part.is_empty() {
name = part.to_string();
break;
}
}
for part in name.rsplit('\\') {
if part.len() > 0 {
if !part.is_empty() {
name = part.to_string();
break;
}
@@ -280,13 +283,13 @@ impl<'a> BlobObject<'a> {
};
let clean = sanitize_filename::sanitize_with_options(name, opts);
let mut iter = clean.rsplitn(2, '.');
let mut ext = iter.next().unwrap_or_default().to_string();
let mut iter = clean.splitn(2, '.');
let mut stem = iter.next().unwrap_or_default().to_string();
ext.truncate(32);
let mut ext = iter.next().unwrap_or_default().to_string();
stem.truncate(64);
match stem.len() {
0 => (ext, "".to_string()),
ext.truncate(32);
match ext.len() {
0 => (stem, "".to_string()),
_ => (stem, format!(".{}", ext).to_lowercase()),
}
}
@@ -607,6 +610,26 @@ mod tests {
}
}
#[test]
fn test_double_ext_preserved() {
let t = dummy_context();
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
let foo = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo.exists());
BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap();
for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() {
let fname = dirent.unwrap().file_name();
if fname == foo.file_name().unwrap() {
assert_eq!(fs::read(&foo).unwrap(), b"hello");
} else {
let name = fname.to_str().unwrap();
println!("{}", name);
assert!(name.starts_with("foo"));
assert!(name.ends_with(".tar.gz"));
}
}
}
#[test]
fn test_create_long_names() {
let t = dummy_context();

View File

@@ -1,6 +1,9 @@
//! # Chat module
use std::path::{Path, PathBuf};
use itertools::Itertools;
use num_traits::FromPrimitive;
use crate::blob::{BlobErrorKind, BlobObject};
use crate::chatlist::*;
@@ -36,6 +39,7 @@ pub struct Chat {
}
impl Chat {
/// Loads chat from the database by its ID.
pub fn load_from_db(context: &Context, chat_id: u32) -> Result<Self, Error> {
let res = context.sql.query_row(
"SELECT c.id,c.type,c.name, c.grpid,c.param,c.archived, \
@@ -96,7 +100,7 @@ impl Chat {
}
if chat.param.exists(Param::Selftalk) {
chat.name = context.stock_str(StockMessage::SelfMsg).into();
chat.name = context.stock_str(StockMessage::SavedMessages).into();
} else if chat.param.exists(Param::Devicetalk) {
chat.name = context.stock_str(StockMessage::DeviceMessages).into();
}
@@ -225,6 +229,7 @@ impl Chat {
color
}
/// Returns true if the chat is archived.
pub fn is_archived(&self) -> bool {
self.archived
}
@@ -598,6 +603,20 @@ fn copy_device_icon_to_blobs(context: &Context) -> Result<String, Error> {
Ok(blob.as_name().to_string())
}
pub fn update_saved_messages_icon(context: &Context) -> Result<(), Error> {
// if there is no saved-messages chat, there is nothing to update. this is no error.
if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF) {
let icon = include_bytes!("../assets/icon-saved-messages.png");
let blob = BlobObject::create(context, "icon-saved-messages.png".to_string(), icon)?;
let icon = blob.as_name().to_string();
let mut chat = Chat::load_from_db(context, chat_id)?;
chat.param.set(Param::ProfileImage, icon);
chat.update_param(context)?;
}
Ok(())
}
pub fn create_or_lookup_by_contact_id(
context: &Context,
contact_id: u32,
@@ -647,6 +666,10 @@ pub fn create_or_lookup_by_contact_id(
params![],
)?;
if contact_id == DC_CONTACT_ID_SELF {
update_saved_messages_icon(context)?;
}
Ok((chat_id, create_blocked))
}
@@ -689,6 +712,8 @@ pub fn prepare_msg<'a>(
pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
match msgtype {
Viewtype::Unknown => false,
Viewtype::Text => false,
Viewtype::Image => true,
Viewtype::Gif => true,
Viewtype::Sticker => true,
@@ -696,7 +721,6 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
Viewtype::Voice => true,
Viewtype::Video => true,
Viewtype::File => true,
_ => false,
}
}
@@ -796,7 +820,8 @@ pub fn is_contact_in_chat(context: &Context, chat_id: u32, contact_id: u32) -> b
.unwrap_or_default()
}
// Should return Result
// note that unarchive() is not the same as archive(false) -
// eg. unarchive() does not send events as done for archive(false).
pub fn unarchive(context: &Context, chat_id: u32) -> Result<(), Error> {
sql::execute(
context,
@@ -872,7 +897,7 @@ pub fn send_text_msg(
) -> Result<MsgId, Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
"bad chat_id = {} <= 9",
"bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL",
chat_id
);
@@ -1012,7 +1037,8 @@ pub fn get_chat_msgs(
Ok(ret)
};
let success = if chat_id == DC_CHAT_ID_DEADDROP {
let show_emails = context.get_config_int(Config::ShowEmails);
let show_emails =
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default();
context.sql.query_map(
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
@@ -1029,7 +1055,7 @@ pub fn get_chat_msgs(
" AND m.msgrmsg>=?",
" ORDER BY m.timestamp,m.id;"
),
params![if show_emails == 2 { 0 } else { 1 }],
params![if show_emails == ShowEmails::All { 0 } else { 1 }],
process_row,
process_rows,
)
@@ -1181,9 +1207,8 @@ pub fn get_chat_media(
|ids| {
let mut ret = Vec::new();
for id in ids {
match id {
Ok(msg_id) => ret.push(msg_id),
Err(_) => (),
if let Ok(msg_id) = id {
ret.push(msg_id)
}
}
Ok(ret)
@@ -1246,7 +1271,7 @@ pub fn get_next_media(
pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
"bad chat_id = {} <= 9",
"bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL",
chat_id
);
@@ -1281,7 +1306,7 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err
pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
"bad chat_id = {} <= 9",
"bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL",
chat_id
);
/* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */
@@ -1504,7 +1529,7 @@ pub(crate) fn add_contact_to_chat_ex(
msg.id = send_msg(context, chat_id, &mut msg)?;
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: MsgId::from(msg.id),
msg_id: msg.id,
});
}
context.call_cb(Event::MsgsChanged {
@@ -1570,7 +1595,7 @@ pub fn remove_contact_from_chat(
) -> Result<(), Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
"bad chat_id = {} <= 9",
"bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL",
chat_id
);
ensure!(
@@ -1926,37 +1951,78 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32,
.unwrap_or((0, false, Blocked::Not))
}
pub fn add_device_msg(context: &Context, msg: &mut Message) -> Result<MsgId, Error> {
pub fn add_device_msg(
context: &Context,
label: Option<&str>,
msg: Option<&mut Message>,
) -> Result<MsgId, Error> {
ensure!(
label.is_some() || msg.is_some(),
"device-messages need label, msg or both"
);
let (chat_id, _blocked) =
create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?;
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
let mut msg_id = MsgId::new_unset();
prepare_msg_blob(context, msg)?;
unarchive(context, chat_id)?;
if let Some(label) = label {
if was_device_msg_ever_added(context, label)? {
info!(context, "device-message {} already added", label);
return Ok(msg_id);
}
}
context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \
VALUES (?,?,?, ?,?,?, ?,?,?);",
params![
chat_id,
DC_CONTACT_ID_DEVICE,
DC_CONTACT_ID_SELF,
dc_create_smeared_timestamp(context),
msg.type_0,
MessageState::InFresh,
msg.text.as_ref().map_or("", String::as_str),
msg.param.to_string(),
rfc724_mid,
],
)?;
if let Some(msg) = msg {
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
prepare_msg_blob(context, msg)?;
unarchive(context, chat_id)?;
let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid);
let msg_id = MsgId::new(row_id);
context.call_cb(Event::IncomingMsg { chat_id, msg_id });
context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \
VALUES (?,?,?, ?,?,?, ?,?,?);",
params![
chat_id,
DC_CONTACT_ID_DEVICE,
DC_CONTACT_ID_SELF,
dc_create_smeared_timestamp(context),
msg.type_0,
MessageState::InFresh,
msg.text.as_ref().map_or("", String::as_str),
msg.param.to_string(),
rfc724_mid,
],
)?;
let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid);
msg_id = MsgId::new(row_id);
}
if let Some(label) = label {
context.sql.execute(
"INSERT INTO devmsglabels (label) VALUES (?);",
params![label],
)?;
}
if !msg_id.is_unset() {
context.call_cb(Event::IncomingMsg { chat_id, msg_id });
}
Ok(msg_id)
}
pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool, Error> {
ensure!(!label.is_empty(), "empty label");
if let Ok(()) = context.sql.query_row(
"SELECT label FROM devmsglabels WHERE label=?",
params![label],
|_| Ok(()),
) {
return Ok(true);
}
Ok(false)
}
pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
@@ -2034,4 +2100,210 @@ mod tests {
let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false).unwrap();
assert_eq!(added, false);
}
#[test]
fn test_self_talk() {
let t = dummy_context();
let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
assert_eq!(DC_CONTACT_ID_SELF, 1);
assert!(chat_id > DC_CHAT_ID_LAST_SPECIAL);
let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap();
assert_eq!(chat.id, chat_id);
assert!(chat.is_self_talk());
assert!(!chat.archived);
assert!(!chat.is_device_talk());
assert!(chat.can_send());
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages));
assert!(chat.get_profile_image(&t.ctx).is_some());
}
#[test]
fn test_deaddrop_chat() {
let t = dummy_context();
let chat = Chat::load_from_db(&t.ctx, DC_CHAT_ID_DEADDROP).unwrap();
assert_eq!(DC_CHAT_ID_DEADDROP, 1);
assert_eq!(chat.id, DC_CHAT_ID_DEADDROP);
assert!(!chat.is_self_talk());
assert!(!chat.archived);
assert!(!chat.is_device_talk());
assert!(!chat.can_send());
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop));
}
#[test]
fn test_add_device_msg_unlabelled() {
let t = test_context(Some(Box::new(logging_cb)));
// add two device-messages
let mut msg1 = Message::new(Viewtype::Text);
msg1.text = Some("first message".to_string());
let msg1_id = add_device_msg(&t.ctx, None, Some(&mut msg1));
assert!(msg1_id.is_ok());
let mut msg2 = Message::new(Viewtype::Text);
msg2.text = Some("second message".to_string());
let msg2_id = add_device_msg(&t.ctx, None, Some(&mut msg2));
assert!(msg2_id.is_ok());
assert_ne!(msg1_id.as_ref().unwrap(), msg2_id.as_ref().unwrap());
// check added messages
let msg1 = message::Message::load_from_db(&t.ctx, msg1_id.unwrap());
assert!(msg1.is_ok());
let msg1 = msg1.unwrap();
assert_eq!(msg1.text.as_ref().unwrap(), "first message");
assert_eq!(msg1.from_id, DC_CONTACT_ID_DEVICE);
assert_eq!(msg1.to_id, DC_CONTACT_ID_SELF);
assert!(!msg1.is_info());
assert!(!msg1.is_setupmessage());
let msg2 = message::Message::load_from_db(&t.ctx, msg2_id.unwrap());
assert!(msg2.is_ok());
let msg2 = msg2.unwrap();
assert_eq!(msg2.text.as_ref().unwrap(), "second message");
// check device chat
assert_eq!(get_msg_cnt(&t.ctx, msg2.chat_id), 2);
}
#[test]
fn test_add_device_msg_labelled() {
let t = test_context(Some(Box::new(logging_cb)));
// add two device-messages with the same label (second attempt is not added)
let mut msg1 = Message::new(Viewtype::Text);
msg1.text = Some("first message".to_string());
let msg1_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg1));
assert!(msg1_id.is_ok());
assert!(!msg1_id.as_ref().unwrap().is_unset());
let mut msg2 = Message::new(Viewtype::Text);
msg2.text = Some("second message".to_string());
let msg2_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2));
assert!(msg2_id.is_ok());
assert!(msg2_id.as_ref().unwrap().is_unset());
// check added message
let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap());
assert!(msg1.is_ok());
let msg1 = msg1.unwrap();
assert_eq!(msg1_id.as_ref().unwrap(), &msg1.id);
assert_eq!(msg1.text.as_ref().unwrap(), "first message");
assert_eq!(msg1.from_id, DC_CONTACT_ID_DEVICE);
assert_eq!(msg1.to_id, DC_CONTACT_ID_SELF);
assert!(!msg1.is_info());
assert!(!msg1.is_setupmessage());
// check device chat
let chat_id = msg1.chat_id;
assert_eq!(get_msg_cnt(&t.ctx, chat_id), 1);
assert!(chat_id > DC_CHAT_ID_LAST_SPECIAL);
let chat = Chat::load_from_db(&t.ctx, chat_id);
assert!(chat.is_ok());
let chat = chat.unwrap();
assert!(chat.is_device_talk());
assert!(!chat.is_self_talk());
assert!(!chat.can_send());
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeviceMessages));
assert!(chat.get_profile_image(&t.ctx).is_some());
// delete device message, make sure it is not added again
message::delete_msgs(&t.ctx, &[*msg1_id.as_ref().unwrap()]);
let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap());
assert!(msg1.is_err() || msg1.unwrap().chat_id == DC_CHAT_ID_TRASH);
let msg3_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2));
assert!(msg3_id.is_ok());
assert!(msg2_id.as_ref().unwrap().is_unset());
}
#[test]
fn test_add_device_msg_label_only() {
let t = test_context(Some(Box::new(logging_cb)));
let res = add_device_msg(&t.ctx, Some(""), None);
assert!(res.is_err());
let res = add_device_msg(&t.ctx, Some("some-label"), None);
assert!(res.is_ok());
let mut msg = Message::new(Viewtype::Text);
msg.text = Some("message text".to_string());
let msg_id = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg));
assert!(msg_id.is_ok());
assert!(msg_id.as_ref().unwrap().is_unset());
let msg_id = add_device_msg(&t.ctx, Some("unused-label"), Some(&mut msg));
assert!(msg_id.is_ok());
assert!(!msg_id.as_ref().unwrap().is_unset());
}
#[test]
fn test_was_device_msg_ever_added() {
let t = test_context(Some(Box::new(logging_cb)));
add_device_msg(&t.ctx, Some("some-label"), None).ok();
assert!(was_device_msg_ever_added(&t.ctx, "some-label").unwrap());
let mut msg = Message::new(Viewtype::Text);
msg.text = Some("message text".to_string());
add_device_msg(&t.ctx, Some("another-label"), Some(&mut msg)).ok();
assert!(was_device_msg_ever_added(&t.ctx, "another-label").unwrap());
assert!(!was_device_msg_ever_added(&t.ctx, "unused-label").unwrap());
assert!(was_device_msg_ever_added(&t.ctx, "").is_err());
}
fn chatlist_len(ctx: &Context, listflags: usize) -> usize {
Chatlist::try_load(ctx, listflags, None, None)
.unwrap()
.len()
}
#[test]
fn test_archive() {
// create two chats
let t = dummy_context();
let mut msg = Message::new(Viewtype::Text);
msg.text = Some("foo".to_string());
let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap();
let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id)
.unwrap()
.chat_id;
let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
assert!(chat_id1 > DC_CHAT_ID_LAST_SPECIAL);
assert!(chat_id2 > DC_CHAT_ID_LAST_SPECIAL);
assert_eq!(get_chat_cnt(&t.ctx), 2);
assert_eq!(chatlist_len(&t.ctx, 0), 2);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 2);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 0);
assert_eq!(DC_GCL_ARCHIVED_ONLY, 0x01);
assert_eq!(DC_GCL_NO_SPECIALS, 0x02);
// archive first chat
assert!(archive(&t.ctx, chat_id1, true).is_ok());
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived());
assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived());
assert_eq!(get_chat_cnt(&t.ctx), 2);
assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1);
// archive second chat
assert!(archive(&t.ctx, chat_id2, true).is_ok());
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived());
assert!(Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived());
assert_eq!(get_chat_cnt(&t.ctx), 2);
assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 0);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2);
// archive already archived first chat, unarchive second chat two times
assert!(archive(&t.ctx, chat_id1, true).is_ok());
assert!(archive(&t.ctx, chat_id2, false).is_ok());
assert!(archive(&t.ctx, chat_id2, false).is_ok());
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived());
assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived());
assert_eq!(get_chat_cnt(&t.ctx), 2);
assert_eq!(chatlist_len(&t.ctx, 0), 2);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1);
}
}

View File

@@ -1,3 +1,5 @@
//! # Chat list module
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
@@ -233,6 +235,7 @@ impl Chatlist {
self.ids.len()
}
/// Returns true if chatlist is empty.
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
@@ -329,6 +332,7 @@ impl Chatlist {
}
}
/// Get the number of archived chats
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql

View File

@@ -1,6 +1,9 @@
//! # Key-value configuration management
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use crate::blob::BlobObject;
use crate::constants::DC_VERSION_STR;
use crate::context::Context;
use crate::dc_tools::*;
@@ -31,7 +34,7 @@ pub enum Config {
Displayname,
Selfstatus,
Selfavatar,
#[strum(props(default = "1"))]
#[strum(props(default = "0"))]
BccSelf,
#[strum(props(default = "1"))]
E2eeEnabled,
@@ -113,13 +116,12 @@ impl Context {
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
match key {
Config::Selfavatar if value.is_some() => {
let rel_path = std::fs::canonicalize(value.unwrap())?;
self.sql
.set_raw_config(self, key, Some(&rel_path.to_string_lossy()))
let blob = BlobObject::create_from_path(&self, value.unwrap())?;
self.sql.set_raw_config(self, key, Some(blob.as_name()))
}
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
interrupt_imap_idle(self);
interrupt_inbox_idle(self, true);
ret
}
Config::SentboxWatch => {
@@ -165,6 +167,8 @@ mod tests {
use std::str::FromStr;
use std::string::ToString;
use crate::test_utils::*;
#[test]
fn test_to_string() {
assert_eq!(Config::MailServer.to_string(), "mail_server");
@@ -181,4 +185,32 @@ mod tests {
fn test_default_prop() {
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
}
#[test]
fn test_selfavatar() -> failure::Fallible<()> {
let t = dummy_context();
let avatar_src = t.dir.path().join("avatar.jpg");
std::fs::write(&avatar_src, b"avatar")?;
let avatar_blob = t.ctx.get_blobdir().join("avatar.jpg");
assert!(!avatar_blob.exists());
t.ctx
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
assert!(avatar_blob.exists());
assert_eq!(std::fs::read(&avatar_blob)?, b"avatar");
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
Ok(())
}
#[test]
fn test_selfavatar_in_blobdir() -> failure::Fallible<()> {
let t = dummy_context();
let avatar_src = t.ctx.get_blobdir().join("avatar.jpg");
std::fs::write(&avatar_src, b"avatar")?;
t.ctx
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))?;
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
Ok(())
}
}

View File

@@ -1,3 +1,4 @@
//! Thunderbird's Autoconfiguration implementation
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};

View File

@@ -1,3 +1,5 @@
//! Email accounts autoconfiguration process module
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::config::Config;
@@ -5,7 +7,6 @@ use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::imap::*;
use crate::job::*;
use crate::login_param::LoginParam;
use crate::oauth2::*;
@@ -44,9 +45,8 @@ pub fn dc_is_configured(context: &Context) -> bool {
/*******************************************************************************
* Configure JOB
******************************************************************************/
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
#[allow(non_snake_case, unused_must_use)]
pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
pub fn JobConfigureImap(context: &Context) {
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
progress!(context, 0);
@@ -62,7 +62,12 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
let mut param_autoconfig: Option<LoginParam> = None;
context.inbox.read().unwrap().disconnect(context);
context
.inbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context
.sentbox_thread
.read()
@@ -90,7 +95,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
const STEP_3_INDEX: u8 = 13;
let mut step_counter: u8 = 0;
while !context.shall_stop_ongoing() {
step_counter = step_counter + 1;
step_counter += 1;
let success = match step_counter {
// Read login parameters from the database
@@ -348,18 +353,14 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
}
16 => {
progress!(context, 900);
let flags: libc::c_int = if context.get_config_bool(Config::MvboxWatch)
|| context.get_config_bool(Config::MvboxMove)
{
DC_CREATE_MVBOX as i32
} else {
0
};
let create_mvbox = context.get_config_bool(Config::MvboxWatch)
|| context.get_config_bool(Config::MvboxMove);
context
.inbox
.inbox_thread
.read()
.unwrap()
.configure_folders(context, flags);
.imap
.configure_folders(context, create_mvbox);
true
}
17 => {
@@ -398,7 +399,12 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
}
}
if imap_connected_here {
context.inbox.read().unwrap().disconnect(context);
context
.inbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
}
if smtp_connected_here {
context.smtp.clone().lock().unwrap().disconnect();
@@ -494,7 +500,13 @@ fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
param.mail_user, param.mail_server, param.mail_port, param.server_flags
);
info!(context, "Trying: {}", inf);
if context.inbox.read().unwrap().connect(context, &param) {
if context
.inbox_thread
.read()
.unwrap()
.imap
.connect(context, &param)
{
info!(context, "success: {}", inf);
return Some(true);
}
@@ -563,28 +575,6 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
}
}
/*******************************************************************************
* Connect to configured account
******************************************************************************/
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
let mut ret_connected = 0;
if async_std::task::block_on(async move { imap.is_connected().await }) {
ret_connected = 1
} else if !context.sql.get_raw_config_bool(context, "configured") {
warn!(context, "Not configured, cannot connect.",);
} else {
let param = LoginParam::from_database(context, "configured_");
// the trailing underscore is correct
if imap.connect(context, &param) {
ret_connected = 2;
}
}
ret_connected
}
/*******************************************************************************
* Configure a Context
******************************************************************************/
@@ -610,7 +600,7 @@ pub fn read_autoconf_file(context: &Context, url: &str) -> Option<String> {
mod tests {
use crate::config::*;
use crate::configure::dc_job_do_DC_JOB_CONFIGURE_IMAP;
use crate::configure::JobConfigureImap;
use crate::test_utils::*;
#[test]
@@ -620,6 +610,6 @@ mod tests {
.set_config(Config::Addr, Some("probably@unexistant.addr"))
.unwrap();
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
JobConfigureImap(&t.ctx);
}
}

View File

@@ -1,4 +1,4 @@
//! Constants
//! # Constants
#![allow(non_camel_case_types, dead_code)]
use deltachat_derive::*;
@@ -8,21 +8,6 @@ lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
}
#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum MoveState {
Undefined = 0,
Pending = 1,
Stay = 2,
Moving = 3,
}
impl Default for MoveState {
fn default() -> Self {
MoveState::Undefined
}
}
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
@@ -134,8 +119,6 @@ pub const DC_CONTACT_ID_INFO: u32 = 2;
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;
// Flags for empty server job
pub const DC_EMPTY_MVBOX: u32 = 0x01;

View File

@@ -47,8 +47,8 @@ pub struct Contact {
/// May be empty, initially set to `authname`.
name: String,
/// Name authorized by the contact himself. Only this name may be spread to others,
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_name`,
/// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
/// to access this field.
authname: String,
/// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr`` to access this field.
addr: String,
@@ -585,7 +585,12 @@ impl Contact {
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() {
if peerstate.is_some()
&& peerstate
.as_ref()
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
.is_some()
{
let peerstate = peerstate.as_ref().unwrap();
let p =
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
@@ -605,11 +610,11 @@ impl Contact {
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
let fingerprint_other_verified = peerstate
.peek_key(2)
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
let fingerprint_other_unverified = peerstate
.peek_key(0)
.peek_key(PeerstateVerifiedStatus::Unverified)
.map(|k| k.formatted_fingerprint())
.unwrap_or_default();
if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() {
@@ -717,6 +722,7 @@ impl Contact {
&self.addr
}
/// Get name authorized by the contact.
pub fn get_authname(&self) -> &str {
&self.authname
}
@@ -875,7 +881,7 @@ impl Contact {
}
pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
if !context.sql.is_open() || contact_id <= 9 {
if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return false;
}
@@ -899,6 +905,7 @@ impl Contact {
}
}
/// Extracts first name from full name.
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
full_name.splitn(2, ' ').next().unwrap_or_default()
}
@@ -909,6 +916,7 @@ pub fn may_be_valid_addr(addr: &str) -> bool {
res.is_ok()
}
/// Returns address with whitespace trimmed and `mailto:` prefix removed.
pub fn addr_normalize(addr: &str) -> &str {
let norm = addr.trim();
@@ -920,7 +928,7 @@ pub fn addr_normalize(addr: &str) -> &str {
}
fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
if contact_id <= 9 {
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return;
}
@@ -1053,6 +1061,8 @@ fn split_address_book(book: &str) -> Vec<(&str, &str)> {
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_may_be_valid_addr() {
assert_eq!(may_be_valid_addr(""), false);
@@ -1094,4 +1104,109 @@ mod tests {
vec![("Name one", "Address one"), ("Name two", "Address two")]
)
}
#[test]
fn test_get_contacts() {
let context = dummy_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
}
#[test]
fn test_add_or_lookup() {
// add some contacts, this also tests add_address_book()
let t = dummy_context();
let book = concat!(
" Name one \n one@eins.org \n",
"Name two\ntwo@deux.net\n",
"\nthree@drei.sam\n",
"Name two\ntwo@deux.net\n" // should not be added again
);
assert_eq!(Contact::add_address_book(&t.ctx, book).unwrap(), 3);
// check first added contact, this does not modify because of lower origin
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t.ctx, "bla foo", "one@eins.org", Origin::IncomingUnknownTo)
.unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_id(), contact_id);
assert_eq!(contact.get_name(), "Name one");
assert_eq!(contact.get_display_name(), "Name one");
assert_eq!(contact.get_addr(), "one@eins.org");
assert_eq!(contact.get_name_n_addr(), "Name one (one@eins.org)");
// modify first added contact
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t.ctx,
"Real one",
" one@eins.org ",
Origin::ManuallyCreated,
)
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_name(), "Real one");
assert_eq!(contact.get_addr(), "one@eins.org");
assert!(!contact.is_blocked());
// check third added contact (contact without name)
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t.ctx, "", "three@drei.sam", Origin::IncomingUnknownTo)
.unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "three@drei.sam");
assert_eq!(contact.get_addr(), "three@drei.sam");
assert_eq!(contact.get_name_n_addr(), "three@drei.sam");
// add name to third contact from incoming message (this becomes authorized name)
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t.ctx,
"m. serious",
"three@drei.sam",
Origin::IncomingUnknownFrom,
)
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_name_n_addr(), "m. serious (three@drei.sam)");
assert!(!contact.is_blocked());
// manually edit name of third contact (does not changed authorized name)
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t.ctx,
"schnucki",
"three@drei.sam",
Origin::ManuallyCreated,
)
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
assert_eq!(contact.get_authname(), "m. serious");
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
assert!(!contact.is_blocked());
// check SELF
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
assert_eq!(DC_CONTACT_ID_SELF, 1);
assert_eq!(contact.get_name(), t.ctx.stock_str(StockMessage::SelfMsg));
assert_eq!(contact.get_addr(), ""); // we're not configured
assert!(!contact.is_blocked());
}
}

View File

@@ -39,12 +39,14 @@ pub type ContextCallback = dyn Fn(&Context, Event) -> uintptr_t + Send + Sync;
#[derive(DebugStub)]
pub struct Context {
/// Database file path
dbfile: PathBuf,
/// Blob directory path
blobdir: PathBuf,
pub sql: Sql,
pub inbox: Arc<RwLock<Imap>>,
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
pub probe_imap_network: Arc<RwLock<bool>>,
pub inbox_thread: Arc<RwLock<JobThread>>,
pub sentbox_thread: Arc<RwLock<JobThread>>,
pub mvbox_thread: Arc<RwLock<JobThread>>,
pub smtp: Arc<Mutex<Smtp>>,
@@ -55,7 +57,7 @@ pub struct Context {
pub os_name: Option<String>,
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
pub bob: Arc<RwLock<BobStatus>>,
pub last_smeared_timestamp: Arc<RwLock<i64>>,
pub last_smeared_timestamp: RwLock<i64>,
pub running_state: Arc<RwLock<RunningState>>,
/// Mutex to avoid generating the key for the user more than once.
pub generating_key_mutex: Mutex<()>,
@@ -93,6 +95,7 @@ pub fn get_info() -> HashMap<&'static str, String> {
}
impl Context {
/// Creates new context.
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
let mut blob_fname = OsString::new();
blob_fname.push(dbfile.file_name().unwrap_or_default());
@@ -118,7 +121,6 @@ impl Context {
let ctx = Context {
blobdir,
dbfile,
inbox: Arc::new(RwLock::new(Imap::new())),
cb,
os_name: Some(os_name),
running_state: Arc::new(RwLock::new(Default::default())),
@@ -127,8 +129,13 @@ impl Context {
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
oauth2_critical: Arc::new(Mutex::new(())),
bob: Arc::new(RwLock::new(Default::default())),
last_smeared_timestamp: Arc::new(RwLock::new(0)),
last_smeared_timestamp: RwLock::new(0),
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
inbox_thread: Arc::new(RwLock::new(JobThread::new(
"INBOX",
"configured_inbox_folder",
Imap::new(),
))),
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
"SENTBOX",
"configured_sentbox_folder",
@@ -153,10 +160,12 @@ impl Context {
Ok(ctx)
}
/// Returns database file path.
pub fn get_dbfile(&self) -> &Path {
self.dbfile.as_path()
}
/// Returns blob directory path.
pub fn get_blobdir(&self) -> &Path {
self.blobdir.as_path()
}
@@ -426,10 +435,9 @@ impl Context {
return;
}
if !self.is_inbox(folder) && !self.is_sentbox(folder) {
if self.is_mvbox(folder) {
return;
}
if let Ok(msg) = Message::load_from_db(self, msg_id) {
if msg.is_setupmessage() {
// do not move setup messages;
@@ -437,10 +445,6 @@ impl Context {
return;
}
if self.is_mvbox(folder) {
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Stay);
}
// 1 = dc message, 2 = reply to dc message
if 0 != msg.is_dc_message {
job_add(
@@ -450,7 +454,6 @@ impl Context {
Params::new(),
0,
);
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
}
}
}
@@ -458,8 +461,8 @@ impl Context {
impl Drop for Context {
fn drop(&mut self) {
info!(self, "disconnecting INBOX-watch",);
self.inbox.read().unwrap().disconnect(self);
info!(self, "disconnecting inbox-thread",);
self.inbox_thread.read().unwrap().imap.disconnect(self);
info!(self, "disconnecting sentbox-thread",);
self.sentbox_thread.read().unwrap().imap.disconnect(self);
info!(self, "disconnecting mvbox-thread",);
@@ -486,12 +489,25 @@ pub struct BobStatus {
pub qr_scan: Option<Lot>,
}
#[derive(Debug, PartialEq)]
pub enum PerformJobsNeeded {
Not,
AtOnce,
AvoidDos,
}
impl Default for PerformJobsNeeded {
fn default() -> Self {
Self::Not
}
}
#[derive(Default, Debug)]
pub struct SmtpState {
pub idle: bool,
pub suspended: bool,
pub doing_jobs: bool,
pub perform_jobs_needed: i32,
pub perform_jobs_needed: PerformJobsNeeded,
pub probe_network: bool,
}

View File

@@ -799,9 +799,12 @@ unsafe fn handle_reports(
&& !of_org_msgid.is_null()
&& !(*of_org_msgid).fld_value.is_null()
{
if let Ok(rfc724_mid) = wrapmime::parse_message_id(
&to_string_lossy((*of_org_msgid).fld_value),
) {
if let Ok(rfc724_mid) =
wrapmime::parse_message_id(std::slice::from_raw_parts(
(*of_org_msgid).fld_value as *const u8,
libc::strlen((*of_org_msgid).fld_value),
))
{
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
context,
from_id,
@@ -1637,7 +1640,11 @@ fn check_verified_properties(
info!(context, "{} has verified {}.", contact.get_addr(), to_addr,);
let fp = peerstate.gossip_key_fingerprint.clone();
if let Some(fp) = fp {
peerstate.set_verified(0, &fp, 2);
peerstate.set_verified(
DC_PS_GOSSIP_KEY,
&fp,
PeerstateVerifiedStatus::BidirectVerified,
);
peerstate.save_to_db(&context.sql, false)?;
is_verified = true;
}

View File

@@ -38,7 +38,7 @@ pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
fn must_encode(byte: u8) -> bool {
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
SPECIALS.into_iter().any(|b| *b == byte)
SPECIALS.iter().any(|b| *b == byte)
}
fn quote_word(word: &[u8]) -> String {

View File

@@ -1,7 +1,7 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use core::cmp::max;
use core::cmp::{max, min};
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
@@ -165,11 +165,26 @@ pub(crate) fn dc_gm2local_offset() -> i64 {
lt.offset().local_minus_utc() as i64
}
/* timesmearing */
// timesmearing
// - as e-mails typically only use a second-based-resolution for timestamps,
// the order of two mails sent withing one second is unclear.
// this is bad eg. when forwarding some messages from a chat -
// these messages will appear at the recipient easily out of order.
// - we work around this issue by not sending out two mails with the same timestamp.
// - for this purpose, in short, we track the last timestamp used in `last_smeared_timestamp`
// when another timestamp is needed in the same second, we use `last_smeared_timestamp+1`
// - after some moments without messages sent out,
// `last_smeared_timestamp` is again in sync with the normal time.
// - however, we do not do all this for the far future,
// but at max `MAX_SECONDS_TO_LEND_FROM_FUTURE`
const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 5;
// returns the currently smeared timestamp,
// may be used to check if call to dc_create_smeared_timestamp() is needed or not.
// the returned timestamp MUST NOT be used to be sent out or saved in the database!
pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
/* function returns a corrected time(NULL) */
let mut now = time();
let ts = *context.last_smeared_timestamp.clone().read().unwrap();
let ts = *context.last_smeared_timestamp.read().unwrap();
if ts >= now {
now = ts + 1;
}
@@ -177,32 +192,36 @@ pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
now
}
// returns a timestamp that is guaranteed to be unique.
pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 {
let now = time();
let mut ret = now;
let ts = *context.last_smeared_timestamp.clone().write().unwrap();
if ret <= ts {
ret = ts + 1;
if ret - now > 5 {
ret = now + 5
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
if ret <= *last_smeared_timestamp {
ret = *last_smeared_timestamp + 1;
if ret - now > MAX_SECONDS_TO_LEND_FROM_FUTURE {
ret = now + MAX_SECONDS_TO_LEND_FROM_FUTURE
}
}
*last_smeared_timestamp = ret;
ret
}
// creates `count` timestamps that are guaranteed to be unique.
// the frist created timestamps is returned directly,
// get the other timestamps just by adding 1..count-1
pub(crate) fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 {
/* get a range to timestamps that can be used uniquely */
let now = time();
let start = now + (if count < 5 { count } else { 5 }) as i64 - count as i64;
let count = count as i64;
let mut start = now + min(count, MAX_SECONDS_TO_LEND_FROM_FUTURE) - count;
let ts = *context.last_smeared_timestamp.clone().write().unwrap();
if ts + 1 > start {
ts + 1
} else {
start
}
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
start = max(*last_smeared_timestamp + 1, start);
*last_smeared_timestamp = start + count - 1;
start
}
/* Message-ID tools */
@@ -1355,4 +1374,34 @@ mod tests {
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
}
#[test]
fn test_create_smeared_timestamp() {
let t = dummy_context();
assert_ne!(
dc_create_smeared_timestamp(&t.ctx),
dc_create_smeared_timestamp(&t.ctx)
);
assert!(
dc_create_smeared_timestamp(&t.ctx)
>= SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as i64
);
}
#[test]
fn test_create_smeared_timestamps() {
let t = dummy_context();
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
let next = dc_smeared_time(&t.ctx);
assert!((start + count - 1) < next);
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30;
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
let next = dc_smeared_time(&t.ctx);
assert!((start + count - 1) < next);
}
}

View File

@@ -75,7 +75,7 @@ impl EncryptHelper {
&mut self,
factory: &mut MimeFactory,
e2ee_guaranteed: bool,
min_verified: libc::c_int,
min_verified: PeerstateVerifiedStatus,
do_gossip: bool,
mut in_out_message: *mut Mailmime,
imffields_unprotected: *mut mailimf_fields,
@@ -118,10 +118,10 @@ impl EncryptHelper {
return Ok(false);
}
if let Some(key) = peerstate.peek_key(min_verified as usize) {
if let Some(key) = peerstate.peek_key(min_verified) {
keyring.add_owned(key.clone());
if do_gossip {
if let Some(header) = peerstate.render_gossip_header(min_verified as usize) {
if let Some(header) = peerstate.render_gossip_header(min_verified) {
gossip_headers.push(header.to_string());
}
}

View File

@@ -34,6 +34,26 @@ pub enum Error {
BlobError(#[cause] crate::blob::BlobError),
#[fail(display = "Invalid Message ID.")]
InvalidMsgId,
#[fail(display = "Watch folder not found {:?}", _0)]
WatchFolderNotFound(String),
#[fail(display = "Connection Failed params: {}", _0)]
ImapConnectionFailed(String),
#[fail(display = "Could not get OAUTH token")]
ImapOauthError,
#[fail(display = "Could not login as {}", _0)]
ImapLoginFailed(String),
#[fail(display = "Cannot idle")]
ImapMissesIdle,
#[fail(display = "Imap IDLE protocol failed to init/complete")]
ImapIdleProtocolFailed(String),
#[fail(display = "Imap IDLE failed to select folder {:?}", _0)]
ImapSelectFailed(String),
#[fail(display = "Connect without configured params")]
ConnectWithoutConfigure,
#[fail(display = "imap operation attempted while imap is torn down")]
ImapInTeardown,
#[fail(display = "No IMAP Connection established")]
ImapNoConnection,
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -243,4 +243,11 @@ pub enum Event {
/// @return 0
#[strum(props(id = "2061"))]
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
/// This event is sent out to the inviter when a joiner successfully joined a group.
/// @param data1 (int) chat_id
/// @param data2 (int) contact_id
/// @return 0
#[strum(props(id = "2062"))]
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
}

View File

@@ -1,24 +1,29 @@
//! # Imap handling module
//!
//! uses [async-email/async-imap](https://github.com/async-email/async-imap)
//! to implement connect, fetch, delete functionality with standard IMAP servers.
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, SystemTime};
use async_imap::{
error::Result as ImapResult,
extensions::idle::IdleResponse,
types::{Fetch, Flag, Mailbox, Name, NameAttribute},
};
use async_std::prelude::*;
use async_std::sync::{Arc, Mutex, RwLock};
use async_std::sync::{Mutex, RwLock};
use async_std::task;
use crate::configure::dc_connect_to_configured_imap;
use crate::constants::*;
use crate::context::Context;
use crate::dc_receive_imf::dc_receive_imf;
use crate::error::Error;
use crate::error::{Error, Result};
use crate::events::Event;
use crate::imap_client::*;
use crate::job::{connect_to_inbox, job_add, Action};
use crate::job::{job_add, Action};
use crate::login_param::{CertificateChecks, LoginParam};
use crate::message::{self, update_msg_move_state, update_server_uid};
use crate::message::{self, update_server_uid};
use crate::oauth2::dc_get_oauth2_access_token;
use crate::param::Params;
use crate::stock::StockMessage;
@@ -40,11 +45,10 @@ const SELECT_ALL: &str = "1:*";
#[derive(Debug)]
pub struct Imap {
config: Arc<RwLock<ImapConfig>>,
session: Arc<Mutex<Option<Session>>>,
connected: Arc<Mutex<bool>>,
interrupt: Arc<Mutex<Option<stop_token::StopSource>>>,
config: RwLock<ImapConfig>,
session: Mutex<Option<Session>>,
connected: Mutex<bool>,
interrupt: Mutex<Option<stop_token::StopSource>>,
skip_next_idle_wait: AtomicBool,
should_reconnect: AtomicBool,
}
@@ -88,7 +92,6 @@ struct ImapConfig {
pub can_idle: bool,
pub has_xlist: bool,
pub imap_delimiter: char,
pub watch_folder: Option<String>,
}
impl Default for ImapConfig {
@@ -107,7 +110,6 @@ impl Default for ImapConfig {
can_idle: false,
has_xlist: false,
imap_delimiter: '.',
watch_folder: None,
}
}
}
@@ -115,10 +117,10 @@ impl Default for ImapConfig {
impl Imap {
pub fn new() -> Self {
Imap {
session: Arc::new(Mutex::new(None)),
config: Arc::new(RwLock::new(ImapConfig::default())),
interrupt: Arc::new(Mutex::new(None)),
connected: Arc::new(Mutex::new(false)),
session: Mutex::new(None),
config: RwLock::new(ImapConfig::default()),
interrupt: Mutex::new(None),
connected: Mutex::new(false),
skip_next_idle_wait: AtomicBool::new(false),
should_reconnect: AtomicBool::new(false),
}
@@ -132,19 +134,21 @@ impl Imap {
self.should_reconnect.load(Ordering::Relaxed)
}
fn setup_handle_if_needed(&self, context: &Context) -> bool {
pub fn trigger_reconnect(&self) {
self.should_reconnect.store(true, Ordering::Relaxed)
}
fn setup_handle_if_needed(&self, context: &Context) -> Result<()> {
task::block_on(async move {
if self.config.read().await.imap_server.is_empty() {
return false;
return Err(Error::ImapInTeardown);
}
if self.should_reconnect() {
self.unsetup_handle(context).await;
}
if self.is_connected().await {
self.should_reconnect.store(false, Ordering::Relaxed);
return true;
} else if self.is_connected().await {
return Ok(());
}
let server_flags = self.config.read().await.server_flags as i32;
@@ -172,13 +176,12 @@ impl Imap {
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
let res = Client::connect_secure(
Client::connect_secure(
(imap_server, imap_port),
imap_server,
config.certificate_checks,
)
.await;
res
.await
};
let login_res = match connection_res {
@@ -200,7 +203,7 @@ impl Imap {
let res = client.authenticate("XOAUTH2", &auth).await;
res
} else {
return false;
return Err(Error::ImapOauthError);
}
} else {
let res = client.login(imap_user, imap_pw).await;
@@ -208,18 +211,19 @@ impl Imap {
}
}
Err(err) => {
let config = self.config.read().await;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
let message = context.stock_string_repl_str2(
StockMessage::ServerResponse,
format!("{}:{}", imap_server, imap_port),
format!("{}", err),
);
let message = {
let config = self.config.read().await;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
context.stock_string_repl_str2(
StockMessage::ServerResponse,
format!("{}:{}", imap_server, imap_port),
err.to_string(),
)
};
// IMAP connection failures are reported to users
emit_event!(context, Event::ErrorNetwork(message));
return false;
return Err(Error::ImapConnectionFailed(err.to_string()));
}
};
@@ -228,7 +232,7 @@ impl Imap {
match login_res {
Ok(session) => {
*self.session.lock().await = Some(session);
true
Ok(())
}
Err((err, _)) => {
let imap_user = self.config.read().await.imap_user.to_owned();
@@ -239,9 +243,11 @@ impl Imap {
context,
Event::ErrorNetwork(format!("{} ({})", message, err))
);
self.unsetup_handle(context).await;
false
self.trigger_reconnect();
Err(Error::ImapLoginFailed(format!(
"cannot login as {}",
imap_user
)))
}
}
})
@@ -257,11 +263,12 @@ impl Imap {
warn!(context, "failed to close connection: {:?}", err);
}
}
*self.connected.lock().await = false;
info!(context, "IMAP unsetup_handle step 3 (clearing config).");
self.config.write().await.selected_folder = None;
self.config.write().await.selected_mailbox = None;
info!(context, "IMAP unsetup_handle step 4 (disconnected).");
info!(context, "IMAP unsetup_handle step 4 (disconnected)");
}
async fn free_connect_params(&self) {
@@ -275,20 +282,46 @@ impl Imap {
cfg.can_idle = false;
cfg.has_xlist = false;
cfg.watch_folder = None;
}
/// Connects to imap account using already-configured parameters.
pub fn connect_configured(&self, context: &Context) -> Result<()> {
if async_std::task::block_on(async move {
self.is_connected().await && !self.should_reconnect()
}) {
return Ok(());
}
if !context.sql.get_raw_config_bool(context, "configured") {
return Err(Error::ConnectWithoutConfigure);
}
let param = LoginParam::from_database(context, "configured_");
// the trailing underscore is correct
if self.connect(context, &param) {
if context
.sql
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
self.configure_folders(context, true);
}
return Ok(());
}
return Err(Error::ImapConnectionFailed(
format!("{}", param).to_string(),
));
}
/// tries connecting to imap account using the specific login
/// parameters
pub fn connect(&self, context: &Context, lp: &LoginParam) -> bool {
task::block_on(async move {
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
return false;
}
if self.is_connected().await {
return true;
}
{
let addr = &lp.addr;
let imap_server = &lp.mail_server;
@@ -307,7 +340,8 @@ impl Imap {
config.server_flags = server_flags;
}
if !self.setup_handle_if_needed(context) {
if let Err(err) = self.setup_handle_if_needed(context) {
warn!(context, "failed to setup imap handle: {}", err);
self.free_connect_params().await;
return false;
}
@@ -357,43 +391,26 @@ impl Imap {
pub fn disconnect(&self, context: &Context) {
task::block_on(async move {
if self.is_connected().await {
self.unsetup_handle(context).await;
self.free_connect_params().await;
*self.connected.lock().await = false;
}
self.unsetup_handle(context).await;
self.free_connect_params().await;
});
}
pub fn set_watch_folder(&self, watch_folder: String) {
pub fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> {
task::block_on(async move {
self.config.write().await.watch_folder = Some(watch_folder);
});
}
pub fn fetch(&self, context: &Context) -> bool {
task::block_on(async move {
if !self.is_connected().await || !context.sql.is_open() {
return false;
if !context.sql.is_open() {
// probably shutdown
return Err(Error::ImapInTeardown);
}
self.setup_handle_if_needed(context);
let watch_folder = self.config.read().await.watch_folder.to_owned();
if let Some(ref watch_folder) = watch_folder {
// as during the fetch commands, new messages may arrive, we fetch until we do not
// get any more. if IDLE is called directly after, there is only a small chance that
while self
.fetch_from_single_folder(context, &watch_folder)
.await?
{
// During the fetch commands new messages may arrive. So we fetch until we do not
// get any more. If IDLE is called directly after, there is only a small chance that
// messages are missed and delayed until the next IDLE call
loop {
if self.fetch_from_single_folder(context, watch_folder).await == 0 {
break;
}
}
true
} else {
false
}
Ok(())
})
}
@@ -462,7 +479,7 @@ impl Imap {
);
self.config.write().await.selected_folder = None;
self.should_reconnect.store(true, Ordering::Relaxed);
self.trigger_reconnect();
return ImapActionResult::Failed;
}
}
@@ -496,15 +513,14 @@ impl Imap {
}
}
async fn fetch_from_single_folder<S: AsRef<str>>(&self, context: &Context, folder: S) -> usize {
async fn fetch_from_single_folder<S: AsRef<str>>(
&self,
context: &Context,
folder: S,
) -> Result<bool> {
match self.select_folder(context, Some(&folder)).await {
ImapActionResult::Failed | ImapActionResult::RetryLater => {
warn!(
context,
"Cannot select folder \"{}\" for fetching.",
folder.as_ref()
);
return 0;
bail!("Cannot select folder {:?} for fetching.", folder.as_ref());
}
ImapActionResult::Success | ImapActionResult::AlreadyDone => {}
}
@@ -515,19 +531,24 @@ impl Imap {
let config = self.config.read().await;
let mailbox = config.selected_mailbox.as_ref().expect("just selected");
if mailbox.uid_validity.is_none() {
error!(
ensure!(
mailbox.uid_validity.is_some(),
"Cannot get UIDVALIDITY for folder {:?}",
folder.as_ref()
);
let new_uid_validity = mailbox.uid_validity.unwrap();
if new_uid_validity != uid_validity {
// First time this folder is selected or UIDVALIDITY has changed.
// Init lastseenuid and save it to config.
info!(
context,
"Cannot get UIDVALIDITY for folder \"{}\".",
folder.as_ref(),
"new_uid_validity={} current local uid_validity={} lastseenuid={}",
new_uid_validity,
uid_validity,
last_seen_uid
);
return 0;
}
if mailbox.uid_validity.unwrap_or_default() != uid_validity {
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
if mailbox.exists == 0 {
info!(context, "Folder \"{}\" is empty.", folder.as_ref());
@@ -535,13 +556,8 @@ impl Imap {
// id we do not do this here, we'll miss the first message
// as we will get in here again and fetch from lastseenuid+1 then
self.set_config_last_seen_uid(
context,
&folder,
mailbox.uid_validity.unwrap_or_default(),
0,
);
return 0;
self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0);
return Ok(false);
}
let list = if let Some(ref mut session) = &mut *self.session.lock().await {
@@ -549,19 +565,12 @@ impl Imap {
let set = format!("{}", mailbox.exists);
match session.fetch(set, PREFETCH_FLAGS).await {
Ok(list) => list,
Err(_err) => {
self.should_reconnect.store(true, Ordering::Relaxed);
info!(
context,
"No result returned for folder \"{}\".",
folder.as_ref()
);
return 0;
Err(err) => {
bail!("fetch failed: {}", err);
}
}
} else {
return 0;
return Err(Error::ImapNoConnection);
};
last_seen_uid = list[0].uid.unwrap_or_else(|| 0);
@@ -571,7 +580,7 @@ impl Imap {
last_seen_uid -= 1;
}
uid_validity = mailbox.uid_validity.unwrap_or_default();
uid_validity = new_uid_validity;
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
info!(
context,
@@ -593,12 +602,11 @@ impl Imap {
match session.uid_fetch(set, PREFETCH_FLAGS).await {
Ok(list) => list,
Err(err) => {
warn!(context, "failed to fetch uids: {}", err);
return 0;
bail!("uid_fetch failed: {}", err);
}
}
} else {
return 0;
return Err(Error::ImapNoConnection);
};
// go through all mails in folder (this is typically _fast_ as we already have the whole list)
@@ -659,7 +667,7 @@ impl Imap {
);
}
read_cnt
Ok(read_cnt > 0)
}
fn set_config_last_seen_uid<S: AsRef<str>>(
@@ -694,20 +702,23 @@ impl Imap {
match session.uid_fetch(set, BODY_FLAGS).await {
Ok(msgs) => msgs,
Err(err) => {
self.should_reconnect.store(true, Ordering::Relaxed);
// TODO maybe differentiate between IO and input/parsing problems
// so we don't reconnect if we have a (rare) input/output parsing problem?
self.trigger_reconnect();
warn!(
context,
"Error on fetching message #{} from folder \"{}\"; retry={}; error={}.",
"Error on fetching message #{} from folder \"{}\"; error={}.",
server_uid,
folder.as_ref(),
self.should_reconnect(),
err
);
return 0;
}
}
} else {
return 1;
// we could not get a valid imap session, this should be retried
self.trigger_reconnect();
return 0;
};
if msgs.is_empty() {
@@ -743,33 +754,19 @@ impl Imap {
1
}
pub fn idle(&self, context: &Context) {
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<()> {
task::block_on(async move {
if self.config.read().await.selected_folder.is_none() {
// this probably means that we are in teardown
// in any case we can't perform any idling
return;
}
if !self.config.read().await.can_idle {
self.fake_idle(context, true).await;
return;
return Err(Error::ImapMissesIdle);
}
self.setup_handle_if_needed(context);
self.setup_handle_if_needed(context)?;
let watch_folder = self.config.read().await.watch_folder.clone();
match self.select_folder(context, watch_folder.as_ref()).await {
match self.select_folder(context, watch_folder.clone()).await {
ImapActionResult::Success | ImapActionResult::AlreadyDone => {}
ImapActionResult::Failed | ImapActionResult::RetryLater => {
warn!(
context,
"idle select_folder failed {:?}",
watch_folder.as_ref()
);
self.fake_idle(context, true).await;
return;
return Err(Error::ImapSelectFailed(format!("{:?}", watch_folder)));
}
}
@@ -777,134 +774,187 @@ impl Imap {
let timeout = Duration::from_secs(23 * 60);
if let Some(session) = session {
match session.idle() {
// BEWARE: If you change the Secure branch you
// typically also need to change the Insecure branch.
IdleHandle::Secure(mut handle) => {
if let Err(err) = handle.init().await {
warn!(context, "Failed to establish IDLE connection: {:?}", err);
return;
return Err(Error::ImapIdleProtocolFailed(err.to_string()));
}
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
*self.interrupt.lock().await = Some(interrupt);
if self.skip_next_idle_wait.load(Ordering::Relaxed) {
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
// interrupt_idle has happened before we
// provided self.interrupt
self.skip_next_idle_wait.store(false, Ordering::Relaxed);
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
std::mem::drop(idle_wait);
info!(context, "Idle wait was skipped");
} else {
info!(context, "Idle entering wait-on-remote state");
let res = idle_wait.await;
info!(context, "Idle finished wait-on-remote: {:?}", res);
match idle_wait.await {
IdleResponse::NewData(_) => {
info!(context, "Idle has NewData");
}
// TODO: idle_wait does not distinguish manual interrupts
// from Timeouts if we would know it's a Timeout we could bail
// directly and reconnect .
IdleResponse::Timeout => {
info!(context, "Idle-wait timeout or interruption");
}
IdleResponse::ManualInterrupt => {
info!(context, "Idle wait was interrupted");
}
}
}
match handle.done().await {
Ok(session) => {
*self.session.lock().await = Some(Session::Secure(session));
}
Err(err) => {
warn!(context, "Failed to close IMAP IDLE connection: {:?}", err);
// if we cannot terminate IDLE it probably
// means that we waited long (with idle_wait)
// but the network went away/changed
self.trigger_reconnect();
return Err(Error::ImapIdleProtocolFailed(err.to_string()));
}
}
}
IdleHandle::Insecure(mut handle) => {
if let Err(err) = handle.init().await {
warn!(context, "Failed to establish IDLE connection: {:?}", err);
return;
return Err(Error::ImapIdleProtocolFailed(err.to_string()));
}
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
*self.interrupt.lock().await = Some(interrupt);
if self.skip_next_idle_wait.load(Ordering::Relaxed) {
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
// interrupt_idle has happened before we
// provided self.interrupt
self.skip_next_idle_wait.store(false, Ordering::Relaxed);
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
std::mem::drop(idle_wait);
info!(context, "Idle wait was skipped");
} else {
info!(context, "Idle entering wait-on-remote state");
let res = idle_wait.await;
info!(context, "Idle finished wait-on-remote: {:?}", res);
match idle_wait.await {
IdleResponse::NewData(_) => {
info!(context, "Idle has NewData");
}
// TODO: idle_wait does not distinguish manual interrupts
// from Timeouts if we would know it's a Timeout we could bail
// directly and reconnect .
IdleResponse::Timeout => {
info!(context, "Idle-wait timeout or interruption");
}
IdleResponse::ManualInterrupt => {
info!(context, "Idle wait was interrupted");
}
}
}
match handle.done().await {
Ok(session) => {
*self.session.lock().await = Some(Session::Insecure(session));
}
Err(err) => {
warn!(context, "Failed to close IMAP IDLE connection: {:?}", err);
// if we cannot terminate IDLE it probably
// means that we waited long (with idle_wait)
// but the network went away/changed
self.trigger_reconnect();
return Err(Error::ImapIdleProtocolFailed(err.to_string()));
}
}
}
}
}
});
Ok(())
})
}
pub(crate) async fn fake_idle(&self, context: &Context, use_network: bool) {
// Idle using timeouts. This is also needed if we're not yet configured -
// in this case, we're waiting for a configure job
let fake_idle_start_time = SystemTime::now();
info!(context, "IMAP-fake-IDLEing...");
pub(crate) fn fake_idle(&self, context: &Context, watch_folder: Option<String>) {
// Idle using polling. This is also needed if we're not yet configured -
// in this case, we're waiting for a configure job (and an interrupt).
task::block_on(async move {
let fake_idle_start_time = SystemTime::now();
info!(context, "IMAP-fake-IDLEing...");
let interrupt = stop_token::StopSource::new();
// we use 1000 minutes if we are told to not try network
// which can happen because the watch_folder is not defined
// but clients are still calling us in a loop.
// if we are to use network, we check every minute if there
// is new mail -- TODO: make this more flexible
let secs = if use_network { 60 } else { 60000 };
let interval = async_std::stream::interval(Duration::from_secs(secs));
// check every minute if there are new messages
// TODO: grow sleep durations / make them more flexible
let interval = async_std::stream::interval(Duration::from_secs(60));
let mut interrupt_interval = interrupt.stop_token().stop_stream(interval);
*self.interrupt.lock().await = Some(interrupt);
while let Some(_) = interrupt_interval.next().await {
if !use_network {
continue;
}
if !self.is_connected().await {
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
// interrupt_idle has happened before we
// provided self.interrupt
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
info!(context, "fake-idle wait was skipped");
} else {
// loop until we are interrupted or if we fetched something
while let Some(_) = interrupt_interval.next().await {
// try to connect with proper login params
// (setup_handle_if_needed might not know about them if we
// never successfully connected)
if dc_connect_to_configured_imap(context, &self) != 0 {
self.interrupt.lock().await.take();
if let Err(err) = self.connect_configured(context) {
warn!(context, "fake_idle: could not connect: {}", err);
continue;
}
}
// we are connected, let's see if fetching messages results
// in anything. If so, we behave as if IDLE had data but
// will have already fetched the messages so perform_*_fetch
// will not find any new.
let watch_folder = self.config.read().await.watch_folder.clone();
if let Some(watch_folder) = watch_folder {
if 0 != self.fetch_from_single_folder(context, watch_folder).await {
self.interrupt.lock().await.take();
if self.config.read().await.can_idle {
// we only fake-idled because network was gone during IDLE, probably
break;
}
info!(context, "fake_idle is connected");
// we are connected, let's see if fetching messages results
// in anything. If so, we behave as if IDLE had data but
// will have already fetched the messages so perform_*_fetch
// will not find any new.
if let Some(ref watch_folder) = watch_folder {
match self.fetch_from_single_folder(context, watch_folder).await {
Ok(res) => {
info!(context, "fetch_from_single_folder returned {:?}", res);
if res {
break;
}
}
Err(err) => {
error!(context, "could not fetch from folder: {}", err);
self.trigger_reconnect()
}
}
}
}
}
});
self.interrupt.lock().await.take();
info!(
context,
"IMAP-fake-IDLE done after {:.4}s",
SystemTime::now()
.duration_since(fake_idle_start_time)
.unwrap()
.as_millis() as f64
/ 1000.,
);
info!(
context,
"IMAP-fake-IDLE done after {:.4}s",
SystemTime::now()
.duration_since(fake_idle_start_time)
.unwrap()
.as_millis() as f64
/ 1000.,
);
})
}
pub fn interrupt_idle(&self) {
task::block_on(async move {
if self.interrupt.lock().await.take().is_none() {
let mut interrupt: Option<stop_token::StopSource> = self.interrupt.lock().await.take();
if interrupt.is_none() {
// idle wait is not running, signal it needs to skip
self.skip_next_idle_wait.store(true, Ordering::Relaxed);
self.skip_next_idle_wait.store(true, Ordering::SeqCst);
// meanwhile idle-wait may have produced the interrupter
let _ = self.interrupt.lock().await.take();
// meanwhile idle-wait may have produced the StopSource
interrupt = self.interrupt.lock().await.take();
}
// let's manually drop the StopSource
if interrupt.is_some() {
eprintln!("low-level: dropping stop-source to interrupt idle");
std::mem::drop(interrupt)
}
});
}
@@ -1035,9 +1085,14 @@ impl Imap {
task::block_on(async move {
if uid == 0 {
return Some(ImapActionResult::Failed);
} else if !self.is_connected().await {
connect_to_inbox(context, &self);
if !self.is_connected().await {
}
if !self.is_connected().await {
// currently jobs are only performed on the INBOX thread
// TODO: make INBOX/SENT/MVBOX perform the jobs on their
// respective folders to avoid select_folder network traffic
// and the involved error states
if let Err(err) = self.connect_configured(context) {
warn!(context, "prepare_imap_op failed: {}", err);
return Some(ImapActionResult::RetryLater);
}
}
@@ -1151,7 +1206,7 @@ impl Imap {
})
}
pub fn configure_folders(&self, context: &Context, flags: libc::c_int) {
pub fn configure_folders(&self, context: &Context, create_mvbox: bool) {
task::block_on(async move {
if !self.is_connected().await {
return;
@@ -1182,7 +1237,7 @@ impl Imap {
.find(|folder| folder.name() == "DeltaChat" || folder.name() == fallback_folder)
.map(|n| n.name().to_string());
if mvbox_folder.is_none() && 0 != (flags as usize & DC_CREATE_MVBOX) {
if mvbox_folder.is_none() && create_mvbox {
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
match session.create("DeltaChat").await {
@@ -1220,10 +1275,9 @@ impl Imap {
session.subscribe(mvbox).await.expect("failed to subscribe");
}
}
context
.sql
.set_raw_config_int(context, "folders_configured", 3)
.set_raw_config(context, "configured_inbox_folder", Some("INBOX"))
.ok();
if let Some(ref mvbox_folder) = mvbox_folder {
context
@@ -1241,6 +1295,10 @@ impl Imap {
)
.ok();
}
context
.sql
.set_raw_config_int(context, "folders_configured", 3)
.ok();
}
})
}
@@ -1355,6 +1413,7 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
{
if old_server_folder.is_empty() && old_server_uid == 0 {
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
context.do_heuristics_moves(server_folder.as_ref(), msg_id);
job_add(
context,
Action::MarkseenMsgOnImap,
@@ -1364,7 +1423,6 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
);
} else if old_server_folder != server_folder {
info!(context, "[move] detected moved message {}", rfc724_mid,);
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
}
if old_server_folder != server_folder || old_server_uid != server_uid {
@@ -1376,7 +1434,12 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
}
}
fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String, Error> {
let message_id = prefetch_msg.envelope().unwrap().message_id.unwrap();
wrapmime::parse_message_id(&message_id)
fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String> {
ensure!(
prefetch_msg.envelope().is_some(),
"Fetched message has no envelope"
);
let message_id = prefetch_msg.envelope().unwrap().message_id;
ensure!(message_id.is_some(), "No message ID found");
wrapmime::parse_message_id(&message_id.unwrap())
}

View File

@@ -1,3 +1,5 @@
//! # Import/export module
use core::cmp::{max, min};
use std::path::Path;
@@ -49,7 +51,7 @@ pub enum ImexMode {
/// Import/export things.
/// For this purpose, the function creates a job that is executed in the IMAP-thread then;
/// this requires to call dc_perform_imap_jobs() regularly.
/// this requires to call dc_perform_inbox_jobs() regularly.
///
/// What to do is defined by the _what_ parameter.
///
@@ -155,6 +157,11 @@ fn do_initiate_key_transfer(context: &Context) -> Result<String> {
}
}
}
// no maybe_add_bcc_self_device_msg() here.
// the ui shows the dialog with the setup code on this device,
// it would be too much noise to have two things popping up at the same time.
// maybe_add_bcc_self_device_msg() is called on the other device
// once the transfer is completed.
Ok(setup_code)
}
@@ -230,6 +237,21 @@ pub fn create_setup_code(_context: &Context) -> String {
ret
}
fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
if !context.sql.get_raw_config_bool(context, "bcc_self") {
let mut msg = Message::new(Viewtype::Text);
// TODO: define this as a stockstring once the wording is settled.
msg.text = Some(
"It seems you are using multiple devices with Delta Chat. Great!\n\n\
If you also want to synchronize outgoing messages accross all devices, \
go to the settings and enable \"Send copy to self\"."
.to_string(),
);
chat::add_device_msg(context, Some("bcc-self-hint"), Some(&mut msg))?;
}
Ok(())
}
pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> {
ensure!(!msg_id.is_special(), "wrong id");
@@ -244,6 +266,7 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str)
let sc = normalize_setup_code(setup_code);
let armored_key = decrypt_setup_file(context, &sc, file)?;
set_self_key(context, &armored_key, true, true)?;
maybe_add_bcc_self_device_msg(context)?;
Ok(())
} else {
@@ -344,7 +367,7 @@ pub fn normalize_setup_code(s: &str) -> String {
}
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
let param = job.param.get(Param::Arg).unwrap_or_default();

View File

@@ -8,7 +8,7 @@ use crate::chat;
use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::context::{Context, PerformJobsNeeded};
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
@@ -22,6 +22,9 @@ use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
use crate::param::*;
use crate::sql;
// results in ~3 weeks for the last backoff timespan
const JOB_RETRIES: u32 = 17;
/// Thread IDs
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
@@ -31,6 +34,13 @@ enum Thread {
Smtp = 5000,
}
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
enum TryAgain {
Dont,
AtOnce,
StandardDelay,
}
impl Default for Thread {
fn default() -> Self {
Thread::Unknown
@@ -100,9 +110,9 @@ pub struct Job {
pub foreign_id: u32,
pub desired_timestamp: i64,
pub added_timestamp: i64,
pub tries: i32,
pub tries: u32,
pub param: Params,
pub try_again: i32,
try_again: TryAgain,
pub pending_error: Option<String>,
}
@@ -130,13 +140,13 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_SEND(&mut self, context: &Context) {
fn SendMsgToSmtp(&mut self, context: &Context) {
/* connect to SMTP server, if not yet done */
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = LoginParam::from_database(context, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if connected.is_err() {
self.try_again_later(3, None);
self.try_again_later(TryAgain::StandardDelay, None);
return;
}
}
@@ -177,28 +187,12 @@ impl Job {
Err(err) => {
smtp.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
}
Ok(()) => {
// smtp success, update db ASAP, then delete smtp file
if 0 != self.foreign_id {
message::update_msg_state(
context,
MsgId::new(self.foreign_id),
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: MsgId::new(self.foreign_id),
});
set_delivered(context, MsgId::new(self.foreign_id));
}
// now also delete the generated file
dc_delete_file(context, filename);
@@ -212,14 +206,14 @@ impl Job {
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
fn try_again_later(&mut self, try_again: TryAgain, pending_error: Option<String>) {
self.try_again = try_again;
self.pending_error = pending_error;
}
#[allow(non_snake_case)]
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
fn MoveMsg(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if context
@@ -228,7 +222,7 @@ impl Job {
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
imap_inbox.configure_folders(context, true);
}
let dest_folder = context
.sql
@@ -238,7 +232,7 @@ impl Job {
let server_folder = msg.server_folder.as_ref().unwrap();
let mut dest_uid = 0;
match inbox.mv(
match imap_inbox.mv(
context,
server_folder,
msg.server_uid,
@@ -246,7 +240,7 @@ impl Job {
&mut dest_uid,
) {
ImapActionResult::RetryLater => {
self.try_again_later(3i32, None);
self.try_again_later(TryAgain::StandardDelay, None);
}
ImapActionResult::Success => {
message::update_server_uid(
@@ -263,8 +257,8 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
fn DeleteMsgOnImap(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
if !msg.rfc724_mid.is_empty() {
@@ -279,9 +273,10 @@ impl Job {
we delete the message from the server */
let mid = msg.rfc724_mid;
let server_folder = msg.server_folder.as_ref().unwrap();
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
let res =
imap_inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
if res == ImapActionResult::RetryLater {
self.try_again_later(-1i32, None);
self.try_again_later(TryAgain::AtOnce, None);
return;
}
}
@@ -291,30 +286,30 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_EMPTY_SERVER(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
fn EmptyServer(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
if let Some(mvbox_folder) = context
.sql
.get_raw_config(context, "configured_mvbox_folder")
{
inbox.empty_folder(context, &mvbox_folder);
imap_inbox.empty_folder(context, &mvbox_folder);
}
}
if self.foreign_id & DC_EMPTY_INBOX > 0 {
inbox.empty_folder(context, "INBOX");
imap_inbox.empty_folder(context, "INBOX");
}
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let inbox = context.inbox.read().unwrap();
fn MarkseenMsgOnImap(&mut self, context: &Context) {
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, folder, msg.server_uid) {
match imap_inbox.set_seen(context, folder, msg.server_uid) {
ImapActionResult::RetryLater => {
self.try_again_later(3i32, None);
self.try_again_later(TryAgain::StandardDelay, None);
}
ImapActionResult::AlreadyDone => {}
ImapActionResult::Success | ImapActionResult::Failed => {
@@ -335,16 +330,16 @@ impl Job {
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
fn MarkseenMdnOnImap(&mut self, context: &Context) {
let folder = self
.param
.get(Param::ServerFolder)
.unwrap_or_default()
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let inbox = context.inbox.read().unwrap();
if inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
self.try_again_later(3i32, None);
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
if imap_inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
self.try_again_later(TryAgain::StandardDelay, None);
return;
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
@@ -354,7 +349,7 @@ impl Job {
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
imap_inbox.configure_folders(context, true);
}
let dest_folder = context
.sql
@@ -362,9 +357,9 @@ impl Job {
if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0;
if ImapActionResult::RetryLater
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
== imap_inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(3, None);
self.try_again_later(TryAgain::StandardDelay, None);
}
}
}
@@ -382,45 +377,14 @@ pub fn job_kill_action(context: &Context, action: Action) -> bool {
.is_ok()
}
pub fn perform_imap_fetch(context: &Context) {
let inbox = context.inbox.read().unwrap();
let start = std::time::Instant::now();
pub fn perform_inbox_fetch(context: &Context) {
let use_network = context.get_config_bool(Config::InboxWatch);
if 0 == connect_to_inbox(context, &inbox) {
return;
}
if !context.get_config_bool(Config::InboxWatch) {
info!(context, "INBOX-watch disabled.",);
return;
}
info!(context, "INBOX-fetch started...",);
inbox.fetch(context);
if inbox.should_reconnect() {
info!(context, "INBOX-fetch aborted, starting over...",);
inbox.fetch(context);
}
info!(
context,
"INBOX-fetch done in {:.4} ms.",
start.elapsed().as_nanos() as f64 / 1_000_000.0,
);
}
pub fn perform_imap_idle(context: &Context) {
let inbox = context.inbox.read().unwrap();
connect_to_inbox(context, &inbox);
if *context.perform_inbox_jobs_needed.clone().read().unwrap() {
info!(
context,
"INBOX-IDLE will not be started because of waiting jobs."
);
return;
}
info!(context, "INBOX-IDLE started...");
inbox.idle(context);
info!(context, "INBOX-IDLE ended.");
context
.inbox_thread
.write()
.unwrap()
.fetch(context, use_network);
}
pub fn perform_mvbox_fetch(context: &Context) {
@@ -433,20 +397,6 @@ pub fn perform_mvbox_fetch(context: &Context) {
.fetch(context, use_network);
}
pub fn perform_mvbox_idle(context: &Context) {
let use_network = context.get_config_bool(Config::MvboxWatch);
context
.mvbox_thread
.read()
.unwrap()
.idle(context, use_network);
}
pub fn interrupt_mvbox_idle(context: &Context) {
context.mvbox_thread.read().unwrap().interrupt_idle(context);
}
pub fn perform_sentbox_fetch(context: &Context) {
let use_network = context.get_config_bool(Config::SentboxWatch);
@@ -457,6 +407,33 @@ pub fn perform_sentbox_fetch(context: &Context) {
.fetch(context, use_network);
}
pub fn perform_inbox_idle(context: &Context) {
if *context.perform_inbox_jobs_needed.clone().read().unwrap() {
info!(
context,
"INBOX-IDLE will not be started because of waiting jobs."
);
return;
}
let use_network = context.get_config_bool(Config::InboxWatch);
context
.inbox_thread
.read()
.unwrap()
.idle(context, use_network);
}
pub fn perform_mvbox_idle(context: &Context) {
let use_network = context.get_config_bool(Config::MvboxWatch);
context
.mvbox_thread
.read()
.unwrap()
.idle(context, use_network);
}
pub fn perform_sentbox_idle(context: &Context) {
let use_network = context.get_config_bool(Config::SentboxWatch);
@@ -467,6 +444,27 @@ pub fn perform_sentbox_idle(context: &Context) {
.idle(context, use_network);
}
pub fn interrupt_inbox_idle(context: &Context, block: bool) {
info!(context, "interrupt_inbox_idle called blocking={}", block);
if block {
context.inbox_thread.read().unwrap().interrupt_idle(context);
} else {
match context.inbox_thread.try_read() {
Ok(inbox_thread) => {
inbox_thread.interrupt_idle(context);
}
Err(err) => {
*context.perform_inbox_jobs_needed.write().unwrap() = true;
warn!(context, "could not interrupt idle: {}", err);
}
}
}
}
pub fn interrupt_mvbox_idle(context: &Context) {
context.mvbox_thread.read().unwrap().interrupt_idle(context);
}
pub fn interrupt_sentbox_idle(context: &Context) {
context
.sentbox_thread
@@ -482,7 +480,7 @@ pub fn perform_smtp_jobs(context: &Context) {
let probe_smtp_network = state.probe_network;
state.probe_network = false;
state.perform_jobs_needed = 0;
state.perform_jobs_needed = PerformJobsNeeded::Not;
if state.suspended {
info!(context, "SMTP-jobs suspended.",);
@@ -510,24 +508,27 @@ pub fn perform_smtp_idle(context: &Context) {
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
if state.perform_jobs_needed == 1 {
info!(
context,
"SMTP-idle will not be started because of waiting jobs.",
);
} else {
let dur = get_next_wakeup_time(context, Thread::Smtp);
loop {
let res = cvar.wait_timeout(state, dur).unwrap();
state = res.0;
if state.idle == true || res.1.timed_out() {
// We received the notification and the value has been updated, we can leave.
break;
}
match state.perform_jobs_needed {
PerformJobsNeeded::AtOnce => {
info!(
context,
"SMTP-idle will not be started because of waiting jobs.",
);
}
PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => {
let dur = get_next_wakeup_time(context, Thread::Smtp);
loop {
let res = cvar.wait_timeout(state, dur).unwrap();
state = res.0;
if state.idle || res.1.timed_out() {
// We received the notification and the value has been updated, we can leave.
break;
}
}
state.idle = false;
}
state.idle = false;
}
}
@@ -567,7 +568,7 @@ pub fn maybe_network(context: &Context) {
}
interrupt_smtp_idle(context);
interrupt_imap_idle(context);
interrupt_inbox_idle(context, true);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
}
@@ -579,6 +580,22 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
.unwrap_or_default()
}
fn set_delivered(context: &Context, msg_id: MsgId) {
message::update_msg_state(context, msg_id, MessageState::OutDelivered);
let chat_id: i32 = context
.sql
.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![msg_id],
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: msg_id,
});
}
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
#[allow(non_snake_case)]
pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
@@ -640,10 +657,12 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
}
if mimefactory.recipients_addr.is_empty() {
warn!(
// may happen eg. for groups with only SELF and bcc_self disabled
info!(
context,
"message {} has no recipient, skipping smtp-send", msg_id
);
set_delivered(context, msg_id);
return Ok(());
}
@@ -681,15 +700,15 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
Ok(())
}
pub fn perform_imap_jobs(context: &Context) {
info!(context, "dc_perform_imap_jobs starting.",);
pub fn perform_inbox_jobs(context: &Context) {
info!(context, "dc_perform_inbox_jobs starting.",);
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
*context.probe_imap_network.write().unwrap() = false;
*context.perform_inbox_jobs_needed.write().unwrap() = false;
job_perform(context, Thread::Imap, probe_imap_network);
info!(context, "dc_perform_imap_jobs ended.",);
info!(context, "dc_perform_inbox_jobs ended.",);
}
pub fn perform_mvbox_jobs(context: &Context) {
@@ -701,55 +720,9 @@ pub fn perform_sentbox_jobs(context: &Context) {
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
let query = if !probe_network {
// processing for first-try and after backoff-timeouts:
// process jobs in the order they were added.
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;"
} else {
// processing after call to dc_maybe_network():
// process _all_ pending jobs that failed before
// in the order of their backoff-times.
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
};
let jobs: Vec<Job> = load_jobs(context, thread, probe_network);
let params_no_probe = params![thread as i64, time()];
let params_probe = params![thread as i64];
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
params_no_probe
} else {
params_probe
};
let jobs: Result<Vec<Job>, _> = context.sql.query_map(
query,
params,
|row| {
let job = Job {
job_id: row.get(0)?,
action: row.get(1)?,
foreign_id: row.get(2)?,
desired_timestamp: row.get(5)?,
added_timestamp: row.get(4)?,
tries: row.get(6)?,
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
try_again: 0,
pending_error: None,
};
Ok(job)
},
|jobs| jobs.collect::<Result<Vec<Job>, _>>().map_err(Into::into),
);
match jobs {
Ok(ref _res) => {}
Err(ref err) => {
info!(context, "query failed: {:?}", err);
}
}
for mut job in jobs.unwrap_or_default() {
for mut job in jobs {
info!(
context,
"{}-job #{}, action {} started...",
@@ -783,43 +756,39 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
suspend_smtp_thread(context, true);
}
let mut tries = 0;
while tries <= 1 {
for _tries in 0..2 {
// this can be modified by a job using dc_job_try_again_later()
job.try_again = 0;
job.try_again = TryAgain::Dont;
match job.action {
Action::Unknown => {
warn!(context, "Unknown job id found");
info!(context, "Unknown job id found");
}
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
Action::EmptyServer => job.do_DC_JOB_EMPTY_SERVER(context),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context),
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context),
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
Action::SendMsgToSmtp => job.SendMsgToSmtp(context),
Action::EmptyServer => job.EmptyServer(context),
Action::DeleteMsgOnImap => job.DeleteMsgOnImap(context),
Action::MarkseenMsgOnImap => job.MarkseenMsgOnImap(context),
Action::MarkseenMdnOnImap => job.MarkseenMdnOnImap(context),
Action::MoveMsg => job.MoveMsg(context),
Action::SendMdn => job.SendMsgToSmtp(context),
Action::ConfigureImap => JobConfigureImap(context),
Action::ImexImap => match JobImexImap(context, &job) {
Ok(()) => {}
Err(err) => {
error!(context, "{}", err);
}
},
Action::MaybeSendLocations => {
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
}
Action::MaybeSendLocations => location::JobMaybeSendLocations(context, &job),
Action::MaybeSendLocationsEnded => {
location::job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
location::JobMaybeSendLocationsEnded(context, &mut job)
}
Action::Housekeeping => sql::housekeeping(context),
Action::SendMdnOld => {}
Action::SendMsgToSmtpOld => {}
}
if job.try_again != -1 {
if job.try_again != TryAgain::AtOnce {
break;
}
tries += 1
}
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
context
@@ -836,21 +805,9 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
.unsuspend(context);
suspend_smtp_thread(context, false);
break;
} else if job.try_again == 2 {
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
info!(
context,
"{}-job #{} not yet ready and will be delayed.",
if thread == Thread::Imap {
"INBOX"
} else {
"SMTP"
},
job.job_id
);
} else if job.try_again == -1 || job.try_again == 3 {
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
let tries = job.tries + 1;
if tries < 17 {
if tries < JOB_RETRIES {
job.tries = tries;
let time_offset = get_backoff_time_offset(tries);
job.desired_timestamp = job.added_timestamp + time_offset;
@@ -868,14 +825,14 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
time_offset,
job.added_timestamp + time_offset - time()
);
if thread == Thread::Smtp && tries < 17 - 1 {
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
context
.smtp_state
.clone()
.0
.lock()
.unwrap()
.perform_jobs_needed = 2;
.perform_jobs_needed = PerformJobsNeeded::AvoidDos;
}
} else {
if job.action == Action::SendMsgToSmtp {
@@ -901,13 +858,11 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
}
}
#[allow(non_snake_case)]
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
// results in ~3 weeks for the last backoff timespan
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
fn get_backoff_time_offset(tries: u32) -> i64 {
let n = 2_i32.pow(tries - 1) * 60;
let mut rng = thread_rng();
let n: i32 = rng.gen();
let mut seconds = n % (N + 1);
let r: i32 = rng.gen();
let mut seconds = r % (n + 1);
if seconds < 1 {
seconds = 1;
}
@@ -926,14 +881,6 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
}
}
pub fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
let ret_connected = dc_connect_to_configured_imap(context, inbox);
if 0 != ret_connected {
inbox.set_watch_folder("INBOX".into());
}
ret_connected
}
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<(), Error> {
let mut mimefactory = MimeFactory::load_mdn(context, msg_id)?;
unsafe { mimefactory.render()? };
@@ -1004,7 +951,7 @@ pub fn job_add(
).ok();
match thread {
Thread::Imap => interrupt_imap_idle(context),
Thread::Imap => interrupt_inbox_idle(context, false),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}
@@ -1016,15 +963,112 @@ pub fn interrupt_smtp_idle(context: &Context) {
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
state.perform_jobs_needed = 1;
state.perform_jobs_needed = PerformJobsNeeded::AtOnce;
state.idle = true;
cvar.notify_one();
info!(context, "Interrupting SMTP-idle... ended",);
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, "Interrupting INBOX-IDLE...",);
/// Load jobs from the database.
///
/// Load jobs for this "[Thread]", i.e. either load SMTP jobs or load
/// IMAP jobs. The `probe_network` parameter decides how to query
/// jobs, this is tricky and probably wrong currently. Look at the
/// SQL queries for details.
fn load_jobs(context: &Context, thread: Thread, probe_network: bool) -> Vec<Job> {
let query = if probe_network {
// Processing after call to dc_maybe_network():
// process all pending jobs in the order of their priority.
// If jobs have the same priority, process the
// one that was added earlier.
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
FROM jobs WHERE thread=? ORDER BY action DESC, added_timestamp;"
} else {
// Processing for the first try and after backoff-timeouts:
// process jobs that have their backoff expired
// in the order of their backoff times.
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY desired_timestamp;"
};
*context.perform_inbox_jobs_needed.write().unwrap() = true;
let params_no_probe = params![thread as i64, time()];
let params_probe = params![thread as i64];
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
params_no_probe
} else {
params_probe
};
context.inbox.read().unwrap().interrupt_idle();
context
.sql
.query_map(
query,
params,
|row| {
let job = Job {
job_id: row.get(0)?,
action: row.get(1)?,
foreign_id: row.get(2)?,
desired_timestamp: row.get(5)?,
added_timestamp: row.get(4)?,
tries: row.get(6)?,
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
try_again: TryAgain::Dont,
pending_error: None,
};
Ok(job)
},
|jobs| {
let mut ret: Vec<Job> = Vec::new();
for job in jobs {
match job {
Ok(j) => ret.push(j),
Err(e) => warn!(context, "Bad job from the database: {}", e),
}
}
Ok(ret)
},
)
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
fn insert_job(context: &Context, foreign_id: i64) {
let now = time();
context
.sql
.execute(
"INSERT INTO jobs
(added_timestamp, thread, action, foreign_id, param, desired_timestamp)
VALUES (?, ?, ?, ?, ?, ?);",
params![
now,
Thread::from(Action::MoveMsg),
Action::MoveMsg,
foreign_id,
Params::new().to_string(),
now
],
)
.unwrap();
}
#[test]
fn test_load_jobs() {
// We want to ensure that loading jobs skips over jobs which
// fails to load from the database instead of failing to load
// all jobs.
let t = dummy_context();
insert_job(&t.ctx, 0);
insert_job(&t.ctx, -1); // This can not be loaded into Job struct.
insert_job(&t.ctx, 1);
let jobs = load_jobs(&t.ctx, Thread::from(Action::MoveMsg), false);
assert_eq!(jobs.len(), 2);
}
}

View File

@@ -1,7 +1,7 @@
use std::sync::{Arc, Condvar, Mutex};
use crate::configure::*;
use crate::context::Context;
use crate::error::{Error, Result};
use crate::imap::Imap;
#[derive(Debug)]
@@ -70,6 +70,7 @@ impl JobThread {
state.idle = true;
cvar.notify_one();
info!(context, "Interrupting {}-IDLE... finished", self.name);
}
pub fn fetch(&mut self, context: &Context, use_network: bool) {
@@ -85,53 +86,49 @@ impl JobThread {
}
if use_network {
let start = std::time::Instant::now();
if self.connect_to_imap(context) {
info!(context, "{}-fetch started...", self.name);
self.imap.fetch(context);
if self.imap.should_reconnect() {
info!(context, "{}-fetch aborted, starting over...", self.name,);
self.imap.fetch(context);
if let Err(err) = self.connect_and_fetch(context) {
warn!(context, "connect+fetch failed: {}, reconnect & retry", err);
self.imap.trigger_reconnect();
if let Err(err) = self.connect_and_fetch(context) {
warn!(context, "connect+fetch failed: {}", err);
}
info!(
context,
"{}-fetch done in {:.3} ms.",
self.name,
start.elapsed().as_millis(),
);
}
}
self.state.0.lock().unwrap().using_handle = false;
}
fn connect_to_imap(&self, context: &Context) -> bool {
if async_std::task::block_on(async move { self.imap.is_connected().await }) {
return true;
fn connect_and_fetch(&mut self, context: &Context) -> Result<()> {
let prefix = format!("{}-fetch", self.name);
match self.imap.connect_configured(context) {
Ok(()) => {
if let Some(watch_folder) = self.get_watch_folder(context) {
let start = std::time::Instant::now();
info!(context, "{} started...", prefix);
let res = self.imap.fetch(context, &watch_folder);
let elapsed = start.elapsed().as_millis();
info!(context, "{} done in {:.3} ms.", prefix, elapsed);
res
} else {
Err(Error::WatchFolderNotFound("not-set".to_string()))
}
}
Err(err) => Err(err),
}
let watch_folder_name = match context.sql.get_raw_config(context, self.folder_config_name) {
Some(name) => name,
}
fn get_watch_folder(&self, context: &Context) -> Option<String> {
match context.sql.get_raw_config(context, self.folder_config_name) {
Some(name) => Some(name),
None => {
return false;
if self.folder_config_name == "configured_inbox_folder" {
// initialized with old version, so has not set configured_inbox_folder
Some("INBOX".to_string())
} else {
None
}
}
};
let ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
if ret_connected {
if context
.sql
.get_raw_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
self.imap.configure_folders(context, 0x1);
}
self.imap.set_watch_folder(watch_folder_name);
}
ret_connected
}
pub fn idle(&self, context: &Context, use_network: bool) {
@@ -170,17 +167,36 @@ impl JobThread {
}
}
if self.connect_to_imap(context) {
info!(context, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
} else {
// It's probably wrong that the thread even runs
// but let's call fake_idle and tell it to not try network at all.
// (once we move to rust-managed threads this problem goes away)
info!(context, "{}-IDLE not connected, fake-idling", self.name);
async_std::task::block_on(async move { self.imap.fake_idle(context, false).await });
info!(context, "{}-IDLE fake-idling finished", self.name);
let prefix = format!("{}-IDLE", self.name);
let do_fake_idle = match self.imap.connect_configured(context) {
Ok(()) => {
info!(context, "{} started...", prefix);
let watch_folder = self.get_watch_folder(context);
let res = self.imap.idle(context, watch_folder);
info!(context, "{} ended...", prefix);
match res {
Ok(()) => false,
Err(Error::ImapMissesIdle) => true, // we have to do fake_idle
Err(err) => {
warn!(context, "{} failed: {} -> reconnecting", prefix, err);
// something is borked, let's start afresh on the next occassion
self.imap.disconnect(context);
false
}
}
}
Err(err) => {
info!(context, "{}-IDLE connection fail: {:?}", self.name, err);
// if the connection fails, use fake_idle to retry periodically
// fake_idle() will be woken up by interrupt_idle() as
// well so will act on maybe_network events
true
}
};
if do_fake_idle {
let watch_folder = self.get_watch_folder(context);
self.imap.fake_idle(context, watch_folder);
}
self.state.0.lock().unwrap().using_handle = false;

View File

@@ -1,3 +1,5 @@
//! Cryptographic key module
use std::collections::BTreeMap;
use std::io::Cursor;
use std::path::Path;
@@ -11,6 +13,7 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::sql::{self, Sql};
/// Cryptographic key
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Key {
Public(SignedPublicKey),

View File

@@ -1,3 +1,5 @@
//! Location handling
use bitflags::bitflags;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
@@ -16,7 +18,7 @@ use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
// location handling
/// Location record
#[derive(Debug, Clone, Default)]
pub struct Location {
pub location_id: u32,
@@ -302,16 +304,15 @@ pub fn get_range(
if timestamp_to == 0 {
timestamp_to = time() + 10;
}
context
.sql
.query_map(
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
m.id, l.from_id, l.chat_id, m.txt \
COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt \
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
AND (? OR l.from_id=?) \
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
params![
if chat_id == 0 { 1 } else { 0 },
chat_id as i32,
@@ -328,7 +329,6 @@ pub fn get_range(
} else {
None
};
let loc = Location {
location_id: row.get(0)?,
latitude: row.get(1)?,
@@ -538,7 +538,7 @@ pub fn save(
}
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
pub fn JobMaybeSendLocations(context: &Context, _job: &Job) {
let now = time();
let mut continue_streaming = false;
info!(
@@ -628,7 +628,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
}
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) {
// this function is called when location-streaming _might_ have ended for a chat.
// the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done.

View File

@@ -1,12 +1,14 @@
//! # Logging macros
#[macro_export]
macro_rules! info {
($ctx:expr, $msg:expr) => {
info!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Info(formatted));
};
}};
}
#[macro_export]
@@ -14,10 +16,10 @@ macro_rules! warn {
($ctx:expr, $msg:expr) => {
warn!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Warning(formatted));
};
}};
}
#[macro_export]
@@ -25,10 +27,10 @@ macro_rules! error {
($ctx:expr, $msg:expr) => {
error!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Error(formatted));
};
}};
}
#[macro_export]

View File

@@ -159,7 +159,6 @@ pub struct Message {
pub(crate) from_id: u32,
pub(crate) to_id: u32,
pub(crate) chat_id: u32,
pub(crate) move_state: MoveState,
pub(crate) type_0: Viewtype,
pub(crate) state: MessageState,
pub(crate) hidden: bool,
@@ -200,7 +199,6 @@ impl Message {
" m.mime_in_reply_to AS mime_in_reply_to,",
" m.server_folder AS server_folder,",
" m.server_uid AS server_uid,",
" m.move_state as move_state,",
" m.chat_id AS chat_id,",
" m.from_id AS from_id,",
" m.to_id AS to_id,",
@@ -228,7 +226,6 @@ impl Message {
msg.in_reply_to = row.get::<_, Option<String>>("mime_in_reply_to")?;
msg.server_folder = row.get::<_, Option<String>>("server_folder")?;
msg.server_uid = row.get("server_uid")?;
msg.move_state = row.get("move_state")?;
msg.chat_id = row.get("chat_id")?;
msg.from_id = row.get("from_id")?;
msg.to_id = row.get("to_id")?;
@@ -841,6 +838,11 @@ pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
for msg_id in msg_ids.iter() {
if let Ok(msg) = Message::load_from_db(context, *msg_id) {
if msg.location_id > 0 {
delete_poi_location(context, msg.location_id);
}
}
update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH);
job_add(
context,
@@ -871,6 +873,16 @@ fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: u32) -> bool {
.is_ok()
}
fn delete_poi_location(context: &Context, location_id: u32) -> bool {
sql::execute(
context,
&context.sql,
"DELETE FROM locations WHERE independent = 1 AND id=?;",
params![location_id as i32],
)
.is_ok()
}
pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
if msg_ids.is_empty() {
return false;
@@ -1059,18 +1071,6 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool {
}
}
pub fn update_msg_move_state(context: &Context, rfc724_mid: &str, state: MoveState) -> bool {
// we update the move_state for all messages belonging to a given Message-ID
// so that the state stay intact when parts are deleted
sql::execute(
context,
&context.sql,
"UPDATE msgs SET move_state=? WHERE rfc724_mid=?;",
params![state as i32, rfc724_mid],
)
.is_ok()
}
pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef<str>>) {
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
if msg.state.can_fail() {
@@ -1104,7 +1104,7 @@ pub fn mdn_from_ext(
rfc724_mid: &str,
timestamp_sent: i64,
) -> Option<(u32, MsgId)> {
if from_id <= 9 || rfc724_mid.is_empty() {
if from_id <= DC_MSG_ID_LAST_SPECIAL || rfc724_mid.is_empty() {
return None;
}

View File

@@ -230,7 +230,7 @@ impl<'a> MimeFactory<'a> {
// 1=add Autocrypt-header (needed eg. for handshaking), 2=no Autocrypte-header (used for MDN)
let mut e2ee_guaranteed = false;
let mut min_verified: libc::c_int = 0;
let mut min_verified = crate::peerstate::PeerstateVerifiedStatus::Unverified;
let mut do_gossip = false;
let mut grpimage = None;
let force_plaintext: libc::c_int;
@@ -246,7 +246,7 @@ impl<'a> MimeFactory<'a> {
wrapmime::new_custom_field(imf_fields, "Chat-Verified", "1");
force_plaintext = 0;
e2ee_guaranteed = true;
min_verified = 2
min_verified = crate::peerstate::PeerstateVerifiedStatus::BidirectVerified;
} else {
force_plaintext = self
.msg

View File

@@ -1,3 +1,5 @@
//! OAuth 2 module
use std::collections::HashMap;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
@@ -33,12 +35,14 @@ struct Oauth2 {
get_userinfo: Option<&'static str>,
}
/// OAuth 2 Access Token Response
#[derive(Debug, Deserialize)]
struct Response {
// Should always be there according to: https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
// but previous code handled its abscense.
access_token: Option<String>,
token_type: String,
/// Duration of time the token is granted for, in seconds
expires_in: Option<u64>,
refresh_token: Option<String>,
scope: Option<String>,
@@ -205,7 +209,7 @@ pub fn dc_get_oauth2_access_token(
.ok();
let expires_in = response
.expires_in
// refresh a bet before
// refresh a bit before
.map(|t| time() + t as i64 - 5)
.unwrap_or_else(|| 0);
context

View File

@@ -1,3 +1,4 @@
//! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module
use std::collections::HashSet;
use std::fmt;
@@ -11,6 +12,17 @@ use crate::error::*;
use crate::key::*;
use crate::sql::{self, Sql};
pub const DC_PS_GOSSIP_KEY: u32 = 0;
pub const DC_PS_PUBLIC_KEY: u32 = 1;
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)]
#[repr(u8)]
pub enum PeerstateVerifiedStatus {
Unverified = 0,
//Verified = 1, // not used
BidirectVerified = 2,
}
/// Peerstate represents the state of an Autocrypt peer.
pub struct Peerstate<'a> {
pub context: &'a Context,
@@ -310,7 +322,7 @@ impl<'a> Peerstate<'a> {
};
}
pub fn render_gossip_header(&self, min_verified: usize) -> Option<String> {
pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> {
if let Some(ref addr) = self.addr {
if let Some(key) = self.peek_key(min_verified) {
// TODO: avoid cloning
@@ -326,12 +338,12 @@ impl<'a> Peerstate<'a> {
None
}
pub fn peek_key(&self, min_verified: usize) -> Option<&Key> {
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> {
if self.public_key.is_none() && self.gossip_key.is_none() && self.verified_key.is_none() {
return None;
}
if 0 != min_verified {
if min_verified != PeerstateVerifiedStatus::Unverified {
return self.verified_key.as_ref();
}
if self.public_key.is_some() {
@@ -341,10 +353,17 @@ impl<'a> Peerstate<'a> {
self.gossip_key.as_ref()
}
pub fn set_verified(&mut self, which_key: usize, fingerprint: &str, verified: usize) -> bool {
pub fn set_verified(
&mut self,
which_key: u32,
fingerprint: &str,
verified: PeerstateVerifiedStatus,
) -> bool {
let mut success = false;
if !(which_key != 0 && which_key != 1 || verified != 2) {
if which_key == 1
if !(which_key != DC_PS_GOSSIP_KEY && which_key != DC_PS_PUBLIC_KEY
|| verified != PeerstateVerifiedStatus::BidirectVerified)
{
if which_key == DC_PS_PUBLIC_KEY
&& self.public_key_fingerprint.is_some()
&& self.public_key_fingerprint.as_ref().unwrap() == fingerprint
{
@@ -353,7 +372,7 @@ impl<'a> Peerstate<'a> {
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
success = true;
}
if which_key == 0
if which_key == DC_PS_GOSSIP_KEY
&& self.gossip_key_fingerprint.is_some()
&& self.gossip_key_fingerprint.as_ref().unwrap() == fingerprint
{
@@ -382,11 +401,11 @@ impl<'a> Peerstate<'a> {
sql::execute(
self.context,
sql,
"UPDATE acpeerstates \
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
"UPDATE acpeerstates \
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
verified_key=?, verified_key_fingerprint=? \
WHERE addr=?;",
WHERE addr=?;",
params![
self.last_seen,
self.last_seen_autocrypt,

View File

@@ -1,5 +1,8 @@
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp)
use std::collections::{BTreeMap, HashSet};
use std::convert::TryInto;
use std::io;
use std::io::Cursor;
use pgp::armor::BlockType;
@@ -8,8 +11,10 @@ use pgp::composed::{
SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder,
};
use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
use rand::thread_rng;
use pgp::types::{
CompressionAlgorithm, KeyTrait, Mpi, PublicKeyTrait, SecretKeyTrait, StringToKey,
};
use rand::{thread_rng, CryptoRng, Rng};
use crate::error::Error;
use crate::key::*;
@@ -18,6 +23,68 @@ use crate::keyring::*;
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
/// A wrapper for rPGP public key types
#[derive(Debug)]
enum SignedPublicKeyOrSubkey<'a> {
Key(&'a SignedPublicKey),
Subkey(&'a SignedPublicSubKey),
}
impl<'a> KeyTrait for SignedPublicKeyOrSubkey<'a> {
fn fingerprint(&self) -> Vec<u8> {
match self {
Self::Key(k) => k.fingerprint(),
Self::Subkey(k) => k.fingerprint(),
}
}
fn key_id(&self) -> pgp::types::KeyId {
match self {
Self::Key(k) => k.key_id(),
Self::Subkey(k) => k.key_id(),
}
}
fn algorithm(&self) -> pgp::crypto::PublicKeyAlgorithm {
match self {
Self::Key(k) => k.algorithm(),
Self::Subkey(k) => k.algorithm(),
}
}
}
impl<'a> PublicKeyTrait for SignedPublicKeyOrSubkey<'a> {
fn verify_signature(
&self,
hash: HashAlgorithm,
data: &[u8],
sig: &[Mpi],
) -> pgp::errors::Result<()> {
match self {
Self::Key(k) => k.verify_signature(hash, data, sig),
Self::Subkey(k) => k.verify_signature(hash, data, sig),
}
}
fn encrypt<R: Rng + CryptoRng>(
&self,
rng: &mut R,
plain: &[u8],
) -> pgp::errors::Result<Vec<Mpi>> {
match self {
Self::Key(k) => k.encrypt(rng, plain),
Self::Subkey(k) => k.encrypt(rng, plain),
}
}
fn to_writer_old(&self, writer: &mut impl io::Write) -> pgp::errors::Result<()> {
match self {
Self::Key(k) => k.to_writer_old(writer),
Self::Subkey(k) => k.to_writer_old(writer),
}
}
}
/// Split data from PGP Armored Data as defined in https://tools.ietf.org/html/rfc4880#section-6.2.
///
/// Returns (type, headers, base64 encoded body).
@@ -97,22 +164,39 @@ pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
Some((Key::Public(public_key), Key::Secret(private_key)))
}
/// Select subkey of the public key to use for encryption.
/// Select public key or subkey to use for encryption.
///
/// Currently the first subkey is selected.
fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
key.public_subkeys.iter().find(|_k|
// TODO: check if it is an encryption subkey
true)
/// First, tries to use subkeys. If none of the subkeys are suitable
/// for encryption, tries to use primary key. Returns `None` if the public
/// key cannot be used for encryption.
///
/// TODO: take key flags and expiration dates into account
fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<SignedPublicKeyOrSubkey> {
key.public_subkeys
.iter()
.find(|subkey| subkey.is_encryption_key())
.map_or_else(
|| {
// No usable subkey found, try primary key
if key.is_encryption_key() {
Some(SignedPublicKeyOrSubkey::Key(key))
} else {
None
}
},
|subkey| Some(SignedPublicKeyOrSubkey::Subkey(subkey)),
)
}
/// Encrypts `plain` text using `public_keys_for_encryption`
/// and signs it using `private_key_for_signing`.
pub fn pk_encrypt(
plain: &[u8],
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
) -> Result<String, Error> {
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption
let pkeys: Vec<SignedPublicKeyOrSubkey> = public_keys_for_encryption
.keys()
.iter()
.filter_map(|key| {
@@ -122,6 +206,7 @@ pub fn pk_encrypt(
.and_then(select_pk_for_encryption)
})
.collect();
let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect();
let mut rng = thread_rng();
@@ -134,9 +219,9 @@ pub fn pk_encrypt(
lit_msg
.sign(skey, || "".into(), Default::default())
.and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB))
.and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys))
.and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs))
} else {
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys)
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs)
};
let msg = encrypted_msg?;

View File

@@ -1,3 +1,5 @@
//! # QR code module
use lazy_static::lazy_static;
use percent_encoding::percent_decode_str;

View File

@@ -1,3 +1,5 @@
//! Verified contact protocol implementation as [specified by countermitm project](https://countermitm.readthedocs.io/en/stable/new.html#setup-contact-protocol)
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use crate::aheader::EncryptPreference;
@@ -609,6 +611,14 @@ pub fn handle_securejoin_handshake(
}
inviter_progress!(context, contact_id, 800);
inviter_progress!(context, contact_id, 1000);
let field_grpid = mimeparser
.lookup_optional_field("Secure-Join-Group")
.unwrap_or_default();
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
context.call_cb(Event::SecurejoinMemberAdded {
chat_id: group_chat_id,
contact_id: contact_id,
});
} else {
warn!(context, "vg-member-added-received invalid.",);
return ret;
@@ -662,7 +672,11 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
if let Some(ref mut peerstate) =
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref())
{
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
if peerstate.set_verified(
DC_PS_PUBLIC_KEY,
fingerprint.as_ref(),
PeerstateVerifiedStatus::BidirectVerified,
) {
peerstate.prefer_encrypt = EncryptPreference::Mutual;
peerstate.to_save = Some(ToSave::All);
peerstate

View File

@@ -138,7 +138,7 @@ impl Smtp {
/// SMTP-Send a prepared mail to recipients.
/// on successful send out Ok() is returned.
pub fn send<'a>(
pub fn send(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,

View File

@@ -1,3 +1,5 @@
//! # SQLite wrapper
use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use std::time::Duration;
@@ -5,6 +7,8 @@ use std::time::Duration;
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
use thread_local_object::ThreadLocal;
use crate::chat::update_saved_messages_icon;
use crate::constants::ShowEmails;
use crate::context::Context;
use crate::dc_tools::*;
use crate::error::{Error, Result};
@@ -348,7 +352,7 @@ fn open(
}
if !readonly {
let mut exists_before_update = 0;
let mut exists_before_update = false;
let mut dbversion_before_update = 0;
/* Init tables to dbversion=0 */
if !sql.table_exists("config") {
@@ -478,7 +482,7 @@ fn open(
sql.set_raw_config_int(context, "dbversion", 0)?;
}
} else {
exists_before_update = 1;
exists_before_update = true;
dbversion_before_update = sql
.get_raw_config_int(context, "dbversion")
.unwrap_or_default();
@@ -488,9 +492,11 @@ fn open(
// this should be done before updates that use high-level objects that
// rely themselves on the low-level structure.
// --------------------------------------------------------------------
let mut dbversion = dbversion_before_update;
let mut recalc_fingerprints = 0;
let mut update_file_paths = 0;
let mut update_icons = false;
if dbversion < 1 {
info!(context, "[migration] v1");
@@ -588,6 +594,8 @@ fn open(
}
if dbversion < 27 {
info!(context, "[migration] v27");
// chat.id=1 and chat.id=2 are the old deaddrops,
// the current ones are defined by chats.blocked=2
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
sql.execute(
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
@@ -653,6 +661,9 @@ fn open(
params![],
)?;
if dbversion_before_update == 34 {
// migrate database from the use of verified-flags to verified_key,
// _only_ version 34 (0.17.0) has the fields public_key_verified and gossip_key_verified
// this block can be deleted in half a year or so (created 5/2018)
sql.execute(
"UPDATE acpeerstates SET verified_key=gossip_key, verified_key_fingerprint=gossip_key_fingerprint WHERE gossip_key_verified=2;",
params![]
@@ -682,6 +693,8 @@ fn open(
}
if dbversion < 42 {
info!(context, "[migration] v42");
// older versions set the txt-field to the filenames, for debugging and fulltext search.
// to allow text+attachment compound messages, we need to reset these fields.
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
dbversion = 42;
sql.set_raw_config_int(context, "dbversion", 42)?;
@@ -716,6 +729,7 @@ fn open(
}
if dbversion < 48 {
info!(context, "[migration] v48");
// NOTE: move_state is not used anymore
sql.execute(
"ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
params![],
@@ -735,14 +749,19 @@ fn open(
}
if dbversion < 50 {
info!(context, "[migration] v50");
if 0 != exists_before_update {
sql.set_raw_config_int(context, "show_emails", 2)?;
// installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly;
// keep this default and use DC_SHOW_EMAILS_NO
// only for new installations
if exists_before_update {
sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32)?;
}
dbversion = 50;
sql.set_raw_config_int(context, "dbversion", 50)?;
}
if dbversion < 53 {
info!(context, "[migration] v53");
// the messages containing _only_ locations
// are also added to the database as _hidden_.
sql.execute(
"CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);",
params![]
@@ -790,9 +809,30 @@ fn open(
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
params![],
)?;
sql.set_raw_config_int(context, "dbversion", 55)?;
}
if dbversion < 59 {
info!(context, "[migration] v59");
// records in the devmsglabels are kept when the message is deleted.
// so, msg_id may or may not exist.
sql.execute(
"CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);",
NO_PARAMS,
)?;
sql.execute(
"CREATE INDEX devmsglabels_index1 ON devmsglabels (label);",
NO_PARAMS,
)?;
if exists_before_update && sql.get_raw_config_int(context, "bcc_self").is_none() {
sql.set_raw_config_int(context, "bcc_self", 1)?;
}
update_icons = true;
sql.set_raw_config_int(context, "dbversion", 59)?;
}
// (2) updates that require high-level objects
// (the structure is complete now and all objects are usable)
// --------------------------------------------------------------------
if 0 != recalc_fingerprints {
info!(context, "[migration] recalc fingerprints");
@@ -840,6 +880,9 @@ fn open(
sql.set_raw_config(context, "backup_for", None)?;
}
if update_icons {
update_saved_messages_icon(context)?;
}
}
info!(context, "Opened {:?}.", dbfile.as_ref(),);

View File

@@ -1,3 +1,5 @@
//! Module to work with translatable stock strings
use std::borrow::Cow;
use strum::EnumProperty;
@@ -110,8 +112,10 @@ pub enum StockMessage {
Location = 66,
#[strum(props(fallback = "Sticker"))]
Sticker = 67,
#[strum(props(fallback = "Device Messages"))]
#[strum(props(fallback = "Device messages"))]
DeviceMessages = 68,
#[strum(props(fallback = "Saved messages"))]
SavedMessages = 69,
}
impl StockMessage {

View File

@@ -1,4 +1,8 @@
//! # Token module
//!
//! Functions to read/write token from/to the database. A token is any string associated with a key.
//!
//! Tokens are used in countermitm verification protocols.
use deltachat_derive::*;
@@ -6,7 +10,7 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::sql;
// Token namespaces
/// Token namespace
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
#[repr(i32)]
pub enum Namespace {
@@ -21,6 +25,8 @@ impl Default for Namespace {
}
}
/// Creates a new token and saves it into the database.
/// Returns created token.
pub fn save(context: &Context, namespace: Namespace, foreign_id: u32) -> String {
// foreign_id may be 0
let token = dc_create_id();

View File

@@ -49,20 +49,27 @@ pub fn get_ct_subtype(mime: *mut Mailmime) -> Option<String> {
}
}
pub fn parse_message_id(message_id: &str) -> Result<String, Error> {
pub fn parse_message_id(message_id: &[u8]) -> Result<String, Error> {
let mut dummy = 0;
let c_message_id = CString::new(message_id).unwrap_or_default();
let c_ptr = c_message_id.as_ptr();
let mut rfc724_mid_c = std::ptr::null_mut();
if unsafe { mailimf_msg_id_parse(c_ptr, libc::strlen(c_ptr), &mut dummy, &mut rfc724_mid_c) }
== MAIL_NO_ERROR as libc::c_int
if unsafe {
mailimf_msg_id_parse(
message_id.as_ptr() as *const libc::c_char,
message_id.len(),
&mut dummy,
&mut rfc724_mid_c,
)
} == MAIL_NO_ERROR as libc::c_int
&& !rfc724_mid_c.is_null()
{
let res = to_string_lossy(rfc724_mid_c);
unsafe { libc::free(rfc724_mid_c.cast()) };
Ok(res)
} else {
bail!("could not parse message_id: {}", message_id);
bail!(
"could not parse message_id: {}",
String::from_utf8_lossy(message_id)
);
}
}
@@ -543,11 +550,11 @@ mod tests {
#[test]
fn test_parse_message_id() {
assert_eq!(
parse_message_id("Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
parse_message_id(b"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org").unwrap(),
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
);
assert_eq!(
parse_message_id("<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
parse_message_id(b"<Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org>").unwrap(),
"Mr.PRUe8HJBoaO.3whNvLCMFU0@testrun.org"
);
}

View File

@@ -128,7 +128,7 @@ fn test_encryption_decryption() {
public_keyring.add_ref(&public_key);
let mut public_keyring2 = Keyring::default();
public_keyring2.add_owned(public_key2.clone());
public_keyring2.add_owned(public_key2);
let mut valid_signatures: HashSet<String> = Default::default();
@@ -220,7 +220,7 @@ fn create_test_context() -> TestContext {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
TestContext { ctx: ctx, dir: dir }
TestContext { ctx, dir }
}
#[test]
@@ -261,22 +261,6 @@ fn test_stress_tests() {
}
}
#[test]
fn test_get_contacts() {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
}
#[test]
fn test_chat() {
let context = create_test_context();