Compare commits

..

72 Commits

Author SHA1 Message Date
holger krekel
9b6d7bde82 bring back remote_rust tests but comment them 2020-06-09 15:00:31 +02:00
holger krekel
95b40ad1d8 avoid hello.com and use example.org 2020-06-09 14:39:00 +02:00
holger krekel
0efb2215e4 renamings and parallel sending 2020-06-09 14:39:00 +02:00
holger krekel
0c8f951d8f address @hocuri comment -- remove as_contact() as it's synonym with create_contact
and the latter is already an established API and conveys better that a contact
object will be created if it doesn't exist.
2020-06-09 14:39:00 +02:00
holger krekel
0bb4ef0bd9 introduce chat.num_contacts() as a more efficient shortcut 2020-06-09 14:39:00 +02:00
holger krekel
f93a863f5f fix and steamline tests and test setup 2020-06-09 14:39:00 +02:00
holger krekel
f263843c5f route all flexible contact add/remove through account.as_contact(obj) 2020-06-09 14:39:00 +02:00
holger krekel
503202376a remove logid from Account creation, one can now just use the "displayname" for log purposes 2020-06-09 14:39:00 +02:00
holger krekel
ca70c6a205 remove account.create_chat_by_message in favor of message.create_chat(), simplifing the API 2020-06-09 14:39:00 +02:00
holger krekel
7d5fba8416 refine contact API and introduce account.create_chat() helper
strike create_chat_by_contact in favor of contact.create_chat()
2020-06-09 14:39:00 +02:00
holger krekel
3a85b671a1 remove acfactory.get_chat() in favour of account.create_chat(account2) directly working. 2020-06-09 14:39:00 +02:00
holger krekel
1083cab972 as discussed with @dignifiedquire only do package-building and upload on master, not on branches. 2020-06-09 14:23:07 +02:00
Friedel Ziegelmayer
7677650b39 Merge pull request #1580 from deltachat/fix/imap-connection 2020-06-09 13:44:57 +02:00
dignifiedquire
1f2087190e ci(github): dont build all branches on push 2020-06-09 13:42:40 +02:00
dignifiedquire
59fadee9e0 ci(circle): remove outdated reference 2020-06-09 13:24:19 +02:00
dignifiedquire
4a3825c302 fix: improve imap connection establishment
- fixes blocking on start_io
- attempts to connect to the imap on all tasks when needed
2020-06-09 13:20:16 +02:00
dignifiedquire
52e74c241f update pipeline name 2020-06-09 13:14:55 +02:00
dignifiedquire
3fa69c1852 fixup: ci 2020-06-09 13:10:25 +02:00
dignifiedquire
b3074f854e remove beta from matrix 2020-06-09 13:07:56 +02:00
dignifiedquire
95c5128d9f fixup: ci 2020-06-09 13:07:18 +02:00
dignifiedquire
dc17006b16 fixup: ci 2020-06-09 13:05:21 +02:00
dignifiedquire
e4a4c230fe fixup: ci 2020-06-09 13:02:17 +02:00
dignifiedquire
f56a4450f3 switch rust tests to github ci 2020-06-09 12:59:53 +02:00
dignifiedquire
913db3b958 ci(github): update toolchain 2020-06-09 12:55:25 +02:00
Alexander Krotov
7de23f86b1 Do not reply to messages that can't be decrypted
This commit fixes the test broken in previous commit.
2020-06-08 23:16:35 +02:00
Alexander Krotov
35566f5ea5 Extend undecipherable group test
Reply to group messages assigned to 1:1 chat on ac4 and see where they
end up on ac1 and ac2. Currently they contain group ID in Reply-To field
and go to group chat, so the test is failing.
2020-06-08 23:16:35 +02:00
Alexander Krotov
34579974c3 Don't make ad-hoc groups when message cannot be decrypted
This fixes the test added in the parent commit.
2020-06-08 23:16:35 +02:00
Alexander Krotov
c6f19ea0a4 Add failing test for undecipherable group messages 2020-06-08 23:16:35 +02:00
Alexander Krotov
64ab955ad7 create_or_lookup_group: streamline group ID parsing 2020-06-08 19:25:36 +03:00
holger krekel
4fdf496cac refine one more test to "newstyle" 2020-06-08 16:31:21 +02:00
holger krekel
6497e6397d merge direct imap tests into their already existing counterparts 2020-06-08 16:31:21 +02:00
holger krekel
d8bbe2fcce refine test / chat API 2020-06-08 16:31:21 +02:00
holger krekel
b6cc44a956 integrate direct imap test in existing BCC test 2020-06-08 16:31:21 +02:00
holger krekel
0105c831f1 make direct_imap a permanent feature of online accounts 2020-06-08 16:31:21 +02:00
holger krekel
d40f96ac65 fixing imap interactions 2020-06-08 16:31:21 +02:00
holger krekel
69135709ac more refines and test fixes 2020-06-08 16:31:21 +02:00
holger krekel
612a9d012c snap using imapclient 2020-06-08 16:31:21 +02:00
bjoern
2ad014faf4 Merge pull request #1563 from deltachat/recode-images
recode images
2020-06-08 11:02:35 +02:00
B. Petersen
f3a59e19d8 simplify condition for jpeg-check 2020-06-08 10:37:13 +02:00
bjoern
17283c86a3 Update src/chat.rs
Co-authored-by: Hocuri <hocuri@gmx.de>
2020-06-08 10:37:13 +02:00
bjoern
945943a849 Update src/constants.rs
Co-authored-by: Hocuri <hocuri@gmx.de>
2020-06-08 10:37:13 +02:00
bjoern
34c69785d0 Update src/blob.rs
Co-authored-by: Hocuri <hocuri@gmx.de>
2020-06-08 10:37:12 +02:00
B. Petersen
d5ea4f9b1a make clippy happy 2020-06-08 10:37:12 +02:00
B. Petersen
191009372b basically recode images 2020-06-08 10:37:12 +02:00
Alexander Krotov
39faddc74d create_or_lookup_adhoc_group: move comment to the correct place
The comment is related to member list processing, not mailing list check.
2020-06-08 08:30:26 +03:00
bjoern
5d1623b98f Merge pull request #1574 from deltachat/better-subject-addon
add new string to deltachat.h
2020-06-07 12:41:21 +02:00
B. Petersen
af0dc42df3 add new string to deltachat.h 2020-06-07 12:29:29 +02:00
Hocuri
c18705fae3 Improve test 2020-06-07 12:11:52 +02:00
Hocuri
22973899b8 Assume that thare always is Config::Addr set 2020-06-07 12:11:52 +02:00
Hocuri
f172e92098 Repair test 2020-06-07 12:11:52 +02:00
Hocuri
e1ff657c78 Dont Hardcode 'Delta Chat' 2020-06-07 12:11:52 +02:00
Hocuri
3e6cd3ff34 Adapt to async, set first subject to 'Message from <sender name>' 2020-06-07 12:11:52 +02:00
Hocuri
f8680724f8 Set subject to Re: <last subject> for better compability with normal MUAs
The code in dc_receive_imf.rs looks a bit funny, an alternative would be a function:

fn upcate_chat_last_subject(context: &Context, chat_id: &ChatId, mime_parser: &mut MimeMessage) -> Result<()> {
    let mut chat = Chat::load_from_db(context, *chat_id)?;
    chat.param.set(Param::LastSubject, mime_parser.get_subject().ok_or_else(||Error::Message("No subject in email".to_string()))?);
    chat.update_param(context)?;
    Ok(())
}
2020-06-07 12:11:52 +02:00
holger krekel
30c76976fc update links and add a little boilerplate / status info 2020-06-06 19:20:37 +02:00
Alexander Krotov
f0f020d9d2 chat: get rid of ChatId.is_error()
get_rowid should not return 0, as we have inserted a row right above.

And using is_error() instead of comparing row_id to 0 is a strange way
to check this condition.

As all functions that actually returned 0 chat ID to indicate error have
been removed, the function is gone too.
2020-06-06 19:49:57 +03:00
Hocuri
17a13f0f83 Adapt spec.md to new subject (#1395) 2020-06-06 18:34:50 +02:00
bjoern
ec441b16f1 Revert "Enable strict TLS certificate checks by default"
This reverts commit 6d9ff3d248.
2020-06-06 18:42:54 +03:00
Alexander Krotov
5239f2edad dc_receive_imf: replace chat_id.is_error() with chat_id.is_unset()
Both methods do the same: compare chat_id to 0. However, in these cases
0 refers to the state when chat_id is not determined yet, because no
corresponding chat has been found.

All functions that returned 0 to indicate error have already been
resultified.
2020-06-06 18:29:35 +03:00
Alexander Krotov
cd751a64cb python tests: fix typos 2020-06-06 16:11:59 +03:00
Alexander Krotov
6d9ff3d248 Enable strict TLS certificate checks by default 2020-06-06 00:08:29 +02:00
holger krekel
d97d9980dd cleanup 2020-06-05 23:28:27 +02:00
holger krekel
4ad4d6d10d Update python/src/deltachat/direct_imap.py
Co-authored-by: Hocuri <hocuri@gmx.de>
2020-06-05 23:28:27 +02:00
holger krekel
82731ee86c fix test 2020-06-05 23:28:27 +02:00
holger krekel
04bdfa17f7 improve shutdown order 2020-06-05 23:28:27 +02:00
holger krekel
7a5759de4b some streamlining and test fixing 2020-06-05 23:28:27 +02:00
holger krekel
e29dcbf8eb remove wrong inbox folders 2020-06-05 23:28:27 +02:00
holger krekel
882f90b5ff more cleanups, don't run the test 30 times anymore -- it's too wasteful
and doesn't gurantee test failure.
2020-06-05 23:28:27 +02:00
holger krekel
469451d5dd rework imap structure logging 2020-06-05 23:28:27 +02:00
holger krekel
af33c2dea7 test pass again 2020-06-05 23:28:27 +02:00
holger krekel
d076ab4d6d fix grouping / helper function 2020-06-05 21:59:33 +02:00
holger krekel
e66ed8eadb shift tests away a little, mark "ignored" for regular pytest runs 2020-06-05 21:59:33 +02:00
Hocuri
05e1c00cd1 fix: update message ids correctly
Fixes #1495
2020-06-05 16:27:22 +02:00
36 changed files with 1520 additions and 949 deletions

View File

@@ -143,7 +143,7 @@ jobs:
steps:
- checkout
- run: ci_scripts/remote_tests_rust.sh
remote_tests_python:
machine: true
steps:
@@ -178,10 +178,10 @@ workflows:
jobs:
# - cargo_fetch
- remote_tests_rust:
filters:
tags:
only: /.*/
#- remote_tests_rust
# filters:
# tags:
# only: /.*/
- remote_tests_python:
filters:
@@ -191,8 +191,10 @@ workflows:
- remote_python_packaging:
requires:
- remote_tests_python
- remote_tests_rust
# - remote_tests_rust
filters:
branches:
only: master
tags:
only: /.*/
@@ -201,6 +203,8 @@ workflows:
- remote_python_packaging
- build_doxygen
filters:
branches:
only: master
tags:
only: /.*/
# - rustfmt:
@@ -212,6 +216,8 @@ workflows:
- build_doxygen:
filters:
branches:
only: master
tags:
only: /.*/

89
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
name: Rust CI
on:
pull_request:
push:
branches:
- master
- staging
- trying
jobs:
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.43.1
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run_clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.43.1
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
build_and_test:
name: Build and test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
rust: [nightly, 1.43.1]
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.rust }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Cache cargo registry
uses: actions/cache@v2
with:
path: ~/.cargo/registry
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.toml') }}
- name: Cache cargo index
uses: actions/cache@v2
with:
path: ~/.cargo/git
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-index-${{ hashFiles('**/Cargo.toml') }}
- name: Cache cargo build
uses: actions/cache@v2
with:
path: target
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.toml') }}
- name: check
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace

View File

@@ -1,48 +0,0 @@
on: push
name: Code Quality
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2020-03-19
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --examples --tests --all-features
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2020-03-19
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run_clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly-2020-03-19
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features

461
Cargo.lock generated
View File

@@ -15,6 +15,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
[[package]]
name = "aead"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf01b9b56e767bb57b94ebf91a58b338002963785cdd7013e21c0d4679471e4"
dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.3.2"
@@ -26,6 +35,20 @@ dependencies = [
"block-cipher-trait",
]
[[package]]
name = "aes-gcm"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "834a6bda386024dbb7c8fc51322856c10ffe69559f972261c868485f5759c638"
dependencies = [
"aead",
"aes",
"block-cipher-trait",
"ghash",
"subtle 2.2.3",
"zeroize",
]
[[package]]
name = "aes-soft"
version = "0.3.3"
@@ -113,15 +136,15 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
dependencies = [
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
name = "async-h1"
version = "1.1.2"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "effd501febce09534b924aa471e6a7fd689071fee63659473413f62a1979ae56"
checksum = "7c966ee4e290159619bdc5282ba252749386552be066dbcf5b97adf612798a9a"
dependencies = [
"async-std",
"byte-pool",
@@ -131,7 +154,6 @@ dependencies = [
"lazy_static",
"log",
"pin-project-lite",
"url",
]
[[package]]
@@ -224,13 +246,13 @@ checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
[[package]]
name = "async-trait"
version = "0.1.31"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b"
checksum = "8f1c13101a3224fb178860ae372a031ce350bbd92d39968518f016744dde0bf7"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -269,6 +291,12 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base-x"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
[[package]]
name = "base64"
version = "0.10.1"
@@ -284,6 +312,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "base64"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
[[package]]
name = "bit-set"
version = "0.5.2"
@@ -391,9 +425,9 @@ checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]]
name = "bumpalo"
version = "3.3.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "byte-pool"
@@ -433,12 +467,6 @@ dependencies = [
"iovec",
]
[[package]]
name = "bytes"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
[[package]]
name = "cargo_metadata"
version = "0.6.4"
@@ -503,7 +531,7 @@ checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
dependencies = [
"num-integer",
"num-traits",
"time",
"time 0.1.43",
]
[[package]]
@@ -514,9 +542,9 @@ checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea"
[[package]]
name = "clear_on_drop"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
checksum = "c9cc5db465b294c3fa986d5bbb0f3017cd850bff6dd6c52f9ccff8b4d21b7b08"
dependencies = [
"cc",
]
@@ -544,11 +572,18 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cookie"
version = "0.12.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5"
checksum = "ca761767cf3fa9068cc893ec8c247a22d0fd0535848e65640c0548bd1f8bbb36"
dependencies = [
"time",
"aes-gcm",
"base64 0.12.1",
"hkdf",
"hmac",
"percent-encoding",
"rand 0.7.3",
"sha2",
"time 0.2.16",
]
[[package]]
@@ -610,9 +645,9 @@ dependencies = [
[[package]]
name = "crossbeam-queue"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -630,25 +665,35 @@ dependencies = [
]
[[package]]
name = "ctor"
version = "0.1.14"
name = "crypto-mac"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d"
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
dependencies = [
"quote 1.0.6",
"syn 1.0.27",
"generic-array",
"subtle 1.0.0",
]
[[package]]
name = "ctor"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39858aa5bac06462d4dd4b9164848eb81ffc4aa5c479746393598fd193afa227"
dependencies = [
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
name = "curve25519-dalek"
version = "2.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839"
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
dependencies = [
"byteorder",
"digest",
"rand_core 0.5.1",
"subtle",
"subtle 2.2.3",
"zeroize",
]
@@ -671,9 +716,9 @@ dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote 1.0.6",
"quote 1.0.7",
"strsim",
"syn 1.0.27",
"syn 1.0.30",
]
[[package]]
@@ -683,8 +728,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -774,8 +819,8 @@ dependencies = [
name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -801,8 +846,8 @@ dependencies = [
"darling",
"derive_builder_core",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -813,8 +858,8 @@ checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
"darling",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -854,6 +899,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dtoa"
version = "0.4.5"
@@ -889,7 +940,7 @@ dependencies = [
"encoding",
"lazy_static",
"rand 0.7.3",
"time",
"time 0.1.43",
"version_check 0.9.2",
]
@@ -1035,8 +1086,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
"synstructure",
]
@@ -1168,8 +1219,8 @@ checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -1239,6 +1290,15 @@ dependencies = [
"wasi",
]
[[package]]
name = "ghash"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f0930ed19a7184089ea46d2fedead2f6dc2b674c5db4276b7da336c7cd83252"
dependencies = [
"polyval",
]
[[package]]
name = "gif"
version = "0.10.3"
@@ -1298,6 +1358,26 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "hkdf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3"
dependencies = [
"digest",
"hmac",
]
[[package]]
name = "hmac"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
dependencies = [
"crypto-mac",
"digest",
]
[[package]]
name = "hostname"
version = "0.1.5"
@@ -1308,22 +1388,11 @@ dependencies = [
"winutil",
]
[[package]]
name = "http"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
dependencies = [
"bytes 0.5.4",
"fnv",
"itoa",
]
[[package]]
name = "http-client"
version = "2.0.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271fae45413daaca82e8c6bf46e452b897873676ce56196d8939645711f542d5"
checksum = "fdae064c76b75ced4793544e843478c8c9c2bebf16fe6913212df83e30dfdb46"
dependencies = [
"async-h1",
"async-native-tls",
@@ -1339,19 +1408,19 @@ dependencies = [
[[package]]
name = "http-types"
version = "1.3.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49d9f44462c2e59d5d5826e7ba74b121ee2fb0ff091ef849692694f1d77aaf50"
checksum = "9a89eaaf43f3700e78c01cb8165d1bd05155065637d26ee2f49800c95e7b62ee"
dependencies = [
"anyhow",
"async-std",
"cookie",
"http",
"infer",
"omnom",
"pin-project-lite",
"rand 0.7.3",
"serde",
"serde_json",
"serde_urlencoded",
"url",
]
@@ -1439,9 +1508,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.3.2"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
dependencies = [
"autocfg 1.0.0",
]
@@ -1550,7 +1619,7 @@ dependencies = [
"lettre",
"mime",
"regex",
"time",
"time 0.1.43",
"uuid",
]
@@ -1787,8 +1856,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -1848,15 +1917,6 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2"
[[package]]
name = "omnom"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b216cee2e0d6e680f73158d15468c80b39e571c11669cd90556f9a644e9fd3"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.4.0"
@@ -1900,9 +1960,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.57"
version = "0.9.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990"
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
dependencies = [
"autocfg 1.0.0",
"cc",
@@ -2019,29 +2079,29 @@ dependencies = [
[[package]]
name = "pin-project"
version = "0.4.17"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791"
checksum = "e75373ff9037d112bb19bc61333a06a159eaeb217660dcfbea7d88e1db823919"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "0.4.17"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
checksum = "10b4b44893d3c370407a1d6a5cfde7c41ae0478e31c516c85f67eb3adc51be6d"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
name = "pin-project-lite"
version = "0.1.5"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f"
checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715"
[[package]]
name = "pin-utils"
@@ -2051,9 +2111,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0deb65f46e873ba8aa7c6a8dbe3f23cb1bf59c339a81a1d56361dde4d66ac8"
checksum = "01608bfa680dafb103f9207fa944facf572e4e3e708d10de19a0d0c3d36e5f18"
dependencies = [
"crossbeam-utils",
"futures-io",
@@ -2079,6 +2139,16 @@ dependencies = [
"inflate",
]
[[package]]
name = "polyval"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ec3341498978de3bfd12d1b22f1af1de22818f5473a11e8a6ef997989e3a212"
dependencies = [
"cfg-if",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.8"
@@ -2116,15 +2186,15 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
[[package]]
name = "proc-macro-nested"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
checksum = "0afe1bd463b9e9ed51d0e0f0b50b6b146aec855c56fd182bb242388710a9b6de"
[[package]]
name = "proc-macro2"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
dependencies = [
"unicode-xid 0.2.0",
]
@@ -2181,9 +2251,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
[[package]]
name = "quote"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
@@ -2404,9 +2474,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.3.8"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226ddd1197737bcb937489322ec1b9edaac1709d46792886e70f2113923585a6"
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
"aho-corasick",
"memchr",
@@ -2446,8 +2516,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -2475,7 +2545,7 @@ dependencies = [
"num-iter",
"num-traits",
"rand 0.7.3",
"subtle",
"subtle 2.2.3",
"zeroize",
]
@@ -2491,7 +2561,7 @@ dependencies = [
"libsqlite3-sys",
"lru-cache",
"memchr",
"time",
"time 0.1.43",
]
[[package]]
@@ -2552,9 +2622,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "safemem"
@@ -2659,22 +2729,22 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
[[package]]
name = "serde"
version = "1.0.110"
version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c"
checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.110"
version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -2712,6 +2782,12 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.8.2"
@@ -2767,8 +2843,9 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]]
name = "smol"
version = "0.1.10"
source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#1ba84935e4e48be927c7f8f4a9cb5c05614135c3"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "845f5e7db6b614a8f4f59e565c3035f0fab2f71c9537ff0119eddb60d866cae3"
dependencies = [
"async-task",
"crossbeam-deque",
@@ -2809,12 +2886,70 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
[[package]]
name = "standback"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0437cfb83762844799a60e1e3b489d5ceb6a650fbacb86437badc1b6d87b246"
dependencies = [
"version_check 0.9.2",
]
[[package]]
name = "static_assertions"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
[[package]]
name = "stdweb"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
dependencies = [
"discard",
"rustc_version",
"stdweb-derive",
"stdweb-internal-macros",
"stdweb-internal-runtime",
"wasm-bindgen",
]
[[package]]
name = "stdweb-derive"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
"proc-macro2",
"quote 1.0.7",
"serde",
"serde_derive",
"syn 1.0.30",
]
[[package]]
name = "stdweb-internal-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
"proc-macro2",
"quote 1.0.7",
"serde",
"serde_derive",
"serde_json",
"sha1",
"syn 1.0.30",
]
[[package]]
name = "stdweb-internal-runtime"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "stop-token"
version = "0.1.2"
@@ -2854,21 +2989,27 @@ checksum = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81"
dependencies = [
"heck",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
name = "subtle"
version = "2.2.2"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
[[package]]
name = "subtle"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1"
[[package]]
name = "surf"
version = "2.0.0-alpha.2"
version = "2.0.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d72c302d4a24b2c9d412f1fe5edd892946778d33c423bba087c6c10deeb656ff"
checksum = "68bf1412b095b3a54da6ec3a969e2663cb3cf535652cd97c02e99443a400fd7d"
dependencies = [
"async-std",
"futures 0.3.5",
@@ -2897,12 +3038,12 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.27"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de"
checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"quote 1.0.7",
"unicode-xid 0.2.0",
]
@@ -2922,8 +3063,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
"unicode-xid 0.2.0",
]
@@ -2976,8 +3117,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
]
[[package]]
@@ -2999,13 +3140,51 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a51cadc5b1eec673a685ff7c33192ff7b7603d0b75446fb354939ee615acb15"
dependencies = [
"cfg-if",
"libc",
"standback",
"stdweb",
"time-macros",
"version_check 0.9.2",
"winapi",
]
[[package]]
name = "time-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d"
dependencies = [
"proc-macro-hack",
"time-macros-impl",
]
[[package]]
name = "time-macros-impl"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote 1.0.7",
"standback",
"syn 1.0.30",
]
[[package]]
name = "tokio-io"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes 0.4.12",
"bytes",
"futures 0.1.29",
"log",
]
@@ -3096,6 +3275,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "universal-hash"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01"
dependencies = [
"generic-array",
"subtle 2.2.3",
]
[[package]]
name = "url"
version = "2.1.1"
@@ -3124,9 +3313,9 @@ dependencies = [
[[package]]
name = "vcpkg"
version = "0.2.8"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
checksum = "55d1e41d56121e07f1e223db0a4def204e45c85425f6a16d462fd07c8d10d74c"
[[package]]
name = "version_check"
@@ -3192,8 +3381,8 @@ dependencies = [
"lazy_static",
"log",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
"wasm-bindgen-shared",
]
@@ -3215,7 +3404,7 @@ version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3"
dependencies = [
"quote 1.0.6",
"quote 1.0.7",
"wasm-bindgen-macro-support",
]
@@ -3226,8 +3415,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3334,7 +3523,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.27",
"quote 1.0.7",
"syn 1.0.30",
"synstructure",
]

View File

@@ -98,5 +98,3 @@ repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"]
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"]
nightly = ["pgp/nightly"]
[patch.crates-io]
smol = { git = "https://github.com/dignifiedquire/smol-1", branch = "isolate-nix" }

View File

@@ -1,19 +0,0 @@
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain nightly-2020-03-19
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- rustc -vV
- cargo -vV
build: false
test_script:
- cargo test --release --all
cache:
- target
- C:\Users\appveyor\.cargo\registry

View File

@@ -4427,7 +4427,8 @@ void dc_event_unref(dc_event_t* event);
#define DC_STR_DEVICE_MESSAGES_HINT 70
#define DC_STR_WELCOME_MESSAGE 71
#define DC_STR_UNKNOWN_SENDER_FOR_CHAT 72
#define DC_STR_COUNT 72
#define DC_STR_SUBJECT_FOR_NEW_CONTACT 73
#define DC_STR_COUNT 73
/*
* @}

View File

@@ -12,7 +12,7 @@ class EchoPlugin:
message.account.shutdown()
else:
# unconditionally accept the chat
message.accept_sender_contact()
message.create_chat()
addr = message.get_sender_contact().addr
if message.is_system_message():
message.chat.send_text("echoing system message from {}:\n{}".format(addr, message))

View File

@@ -12,7 +12,7 @@ class GroupTrackingPlugin:
message.account.shutdown()
else:
# unconditionally accept the chat
message.accept_sender_contact()
message.create_chat()
addr = message.get_sender_contact().addr
text = message.text
message.chat.send_text("echoing from {}:\n{}".format(addr, text))

View File

@@ -26,15 +26,15 @@ def test_echo_quit_plugin(acfactory, lp):
lp.sec("sending a message to the bot")
bot_contact = ac1.create_contact(botproc.addr)
ch1 = ac1.create_chat_by_contact(bot_contact)
ch1.send_text("hello")
bot_chat = bot_contact.create_chat()
bot_chat.send_text("hello")
lp.sec("waiting for the bot-reply to arrive")
lp.sec("waiting for the reply message from the bot to arrive")
reply = ac1._evtracker.wait_next_incoming_message()
assert reply.chat == bot_chat
assert "hello" in reply.text
assert reply.chat == ch1
lp.sec("send quit sequence")
ch1.send_text("/quit")
bot_chat.send_text("/quit")
botproc.wait()
@@ -47,8 +47,8 @@ def test_group_tracking_plugin(acfactory, lp):
botproc.fnmatch_lines("""
*ac_configure_completed*
""")
ac1.add_account_plugin(FFIEventLogger(ac1, "ac1"))
ac2.add_account_plugin(FFIEventLogger(ac2, "ac2"))
ac1.add_account_plugin(FFIEventLogger(ac1))
ac2.add_account_plugin(FFIEventLogger(ac2))
lp.sec("creating bot test group with bot")
bot_contact = ac1.create_contact(botproc.addr)

View File

@@ -18,7 +18,7 @@ def main():
description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat',
long_description=long_description,
author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors',
install_requires=['cffi>=1.0.0', 'pluggy'],
install_requires=['cffi>=1.0.0', 'pluggy', 'imapclient'],
packages=setuptools.find_packages('src'),
package_dir={'': 'src'},
cffi_modules=['src/deltachat/_build.py:ffibuilder'],

View File

@@ -60,7 +60,8 @@ def run_cmdline(argv=None, account_plugins=None):
ac = Account(args.db)
if args.show_ffi:
log = events.FFIEventLogger(ac, "bot")
ac.set_config("displayname", "bot")
log = events.FFIEventLogger(ac)
ac.add_account_plugin(log)
for plugin in account_plugins or []:

View File

@@ -28,7 +28,7 @@ class Account(object):
"""
MissingCredentials = MissingCredentials
def __init__(self, db_path, os_name=None, logging=True, logid=None):
def __init__(self, db_path, os_name=None, logging=True):
""" initialize account object.
:param db_path: a path to the account database. The database
@@ -38,7 +38,6 @@ class Account(object):
# initialize per-account plugin system
self._pm = hookspec.PerAccount._make_plugin_manager()
self._logging = logging
self.logid = logid
self.add_account_plugin(self)
@@ -214,22 +213,40 @@ class Account(object):
"""
return Contact(self, const.DC_CONTACT_ID_SELF)
def create_contact(self, email, name=None):
""" create a (new) Contact. If there already is a Contact
with that e-mail address, it is unblocked and its name is
updated.
def create_contact(self, obj, name=None):
""" create a (new) Contact or return an existing one.
:param email: email-address (text type)
:param name: display name for this contact (optional)
Calling this method will always resulut in the same
underlying contact id. If there already is a Contact
with that e-mail address, it is unblocked and its display
`name` is updated if specified.
:param obj: email-address, Account or Contact instance.
:param name: (optional) display name for this contact
:returns: :class:`deltachat.contact.Contact` instance.
"""
realname, addr = parseaddr(email)
if name:
realname = name
realname = as_dc_charpointer(realname)
if isinstance(obj, Account):
if not obj.is_configured():
raise ValueError("can only add addresses from configured accounts")
addr, displayname = obj.get_config("addr"), obj.get_config("displayname")
elif isinstance(obj, Contact):
if obj.account != self:
raise ValueError("account mismatch {}".format(obj))
addr, displayname = obj.addr, obj.name
elif isinstance(obj, str):
displayname, addr = parseaddr(obj)
else:
raise TypeError("don't know how to create chat for %r" % (obj, ))
if name is None and displayname:
name = displayname
return self._create_contact(addr, name)
def _create_contact(self, addr, name):
addr = as_dc_charpointer(addr)
contact_id = lib.dc_create_contact(self._dc_context, realname, addr)
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
name = as_dc_charpointer(name)
contact_id = lib.dc_create_contact(self._dc_context, name, addr)
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL, contact_id
return Contact(self, contact_id)
def delete_contact(self, contact):
@@ -251,6 +268,13 @@ class Account(object):
if contact_id:
return self.get_contact_by_id(contact_id)
def get_contact_by_id(self, contact_id):
""" return Contact instance or None.
:param contact_id: integer id of this contact.
:returns: None or :class:`deltachat.contact.Contact` instance.
"""
return Contact(self, contact_id)
def get_contacts(self, query=None, with_self=False, only_verified=False):
""" get a (filtered) list of contacts.
@@ -280,53 +304,29 @@ class Account(object):
)
yield from iter_array(dc_array, lambda x: Message.from_db(self, x))
def create_chat_by_contact(self, contact):
""" create or get an existing 1:1 chat object for the specified contact or contact id.
def create_chat(self, obj):
""" Create a 1:1 chat with Account, Contact or e-mail address. """
return self.create_contact(obj).create_chat()
:param contact: chat_id (int) or contact object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(contact, "id"):
if contact.account != self:
raise ValueError("Contact belongs to a different Account")
contact_id = contact.id
else:
assert isinstance(contact, int)
contact_id = contact
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id)
return Chat(self, chat_id)
def _create_chat_by_message_id(self, msg_id):
return Chat(self, lib.dc_create_chat_by_msg_id(self._dc_context, msg_id))
def create_chat_by_message(self, message):
""" create or get an existing chat object for the
the specified message.
If this message is in the deaddrop chat then
the sender will become an accepted contact.
:param message: messsage id or message instance.
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(message, "id"):
if message.account != self:
raise ValueError("Message belongs to a different Account")
msg_id = message.id
else:
assert isinstance(message, int)
msg_id = message
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)
return Chat(self, chat_id)
def create_group_chat(self, name, verified=False):
def create_group_chat(self, name, contacts=None, verified=False):
""" create a new group chat object.
Chats are unpromoted until the first message is sent.
:param contacts: list of contacts to add
:param verified: if true only verified contacts can be added.
:returns: a :class:`deltachat.chat.Chat` object.
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
return Chat(self, chat_id)
chat = Chat(self, chat_id)
if contacts is not None:
for contact in contacts:
chat.add_contact(contact)
return chat
def get_chats(self):
""" return list of chats.
@@ -355,13 +355,6 @@ class Account(object):
"""
return Message.from_db(self, msg_id)
def get_contact_by_id(self, contact_id):
""" return Contact instance or None.
:param contact_id: integer id of this contact.
:returns: None or :class:`deltachat.contact.Contact` instance.
"""
return Contact(self, contact_id)
def get_chat_by_id(self, chat_id):
""" return Chat instance.
:param chat_id: integer id of this chat.
@@ -581,7 +574,7 @@ class Account(object):
raise MissingCredentials("addr or mail_pwd not set in config")
if hasattr(self, "_configtracker"):
self.remove_account_plugin(self._configtracker)
self._configtracker = ConfigureTracker()
self._configtracker = ConfigureTracker(self)
self.add_account_plugin(self._configtracker)
lib.dc_configure(self._dc_context)

View File

@@ -18,6 +18,8 @@ class Chat(object):
"""
def __init__(self, account, id):
from .account import Account
assert isinstance(account, Account), repr(account)
self.account = account
self.id = id
@@ -328,33 +330,34 @@ class Chat(object):
# ------ group management API ------------------------------
def add_contact(self, contact):
def add_contact(self, obj):
""" add a contact to this chat.
:params: contact object.
:params obj: Contact, Account or e-mail address.
:raises ValueError: if contact could not be added
:returns: None
"""
contact = self.account.create_contact(obj)
ret = lib.dc_add_contact_to_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not add contact {!r} to chat".format(contact))
return contact
def remove_contact(self, contact):
def remove_contact(self, obj):
""" remove a contact from this chat.
:params: contact object.
:params obj: Contact, Account or e-mail address.
:raises ValueError: if contact could not be removed
:returns: None
"""
contact = self.account.create_contact(obj)
ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not remove contact {!r} from chat".format(contact))
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
"""
from .contact import Contact
dc_array = ffi.gc(
@@ -365,6 +368,14 @@ class Chat(object):
dc_array, lambda id: Contact(self.account, id))
)
def num_contacts(self):
""" return number of contacts in this chat. """
dc_array = ffi.gc(
lib.dc_get_chat_contacts(self.account._dc_context, self.id),
lib.dc_array_unref
)
return lib.dc_array_get_cnt(dc_array)
def set_profile_image(self, img_path):
"""Set group profile image.

View File

@@ -3,6 +3,8 @@
from . import props
from .cutil import from_dc_charpointer
from .capi import lib, ffi
from .chat import Chat
from . import const
class Contact(object):
@@ -11,6 +13,8 @@ class Contact(object):
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, account, id):
from .account import Account
assert isinstance(account, Account), repr(account)
self.account = account
self.id = id
@@ -36,10 +40,13 @@ class Contact(object):
return from_dc_charpointer(lib.dc_contact_get_addr(self._dc_contact))
@props.with_doc
def display_name(self):
def name(self):
""" display name for this contact. """
return from_dc_charpointer(lib.dc_contact_get_display_name(self._dc_contact))
# deprecated alias
display_name = name
def is_blocked(self):
""" Return True if the contact is blocked. """
return lib.dc_contact_is_blocked(self._dc_contact)
@@ -58,6 +65,16 @@ class Contact(object):
return None
return from_dc_charpointer(dc_res)
def get_chat(self):
"""return 1:1 chat for this contact. """
return self.account.create_chat_by_contact(self)
def create_chat(self):
""" create or get an existing 1:1 chat object for the specified contact or contact id.
:param contact: chat_id (int) or contact object.
:returns: a :class:`deltachat.chat.Chat` object.
"""
dc_context = self.account._dc_context
chat_id = lib.dc_create_chat_by_contact_id(dc_context, self.id)
assert chat_id > const.DC_CHAT_ID_LAST_SPECIAL, chat_id
return Chat(self.account, chat_id)
# deprecated name
get_chat = create_chat

View File

@@ -1,141 +1,211 @@
import imaplib
"""
Internal Python-level IMAP handling used by the testplugin
and for cleaning up inbox/mvbox for each test function run.
"""
import io
import email
import ssl
import pathlib
from . import Account
INBOX = "Inbox"
SENT = "Sent"
MVBOX = "DeltaChat"
MVBOX_FALLBBACK = "INBOX/DeltaChat"
DC_CONSTANT_MSG_MOVESTATE_PENDING = 1
DC_CONSTANT_MSG_MOVESTATE_STAY = 2
DC_CONSTANT_MSG_MOVESTATE_MOVING = 3
from imapclient import IMAPClient
from imapclient.exceptions import IMAPClientError
import deltachat
def db_folder_attr(name):
def fget(s):
return s.db_folder.get(name, 1)
def fset(s, val):
s.db_folder[name] = val
return property(fget, fset, None, None)
SEEN = b'\\Seen'
DELETED = b'\\Deleted'
FLAGS = b'FLAGS'
FETCH = b'FETCH'
ALL = "1:*"
class ImapConn():
def __init__(self, foldername, conn_info):
self.foldername = foldername
host, user, pw = conn_info
@deltachat.global_hookimpl
def dc_account_extra_configure(account):
""" Reset the account (we reuse accounts across tests)
and make 'account.direct_imap' available for direct IMAP ops.
"""
imap = DirectImap(account)
if imap.select_config_folder("mvbox"):
imap.delete(ALL, expunge=True)
assert imap.select_config_folder("inbox")
imap.delete(ALL, expunge=True)
setattr(account, "direct_imap", imap)
self.connection = imaplib.IMAP4_SSL(host)
self.connection.login(user, pw)
messages = self.reselect_folder()
@deltachat.global_hookimpl
def dc_account_after_shutdown(account):
""" shutdown the imap connection if there is one. """
imap = getattr(account, "direct_imap", None)
if imap is not None:
imap.shutdown()
del account.direct_imap
class DirectImap:
def __init__(self, account):
self.account = account
self.logid = account.get_config("displayname") or id(account)
self._idling = False
self.connect()
def connect(self):
ssl_context = ssl.create_default_context()
# don't check if certificate hostname doesn't match target hostname
ssl_context.check_hostname = False
# don't check if the certificate is trusted by a certificate authority
ssl_context.verify_mode = ssl.CERT_NONE
host = self.account.get_config("configured_mail_server")
user = self.account.get_config("addr")
pw = self.account.get_config("mail_pw")
self.conn = IMAPClient(host, ssl_context=ssl_context)
self.conn.login(user, pw)
self.select_folder("INBOX")
def shutdown(self):
try:
self.original_msg_count = int(messages[0])
except IndexError:
self.original_msg_count = 0
def mark_all_read(self):
self.reselect_folder()
# result, data = self.connection.uid('search', None, "(UNSEEN)")
result, data = self.connection.search(None, 'UnSeen')
try:
mails_uid = data[0].split()
print("New mails")
# self.connection.store(data[0].replace(' ',','),'+FLAGS','\Seen')
for e_id in mails_uid:
self.connection.store(e_id, '+FLAGS', '\\Seen')
print("marked:", e_id)
return True
except IndexError:
print("No unread")
return False
def get_unread_cnt(self):
self.reselect_folder()
# result, data = self.connection.uid('search', None, "(UNSEEN)")
result, data = self.connection.search(None, 'UnSeen')
try:
mails_uid = data[0].split()
return len(mails_uid)
except IndexError:
return 0
def get_new_email_cnt(self):
messages = self.reselect_folder()
try:
return int(messages[0]) - self.original_msg_count
except IndexError:
return 0
def reselect_folder(self):
status, messages = self.connection.select(self.foldername)
if status != "OK":
print("Incorrect mail box " + status + str(messages))
raise ConnectionError
# print("(Re-)Selected mailbox: " + status + " " + str(messages))
return messages
def __del__(self):
try:
self.connection.close()
except Exception:
self.conn.idle_done()
except (OSError, IMAPClientError):
pass
try:
self.connection.logout()
except Exception:
self.conn.logout()
except (OSError, IMAPClientError):
print("Could not logout direct_imap conn")
def select_folder(self, foldername):
assert not self._idling
return self.conn.select_folder(foldername)
def make_direct_imap(account, folder):
conn_info = (account.get_config("configured_mail_server"),
account.get_config("addr"), account.get_config("mail_pw"))
# try:
# return ImapConn(folder, conn_info=conn_info)
# except ConnectionError as e:
# if folder == MVBOX:
# account.log("Selecting " + MVBOX_FALLBBACK + " not " + MVBOX + " because connecting to the latter failed")
# return ImapConn(MVBOX_FALLBBACK, conn_info=conn_info)
# else:
# raise e
if folder == MVBOX:
new_folder = account.get_config("configured_mvbox_folder")
else:
new_folder = folder
if new_folder != folder:
account.log("Making connection with " + new_folder + " not " + folder)
return ImapConn(new_folder, conn_info=conn_info)
def select_config_folder(self, config_name):
""" Return info about selected folder if it is
configured, otherwise None. """
if "_" not in config_name:
config_name = "configured_{}_folder".format(config_name)
foldername = self.account.get_config(config_name)
if foldername:
return self.select_folder(foldername)
def list_folders(self):
""" return list of all existing folder names"""
assert not self._idling
folders = []
for meta, sep, foldername in self.conn.list_folders():
folders.append(foldername)
return folders
def print_imap_structure(database, dir="."):
print_imap_structure_ac(Account(database), dir)
def delete(self, range, expunge=True):
""" delete a range of messages (imap-syntax).
If expunge is true, perform the expunge-operation
to make sure the messages are really gone and not
just flagged as deleted.
"""
self.conn.set_flags(range, [DELETED])
if expunge:
self.conn.expunge()
def get_all_messages(self):
assert not self._idling
return self.conn.fetch(ALL, [FLAGS])
def print_imap_structure_ac(ac, dir="."):
acinfo = ac.logid + "-" + ac.get_config("addr")
print("================= ACCOUNT", acinfo, "=================")
print("----------------- CONFIG: -----------------")
print(ac.get_info())
def get_unread_messages(self):
assert not self._idling
res = self.conn.fetch(ALL, [FLAGS])
return [uid for uid in res
if SEEN not in res[uid][FLAGS]]
for imapfolder in [INBOX, MVBOX, SENT, MVBOX_FALLBBACK]:
try:
imap = make_direct_imap(ac, imapfolder)
c = imap.connection
typ, data = c.search(None, 'ALL')
c._get_tagged_response
print("-----------------", imapfolder, "-----------------")
for num in data[0].split():
typ, data = c.fetch(num, '(RFC822)')
body = data[0][1]
def mark_all_read(self):
messages = self.get_unread_messages()
if messages:
res = self.conn.set_flags(messages, [SEEN])
print("marked seen:", messages, res)
typ, data = c.fetch(num, '(UID FLAGS)')
info = data[0]
def get_unread_cnt(self):
return len(self.get_unread_messages())
path = pathlib.Path(dir).joinpath("IMAP-MESSAGES", acinfo, imapfolder)
def dump_account_info(self, logfile):
def log(*args, **kwargs):
kwargs["file"] = logfile
print(*args, **kwargs)
cursor = 0
for name, val in self.account.get_info().items():
entry = "{}={}".format(name.upper(), val)
if cursor + len(entry) > 80:
log("")
cursor = 0
log(entry, end=" ")
cursor += len(entry) + 1
log("")
def dump_imap_structures(self, dir, logfile):
assert not self._idling
stream = io.StringIO()
def log(*args, **kwargs):
kwargs["file"] = stream
print(*args, **kwargs)
empty_folders = []
for imapfolder in self.list_folders():
self.select_folder(imapfolder)
messages = list(self.get_all_messages())
if not messages:
empty_folders.append(imapfolder)
continue
log("---------", imapfolder, len(messages), "messages ---------")
# get message content without auto-marking it as seen
# fetching 'RFC822' would mark it as seen.
requested = [b'BODY.PEEK[HEADER]', FLAGS]
for uid, data in self.conn.fetch(messages, requested).items():
body_bytes = data[b'BODY[HEADER]']
flags = data[FLAGS]
path = pathlib.Path(str(dir)).joinpath("IMAP", self.logid, imapfolder)
path.mkdir(parents=True, exist_ok=True)
file = path.joinpath(str(info).replace("b'", "").replace("'", "").replace("\\", ""))
file.write_bytes(body)
print("Message", info, "saved as", file)
except Exception:
pass
fn = path.joinpath(str(uid))
fn.write_bytes(body_bytes)
log("Message", uid, fn)
email_message = email.message_from_bytes(body_bytes)
log("Message", uid, flags, "Message-Id:", email_message.get("Message-Id"))
if empty_folders:
log("--------- EMPTY FOLDERS:", empty_folders)
print(stream.getvalue(), file=logfile)
def idle_start(self):
""" switch this connection to idle mode. non-blocking. """
assert not self._idling
res = self.conn.idle()
self._idling = True
return res
def idle_check(self, terminate=False):
""" (blocking) wait for next idle message from server. """
assert self._idling
self.account.log("imap-direct: calling idle_check")
res = self.conn.idle_check()
if terminate:
self.idle_done()
return res
def idle_wait_for_seen(self):
""" Return first message with SEEN flag
from a running idle-stream REtiurn.
"""
while 1:
for item in self.idle_check():
if item[1] == FETCH:
if item[2][0] == FLAGS:
if SEEN in item[2][1]:
return item[0]
def idle_done(self):
""" send idle-done to server if we are currently in idle mode. """
if self._idling:
res = self.conn.idle_done()
self._idling = False
return res

View File

@@ -28,13 +28,9 @@ class FFIEventLogger:
# to prevent garbled logging
_loglock = threading.RLock()
def __init__(self, account, logid):
"""
:param logid: an optional logging prefix that should be used with
the default internal logging.
"""
def __init__(self, account):
self.account = account
self.logid = logid
self.logid = self.account.get_config("displayname")
self.init_time = time.time()
@account_hookimpl
@@ -127,6 +123,12 @@ class FFIEventTracker:
if ev.data2 > 0:
return self.account.get_message_by_id(ev.data2)
def wait_msg_delivered(self, msg):
ev = self.get_matching("DC_EVENT_MSG_DELIVERED")
assert ev.data1 == msg.chat.id
assert ev.data2 == msg.id
assert msg.is_out_delivered()
class EventThread(threading.Thread):
""" Event Thread for an account.

View File

@@ -43,7 +43,7 @@ class PerAccount:
@account_hookspec
def ac_configure_completed(self, success):
""" Called when a configure process completed. """
""" Called after a configure process completed. """
@account_hookspec
def ac_incoming_message(self, message):
@@ -88,6 +88,14 @@ class Global:
def dc_account_init(self, account):
""" called when `Account::__init__()` function starts executing. """
@global_hookspec
def dc_account_extra_configure(self, account):
""" Called when account configuration successfully finished.
This hook can be used to perform extra work before
ac_configure_completed is called.
"""
@global_hookspec
def dc_account_after_shutdown(self, account):
""" Called after the account has been shutdown. """

View File

@@ -53,15 +53,19 @@ class Message(object):
lib.dc_msg_unref
))
def accept_sender_contact(self):
""" ensure that the sender is an accepted contact
and that the message has a non-deaddrop chat object.
def create_chat(self):
""" create or get an existing chat (group) object for this message.
If the message is a deaddrop contact request
the sender will become an accepted contact.
:returns: a :class:`deltachat.chat.Chat` object.
"""
self.account.create_chat_by_message(self)
self._dc_msg = ffi.gc(
lib.dc_get_msg(self.account._dc_context, self.id),
lib.dc_msg_unref
)
from .chat import Chat
chat_id = lib.dc_create_chat_by_msg_id(self.account._dc_context, self.id)
ctx = self.account._dc_context
self._dc_msg = ffi.gc(lib.dc_get_msg(ctx, self.id), lib.dc_msg_unref)
return Chat(self.account, chat_id)
@props.with_doc
def text(self):

View File

@@ -1,6 +1,7 @@
from __future__ import print_function
import os
import sys
import io
import subprocess
import queue
import threading
@@ -12,10 +13,11 @@ import tempfile
import pytest
import requests
from . import Account, const, direct_imap
from . import Account, const
from .capi import lib
from .events import FFIEventLogger, FFIEventTracker
from _pytest._code import Source
from deltachat import direct_imap
import deltachat
@@ -213,6 +215,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
self._generated_keys = ["alice", "bob", "charlie",
"dom", "elena", "fiona"]
self.set_logging_default(False)
deltachat.register_global_plugin(direct_imap)
def finalize(self):
while self._finalizers:
@@ -223,13 +226,15 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
acc = self._accounts.pop()
acc.shutdown()
acc.disable_logging()
deltachat.unregister_global_plugin(direct_imap)
def make_account(self, path, logid, quiet=False):
ac = Account(path, logging=self._logging, logid=logid)
ac = Account(path, logging=self._logging)
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
ac.addr = ac.get_self_contact().addr
ac.set_config("displayname", logid)
if not quiet:
ac.add_account_plugin(FFIEventLogger(ac, logid=logid))
ac.add_account_plugin(FFIEventLogger(ac))
self._accounts.append(ac)
return ac
@@ -309,7 +314,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
return ac1
def get_two_online_accounts(self, move=False, quiet=False):
ac1 = self.get_online_configuring_account(move=True, quiet=quiet)
ac1 = self.get_online_configuring_account(move=move, quiet=quiet)
ac2 = self.get_online_configuring_account(quiet=quiet)
ac1.wait_configure_finish()
ac1.start_io()
@@ -317,12 +322,16 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
ac2.start_io()
return ac1, ac2
def get_many_online_accounts(self, num, move=True, quiet=True):
accounts = [self.get_online_configuring_account(move=move, quiet=quiet)
def get_many_online_accounts(self, num, move=True):
accounts = [self.get_online_configuring_account(move=move, quiet=True)
for i in range(num)]
for acc in accounts:
acc._configtracker.wait_finish()
acc.start_io()
print("{}: {} account was successfully setup".format(
acc.get_config("displayname"), acc.get_config("addr")))
for acc in accounts:
acc.add_account_plugin(FFIEventLogger(acc))
return accounts
def clone_online_account(self, account, pre_generated_key=True):
@@ -372,12 +381,42 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
self._finalizers.append(bot.kill)
return bot
def dump_imap_summary(self, logfile):
for ac in self._accounts:
imap = getattr(ac, "direct_imap", None)
if imap is not None:
try:
imap.idle_done()
except Exception:
pass
imap.dump_account_info(logfile=logfile)
imap.dump_imap_structures(tmpdir, logfile=logfile)
def get_accepted_chat(self, ac1, ac2):
ac2.create_chat(ac1)
return ac1.create_chat(ac2)
def introduce_each_other(self, accounts, sending=True):
to_wait = []
for i, acc in enumerate(accounts):
for acc2 in accounts[i + 1:]:
chat = self.get_accepted_chat(acc, acc2)
if sending:
chat.send_text("hi")
to_wait.append(acc2)
acc2.create_chat(acc).send_text("hi back")
to_wait.append(acc)
for acc in to_wait:
acc._evtracker.wait_next_incoming_message()
am = AccountMaker()
request.addfinalizer(am.finalize)
yield am
if request.node.rep_call.failed:
for ac in am._accounts:
direct_imap.print_imap_structure_ac(ac, tmpdir)
if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
logfile = io.StringIO()
am.dump_imap_summary(logfile=logfile)
print(logfile.getvalue())
# request.node.add_report_section("call", "imap-server-state", s)
class BotProcess:
@@ -445,6 +484,7 @@ def lp():
def step(self, msg):
print("-" * 5, "step " + msg, "-" * 5)
return Printer()

View File

@@ -2,7 +2,7 @@
from queue import Queue
from threading import Event
from .hookspec import account_hookimpl
from .hookspec import account_hookimpl, Global
class ImexFailed(RuntimeError):
@@ -40,12 +40,14 @@ class ConfigureFailed(RuntimeError):
class ConfigureTracker:
ConfigureFailed = ConfigureFailed
def __init__(self):
def __init__(self, account):
self.account = account
self._configure_events = Queue()
self._smtp_finished = Event()
self._imap_finished = Event()
self._ffi_events = []
self._progress = Queue()
self._gm = Global._get_plugin_manager()
@account_hookimpl
def ac_process_ffi_event(self, ffi_event):
@@ -59,6 +61,8 @@ class ConfigureTracker:
@account_hookimpl
def ac_configure_completed(self, success):
if success:
self._gm.hook.dc_account_extra_configure(account=self.account)
self._configure_events.put(success)
def wait_smtp_connected(self):

View File

@@ -110,7 +110,7 @@ class AutoReplier:
if self.current_sent >= self.num_send:
self.report_func(self, ReportType.exit)
return
message.accept_sender_contact()
message.create_chat()
message.mark_seen()
self.log("incoming message: {}".format(message))

File diff suppressed because it is too large Load Diff

View File

@@ -36,9 +36,7 @@ def wait_msgs_changed(account, msgs_list):
class TestOnlineInCreation:
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
chat = ac1.create_chat(ac2)
lp.sec("Creating in-creation file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
@@ -48,9 +46,7 @@ class TestOnlineInCreation:
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
chat = ac1.create_chat(ac2)
lp.sec("Creating file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
@@ -64,9 +60,7 @@ class TestOnlineInCreation:
def test_forward_increation(self, acfactory, data, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
chat = ac1.create_chat(ac2)
wait_msgs_changed(ac1, [(0, 0)]) # why no chat id?
lp.sec("create a message with a file in creation")
@@ -80,7 +74,7 @@ class TestOnlineInCreation:
lp.sec("forward the message while still in creation")
chat2 = ac1.create_group_chat("newgroup")
chat2.add_contact(c2)
chat2.add_contact(ac2)
wait_msgs_changed(ac1, [(0, 0)]) # why not chat id?
ac1.forward_messages([prepared_original], chat2)
# XXX there might be two EVENT_MSGS_CHANGED and only one of them

View File

@@ -69,8 +69,8 @@ def test_sig():
def test_markseen_invalid_message_ids(acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
contact1 = ac1.create_contact("some1@example.com", name="some1")
chat = contact1.create_chat()
chat.send_text("one messae")
ac1._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
msg_ids = [9]

21
spec.md
View File

@@ -1,10 +1,12 @@
# Chat-over-Email specification
# chat-mail specification
Version 0.30.0
Version: 0.32.0
Status: In-progress
Format: [Semantic Line Breaks](https://sembr.org/)
This document describes how emails can be used
to implement typical messenger functions
while staying compatible to existing MUAs.
This document roughly describes how chat-mail
apps use the standard e-mail system
to implement typical messenger functions.
- [Encryption](#encryption)
- [Outgoing messages](#outgoing-messages)
@@ -30,17 +32,14 @@ Messages SHOULD be encrypted by the
`prefer-encrypt=mutual` MAY be set by default.
Meta data (at least the subject and all chat-headers) SHOULD be encrypted
by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
If Memoryhole is not used,
the subject of encrypted messages SHOULD be replaced by the string `...`.
by the [Protected Headers](https://www.ietf.org/id/draft-autocrypt-lamps-protected-headers-02.html) standard.
# Outgoing messages
Messengers MUST add a `Chat-Version: 1.0` header to outgoing messages.
For filtering and smart appearance of the messages in normal MUAs,
the `Subject` header SHOULD start with the characters `Chat:`
and SHOULD be an excerpt of the message.
the `Subject` header SHOULD be `Message from <sender name>`.
Replies to messages MAY follow the typical `Re:`-format.
The body MAY contain text which MUST have the content type `text/plain`
@@ -58,7 +57,7 @@ Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
To: rcpt@domain
Chat-Version: 1.0
Content-Type: text/plain
Subject: Chat: Hello ...
Subject: Message from sender@domain
Hello world!

View File

@@ -8,11 +8,14 @@ use async_std::prelude::*;
use async_std::{fs, io};
use image::GenericImageView;
use num_traits::FromPrimitive;
use thiserror::Error;
use crate::constants::AVATAR_SIZE;
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::events::Event;
use crate::message;
/// Represents a file in the blob directory.
///
@@ -377,6 +380,44 @@ impl<'a> BlobObject<'a> {
Ok(())
}
pub async fn recode_to_image_size(&self, context: &Context) -> Result<(), BlobError> {
let blob_abs = self.to_abs_path();
if message::guess_msgtype_from_suffix(Path::new(&blob_abs))
!= Some((Viewtype::Image, "image/jpeg"))
{
return Ok(());
}
let img = image::open(&blob_abs).map_err(|err| BlobError::RecodeFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
cause: err,
})?;
let img_wh = if MediaQuality::from_i32(context.get_config_int(Config::MediaQuality).await)
.unwrap_or_default()
== MediaQuality::Balanced
{
BALANCED_IMAGE_SIZE
} else {
WORSE_IMAGE_SIZE
};
if img.width() <= img_wh && img.height() <= img_wh {
return Ok(());
}
let img = img.thumbnail(img_wh, img_wh);
img.save(&blob_abs).map_err(|err| BlobError::WriteFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: blob_abs.to_str().unwrap_or_default().to_string(),
cause: err,
})?;
Ok(())
}
}
impl<'a> fmt::Display for BlobObject<'a> {

View File

@@ -39,18 +39,9 @@ impl ChatId {
ChatId(id)
}
/// A ChatID which indicates an error.
///
/// This is transitional and should not be used in new code. Do
/// not represent errors in a ChatId.
pub fn is_error(self) -> bool {
self.0 == 0
}
/// An unset ChatId
///
/// Like [ChatId::is_error], from which it is indistinguishable, this is
/// transitional and should not be used in new code.
/// This is transitional and should not be used in new code.
pub fn is_unset(self) -> bool {
self.0 == 0
}
@@ -431,15 +422,35 @@ impl ChatId {
}
async fn get_parent_mime_headers(self, context: &Context) -> Option<(String, String, String)> {
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
self.parent_query(
context,
"rfc724_mid, mime_in_reply_to, mime_references",
collect,
)
.await
.ok()
.flatten()
let collect =
|row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?));
let (packed, rfc724_mid, mime_in_reply_to, mime_references): (
String,
String,
String,
String,
) = self
.parent_query(
context,
"param, rfc724_mid, mime_in_reply_to, mime_references",
collect,
)
.await
.ok()
.flatten()?;
let param = packed.parse::<Params>().ok()?;
if param.exists(Param::Error) {
// Do not reply to error messages.
//
// An error message could be a group chat message that we failed to decrypt and
// assigned to 1:1 chat. A reply to it will show up as a reply to group message
// on the other side. To avoid such situations, it is better not to reply to
// error messages at all.
None
} else {
Some((rfc724_mid, mime_in_reply_to, mime_references))
}
}
async fn parent_is_encrypted(self, context: &Context) -> Result<bool, Error> {
@@ -448,7 +459,7 @@ impl ChatId {
if let Some(ref packed) = packed {
let param = packed.parse::<Params>()?;
Ok(param.exists(Param::GuaranteeE2ee))
Ok(!param.exists(Param::Error) && param.exists(Param::GuaranteeE2ee))
} else {
// No messages
Ok(false)
@@ -1341,6 +1352,12 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Er
.ok_or_else(|| {
format_err!("Attachment missing for message of type #{}", msg.viewtype)
})?;
if msg.viewtype == Viewtype::Image {
if let Err(e) = blob.recode_to_image_size(context).await {
warn!(context, "Cannot recode image, using original data: {:?}", e);
}
}
msg.param.set(Param::File, blob.as_name());
if msg.viewtype == Viewtype::File || msg.viewtype == Viewtype::Image {
@@ -1930,20 +1947,19 @@ pub async fn create_group_chat(
.sql
.get_rowid(context, "chats", "grpid", grpid)
.await?;
let chat_id = ChatId::new(row_id);
if !chat_id.is_error() {
if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await {
let mut draft_msg = Message::new(Viewtype::Text);
draft_msg.set_text(Some(draft_txt));
chat_id.set_draft_raw(context, &mut draft_msg).await;
}
context.emit_event(Event::MsgsChanged {
msg_id: MsgId::new(0),
chat_id: ChatId::new(0),
});
let chat_id = ChatId::new(row_id);
if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await {
let mut draft_msg = Message::new(Viewtype::Text);
draft_msg.set_text(Some(draft_txt));
chat_id.set_draft_raw(context, &mut draft_msg).await;
}
context.emit_event(Event::MsgsChanged {
msg_id: MsgId::new(0),
chat_id: ChatId::new(0),
});
Ok(chat_id)
}

View File

@@ -227,6 +227,10 @@ pub const DC_BOB_SUCCESS: i32 = 1;
// max. width/height of an avatar
pub const AVATAR_SIZE: u32 = 192;
// max. width/height of images
pub const BALANCED_IMAGE_SIZE: u32 = 1280;
pub const WORSE_IMAGE_SIZE: u32 = 640;
// this value can be increased if the folder configuration is changed and must be redone on next program start
pub const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3;

View File

@@ -10,7 +10,7 @@ use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::error::{bail, ensure, Result};
use crate::error::{bail, ensure, format_err, Result};
use crate::events::Event;
use crate::headerdef::HeaderDef;
use crate::job::{self, Action};
@@ -749,6 +749,31 @@ async fn add_parts(
}
}
async fn update_last_subject(
context: &Context,
chat_id: ChatId,
mime_parser: &MimeMessage,
) -> Result<()> {
let mut chat = Chat::load_from_db(context, chat_id).await?;
chat.param.set(
Param::LastSubject,
mime_parser
.get_subject()
.ok_or_else(|| format_err!("No subject in email"))?,
);
chat.update_param(context).await?;
Ok(())
}
update_last_subject(context, chat_id, mime_parser)
.await
.unwrap_or_else(|e| {
warn!(
context,
"Could not update LastSubject of chat: {}",
e.to_string()
)
});
Ok(())
}
@@ -889,32 +914,29 @@ async fn create_or_lookup_group(
}
if grpid.is_empty() {
if let Some(value) = mime_parser.get(HeaderDef::MessageId) {
if let Some(extracted_grpid) = dc_extract_grpid_from_rfc724_mid(&value) {
grpid = extracted_grpid.to_string();
}
}
if grpid.is_empty() {
if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
grpid = extracted_grpid.to_string();
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References)
{
grpid = extracted_grpid.to_string();
} else {
return create_or_lookup_adhoc_group(
context,
mime_parser,
allow_creation,
create_blocked,
from_id,
to_ids,
)
.await
.map_err(|err| {
info!(context, "could not create adhoc-group: {:?}", err);
err
});
}
if let Some(extracted_grpid) = mime_parser
.get(HeaderDef::MessageId)
.and_then(|value| dc_extract_grpid_from_rfc724_mid(&value))
{
grpid = extracted_grpid.to_string();
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
grpid = extracted_grpid.to_string();
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References) {
grpid = extracted_grpid.to_string();
} else {
return create_or_lookup_adhoc_group(
context,
mime_parser,
allow_creation,
create_blocked,
from_id,
to_ids,
)
.await
.map_err(|err| {
info!(context, "could not create adhoc-group: {:?}", err);
err
});
}
}
// now we have a grpid that is non-empty
@@ -1001,7 +1023,7 @@ async fn create_or_lookup_group(
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid)
.await
.unwrap_or((ChatId::new(0), false, Blocked::Not));
if !chat_id.is_error() {
if !chat_id.is_unset() {
if chat_id_verified {
if let Err(err) =
check_verified_properties(context, mime_parser, from_id as u32, to_ids).await
@@ -1032,7 +1054,7 @@ async fn create_or_lookup_group(
.await
.unwrap_or_default();
if chat_id.is_error()
if chat_id.is_unset()
&& !mime_parser.is_mailinglist_message()
&& !grpid.is_empty()
&& grpname.is_some()
@@ -1205,10 +1227,6 @@ async fn create_or_lookup_adhoc_group(
from_id: u32,
to_ids: &ContactIds,
) -> Result<(ChatId, Blocked)> {
// if we're here, no grpid was found, check if there is an existing
// ad-hoc group matching the to-list or if we should and can create one
// (we do not want to heuristically look at the likely mangled Subject)
if mime_parser.is_mailinglist_message() {
// XXX we could parse List-* headers and actually create and
// manage a mailing list group, eventually
@@ -1219,6 +1237,10 @@ async fn create_or_lookup_adhoc_group(
return Ok((ChatId::new(0), Blocked::Not));
}
// if we're here, no grpid was found, check if there is an existing
// ad-hoc group matching the to-list or if we should and can create one
// (we do not want to heuristically look at the likely mangled Subject)
let mut member_ids: Vec<u32> = to_ids.iter().copied().collect();
if !member_ids.contains(&from_id) {
member_ids.push(from_id);
@@ -1271,6 +1293,24 @@ async fn create_or_lookup_adhoc_group(
return Ok((ChatId::new(0), Blocked::Not));
}
if mime_parser.decrypting_failed {
// Do not create a new ad-hoc group if the message cannot be
// decrypted.
//
// The subject may be encrypted and contain a placeholder such
// as "...". Besides that, it is possible that the message was
// sent to a valid, yet unknown group, which was rejected
// because Chat-Group-Name, which is in the encrypted part,
// was not found. Generating a new ID in this case would
// result in creation of a twin group with a different group
// ID.
warn!(
context,
"not creating ad-hoc group for message that cannot be decrypted"
);
return Ok((ChatId::new(0), Blocked::Not));
}
// we do not check if the message is a reply to another group, this may result in
// chats with unclear member list. instead we create a new group in the following lines ...

View File

@@ -498,12 +498,12 @@ impl Job {
}
async fn move_msg(&mut self, context: &Context, imap: &mut Imap) -> Status {
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
if let Err(err) = imap.ensure_configured_folders(context, true).await {
warn!(context, "could not configure folders: {:?}", err);
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
let dest_folder = context.get_config(Config::ConfiguredMvboxFolder).await;
if let Some(dest_folder) = dest_folder {
@@ -538,6 +538,11 @@ impl Job {
/// records pointing to the same message on the server, the job
/// also removes the message on the server.
async fn delete_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
if !msg.rfc724_mid.is_empty() {
@@ -608,6 +613,11 @@ impl Job {
}
async fn empty_server(&mut self, context: &Context, imap: &mut Imap) -> Status {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
if let Some(mvbox_folder) = &context.get_config(Config::ConfiguredMvboxFolder).await {
imap.empty_folder(context, &mvbox_folder).await;
@@ -620,6 +630,11 @@ impl Job {
}
async fn markseen_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
let folder = msg.server_folder.as_ref().unwrap();

View File

@@ -351,16 +351,47 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
};
format!("{}{}", re, chat.name)
} else {
let raw = message::get_summarytext_by_raw(
self.msg.viewtype,
self.msg.text.as_ref(),
&self.msg.param,
32,
self.context,
)
.await;
let raw_subject = raw.lines().next().unwrap_or_default();
format!("Chat: {}", raw_subject)
match chat.param.get(Param::LastSubject) {
Some(last_subject) => {
let subject_start = if last_subject.starts_with("Chat:") {
0
} else {
// "Antw:" is the longest abbreviation in
// https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages,
// so look at the first _5_ characters:
match last_subject.chars().take(5).position(|c| c == ':') {
Some(prefix_end) => prefix_end + 1,
None => 0,
}
};
format!(
"Re: {}",
last_subject
.chars()
.skip(subject_start)
.collect::<String>()
.trim()
)
}
None => {
let self_name = match self.context.get_config(Config::Displayname).await
{
Some(name) => name,
None => self
.context
.get_config(Config::Addr)
.await
.unwrap_or_default(),
};
self.context
.stock_string_repl_str(
StockMessage::SubjectForNewContact,
self_name,
)
.await
}
}
}
}
Loaded::MDN { .. } => self
@@ -1234,4 +1265,167 @@ mod tests {
assert!(needs_encoding(" "));
assert!(needs_encoding("foo bar"));
}
use crate::test_utils::{dummy_context, TestContext};
async fn configured_offline_context() -> TestContext {
let t = dummy_context().await;
t.ctx
.set_config(Config::Addr, Some("alice@example.org"))
.await
.unwrap();
t.ctx
.set_config(Config::ConfiguredAddr, Some("alice@example.org"))
.await
.unwrap();
t.ctx
.set_config(Config::Configured, Some("1"))
.await
.unwrap();
t
}
#[async_std::test]
async fn test_subject() {
// 1.: Receive a mail from an MUA or Delta Chat
assert_eq!(
msg_to_subject_str(
b"From: Bob <bob@example.org>\n\
To: alice@example.org\n\
Subject: Antw: Chat: hello\n\
Message-ID: <2222@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
)
.await,
"Re: Chat: hello"
);
assert_eq!(
msg_to_subject_str(
b"From: Bob <bob@example.org>\n\
To: alice@example.org\n\
Subject: Infos: 42\n\
Message-ID: <2222@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
)
.await,
"Re: Infos: 42"
);
// 2. Receive a message from Delta Chat when we did not send any messages before
assert_eq!(
msg_to_subject_str(
b"From: Charlie <charlie@example.org>\n\
To: alice@example.org\n\
Subject: Chat: hello\n\
Chat-Version: 1.0\n\
Message-ID: <2223@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
)
.await,
"Re: Chat: hello"
);
// 3. Send the first message to a new contact
let t = configured_offline_context().await;
assert_eq!(first_subject_str(t).await, "Message from alice@example.org");
let t = configured_offline_context().await;
t.ctx
.set_config(Config::Displayname, Some("Alice"))
.await
.unwrap();
assert_eq!(first_subject_str(t).await, "Message from Alice");
// 4. Receive messages with unicode characters and make sure that we do not panic (we do not care about the result)
msg_to_subject_str(
"From: Charlie <charlie@example.org>\n\
To: alice@example.org\n\
Subject: äääää\n\
Chat-Version: 1.0\n\
Message-ID: <2893@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
.as_bytes(),
)
.await;
msg_to_subject_str(
"From: Charlie <charlie@example.org>\n\
To: alice@example.org\n\
Subject: aäääää\n\
Chat-Version: 1.0\n\
Message-ID: <2893@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
.as_bytes(),
)
.await;
}
async fn first_subject_str(t: TestContext) -> String {
let contact_id =
Contact::add_or_lookup(&t.ctx, "Dave", "dave@example.org", Origin::ManuallyCreated)
.await
.unwrap()
.0;
let chat_id = chat::create_by_contact_id(&t.ctx, contact_id)
.await
.unwrap();
let mut new_msg = Message::new(Viewtype::Text);
new_msg.set_text(Some("Hi".to_string()));
new_msg.chat_id = chat_id;
chat::prepare_msg(&t.ctx, chat_id, &mut new_msg)
.await
.unwrap();
let mf = MimeFactory::from_msg(&t.ctx, &new_msg, false)
.await
.unwrap();
mf.subject_str().await
}
async fn msg_to_subject_str(imf_raw: &[u8]) -> String {
use crate::chatlist::Chatlist;
use crate::dc_receive_imf::dc_receive_imf;
let t = configured_offline_context().await;
t.ctx
.set_config(Config::ShowEmails, Some("2"))
.await
.unwrap();
dc_receive_imf(&t.ctx, imf_raw, "INBOX", 1, false)
.await
.unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
let chat_id = chat::create_by_msg_id(&t.ctx, chats.get_msg_id(0).unwrap())
.await
.unwrap();
let mut new_msg = Message::new(Viewtype::Text);
new_msg.set_text(Some("Hi".to_string()));
new_msg.chat_id = chat_id;
chat::prepare_msg(&t.ctx, chat_id, &mut new_msg)
.await
.unwrap();
let mf = MimeFactory::from_msg(&t.ctx, &new_msg, false)
.await
.unwrap();
mf.subject_str().await
}
}

View File

@@ -527,6 +527,7 @@ impl MimeMessage {
part.typ = Viewtype::Text;
part.msg_raw = Some(txt.clone());
part.msg = txt;
part.param.set(Param::Error, "Decryption failed");
self.parts.push(part);

View File

@@ -103,6 +103,9 @@ pub enum Param {
/// For Chats
Selftalk = b'K',
/// For Chats: So that on sending a new message we can sent the subject to "Re: <last subject>"
LastSubject = b't',
/// For Chats
Devicetalk = b'D',

View File

@@ -61,17 +61,12 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
let ctx1 = ctx.clone();
let fut = async move {
let ctx = ctx1;
if let Err(err) = connection.connect_configured(&ctx).await {
error!(ctx, "{}", err);
return;
}
started.send(()).await;
let ctx = ctx1;
// track number of continously executed jobs
let mut jobs_loaded: i32 = 0;
let mut info: InterruptInfo = Default::default();
let mut jobs_loaded = 0;
let mut info = InterruptInfo::default();
loop {
match job::load_next(&ctx, Thread::Imap, &info).await {
Some(job) if jobs_loaded <= 20 => {
@@ -106,6 +101,11 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
async fn fetch(ctx: &Context, connection: &mut Imap) {
match ctx.get_config(Config::ConfiguredInboxFolder).await {
Some(watch_folder) => {
if let Err(err) = connection.connect_configured(&ctx).await {
error!(ctx, "{}", err);
return;
}
// fetch
if let Err(err) = connection.fetch(&ctx, &watch_folder).await {
connection.trigger_reconnect();
@@ -122,6 +122,12 @@ async fn fetch(ctx: &Context, connection: &mut Imap) {
async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> InterruptInfo {
match ctx.get_config(folder).await {
Some(watch_folder) => {
// connect and fake idle if unable to connect
if let Err(err) = connection.connect_configured(&ctx).await {
error!(ctx, "imap connection failed: {}", err);
return connection.fake_idle(&ctx, None).await;
}
// fetch
if let Err(err) = connection.fetch(&ctx, &watch_folder).await {
connection.trigger_reconnect();
@@ -167,13 +173,8 @@ async fn simple_imap_loop(
let ctx1 = ctx.clone();
let fut = async move {
let ctx = ctx1;
if let Err(err) = connection.connect_configured(&ctx).await {
error!(ctx, "{}", err);
return;
}
started.send(()).await;
let ctx = ctx1;
loop {
fetch_idle(&ctx, &mut connection, folder).await;

View File

@@ -179,6 +179,9 @@ pub enum StockMessage {
#[strum(props(fallback = "Unknown Sender for this chat. See 'info' for more details."))]
UnknownSenderForChat = 72,
#[strum(props(fallback = "Message from %1$s"))]
SubjectForNewContact = 73,
}
/*