Compare commits

..

2 Commits

Author SHA1 Message Date
holger krekel
a7835ae2d0 some real tests 2021-05-12 19:07:47 +02:00
holger krekel
9f988f68a1 starting on provider tests, two basic tests pass for me on a three provider file 2021-05-12 19:07:47 +02:00
54 changed files with 741 additions and 1384 deletions

View File

@@ -27,10 +27,7 @@ jobs:
- checkout
# the following commands on success produces
# workspace/{wheelhouse,py-docs} as artefact directories
- run:
# building aarch64 packages under qemu is very slow
no_output_timeout: 60m
command: bash scripts/remote_python_packaging.sh
- run: bash scripts/remote_python_packaging.sh
- persist_to_workspace:
root: workspace
paths:
@@ -54,24 +51,18 @@ workflows:
jobs:
- remote_python_packaging:
filters:
tags:
only: /.+/
branches:
ignore: /.*/
- build_doxygen:
filters:
tags:
only: /.+/
branches:
ignore: /.*/
only: master
- upload_docs_wheels:
requires:
- remote_python_packaging
- build_doxygen
filters:
tags:
only: /.+/
branches:
ignore: /.*/
only: master
- build_doxygen:
filters:
branches:
only: master

View File

@@ -65,16 +65,26 @@ jobs:
build_and_test:
name: Build and test
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
include:
- os: ubuntu-latest
rust: 1.50.0
python: 3.6
- os: windows-latest
rust: 1.50.0
python: false # Python bindings compilation on Windows is not supported.
runs-on: ${{ matrix.os }}
# macOS disabled due to random failures related to caching
#os: [ubuntu-latest, windows-latest, macOS-latest]
os: [ubuntu-latest, windows-latest]
rust: [1.50.0]
experimental: [false]
# include:
# - os: ubuntu-latest
# rust: nightly
# experimental: true
# - os: windows-latest
# rust: nightly
# experimental: true
# - os: macOS-latest
# rust: nightly
# experimental: true
steps:
- uses: actions/checkout@master
@@ -115,29 +125,3 @@ jobs:
with:
command: test
args: --all
- name: install python
if: ${{ matrix.python }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: install tox
if: ${{ matrix.python }}
run: pip install tox
- name: build C library
if: ${{ matrix.python }}
uses: actions-rs/cargo@v1
with:
command: build
args: -p deltachat_ffi
- name: run python tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e lint,doc,py3

18
.github/workflows/remote_tests.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Remote tests
on: [push]
jobs:
remote_tests_python:
name: Remote Python tests
runs-on: ubuntu-latest
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
steps:
- uses: actions/checkout@v2
- run: mkdir -m 700 -p ~/.ssh
- run: touch ~/.ssh/id_ed25519
- run: chmod 600 ~/.ssh/id_ed25519
- run: 'echo "$SSH_KEY" | base64 -d > ~/.ssh/id_ed25519'
shell: bash
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
- run: scripts/remote_tests_python.sh "deltachat-core/python/${{ github.ref }}/${{ github.run_number }}"

View File

@@ -1,30 +1,5 @@
# Changelog
## 1.56.0
- fix downscaling images #2469
- fix outgoing messages popping up in selfchat #2456
- securejoin: display error reason if there is any #2470
- do not allow deleting contacts with ongoing chats #2458
- fix: ignore drafts folder when scanning #2454
- fix: scan folders also when inbox is not watched #2446
- more robust In-Reply-To parsing #2182
- update dependencies #2441 #2438 #2439 #2440 #2447 #2448 #2449 #2452 #2453 #2460 #2464 #2466
- update provider-database #2471
- refactorings #2459 #2457
- improve tests and ci #2445 #2450 #2451
## 1.55.0
- fix panic when receiving some HTML messages #2434

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(deltachat LANGUAGES C)
project(deltachat)
find_program(CARGO cargo)

177
Cargo.lock generated
View File

@@ -138,9 +138,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.41"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
[[package]]
name = "arrayvec"
@@ -374,9 +374,9 @@ dependencies = [
[[package]]
name = "async-std-resolver"
version = "0.20.3"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed4e2c3da14d8ad45acb1e3191db7a918e9505b6f155b218e70a7c9a1a48c638"
checksum = "4d613d619c2886fc0f4b5a777eceab405b23de82d73f0fc61ae402fdb9bc6fb2"
dependencies = [
"async-std",
"async-trait",
@@ -748,17 +748,6 @@ dependencies = [
"cc",
]
[[package]]
name = "clipboard-win"
version = "4.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4ea1881992efc993e4dc50a324cdbd03216e41bdc8385720ff47efc9bd2ca8"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@@ -826,12 +815,9 @@ checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpufeatures"
version = "0.1.1"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec1028182c380cc45a2e2c5ec841134f2dfd0f8f5f0a5bcd68004f81b5efdf4"
dependencies = [
"libc",
]
checksum = "5cd5a7748210e7ec1a9696610b1015e6e31fbf58f77a160801f124bd1c36592a"
[[package]]
name = "cpuid-bool"
@@ -865,7 +851,7 @@ dependencies = [
"clap",
"criterion-plot",
"csv",
"itertools 0.10.1",
"itertools 0.10.0",
"lazy_static",
"num-traits",
"oorandom",
@@ -1108,7 +1094,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.56.0"
version = "1.55.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1136,7 +1122,7 @@ dependencies = [
"hex",
"image",
"indexmap",
"itertools 0.10.1",
"itertools 0.10.0",
"kamadak-exif",
"lettre_email",
"libc",
@@ -1187,7 +1173,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.56.0"
version = "1.55.0"
dependencies = [
"anyhow",
"async-std",
@@ -1466,16 +1452,6 @@ dependencies = [
"termcolor",
]
[[package]]
name = "error-code"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "escaper"
version = "0.1.1"
@@ -1521,16 +1497,6 @@ dependencies = [
"instant",
]
[[package]]
name = "fd-lock"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0010f02effd88c702318c5dde0463206be67495d0b4d906ba7c0a8f166cc7f06"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "filetime"
version = "0.2.14"
@@ -1587,10 +1553,20 @@ dependencies = [
]
[[package]]
name = "futures"
version = "0.3.15"
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "futures"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253"
dependencies = [
"futures-channel",
"futures-core",
@@ -1603,9 +1579,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
dependencies = [
"futures-core",
"futures-sink",
@@ -1613,15 +1589,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
[[package]]
name = "futures-executor"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79"
checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
dependencies = [
"futures-core",
"futures-task",
@@ -1630,15 +1606,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
[[package]]
name = "futures-lite"
version = "1.12.0"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb"
dependencies = [
"fastrand",
"futures-core",
@@ -1651,11 +1627,10 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121"
checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b"
dependencies = [
"autocfg 1.0.1",
"proc-macro-hack",
"proc-macro2",
"quote",
@@ -1664,23 +1639,22 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282"
checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
[[package]]
name = "futures-task"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
[[package]]
name = "futures-util"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
dependencies = [
"autocfg 1.0.1",
"futures-channel",
"futures-core",
"futures-io",
@@ -2028,9 +2002,9 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.10.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
dependencies = [
"either",
]
@@ -2128,9 +2102,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.97"
version = "0.2.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]]
name = "libm"
@@ -2449,9 +2423,9 @@ checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
[[package]]
name = "once_cell"
version = "1.8.0"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "oorandom"
@@ -3180,15 +3154,14 @@ dependencies = [
[[package]]
name = "rustyline"
version = "8.2.0"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd4eaf7a7738f76c98e4f0395253ae853be3eb018f7b0bb57fe1b6c17e31874"
checksum = "b9e1b597fcd1eeb1d6b25b493538e5aa19629eb08932184b85fef931ba87e893"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"clipboard-win",
"dirs-next",
"fd-lock",
"fs2",
"libc",
"log",
"memchr",
@@ -3298,9 +3271,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.126"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
@@ -3317,9 +3290,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.126"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2",
"quote",
@@ -3363,9 +3336,9 @@ dependencies = [
[[package]]
name = "sha-1"
version = "0.9.6"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
checksum = "b659df5fc3ce22274daac600ffb845300bd2125bcfaec047823075afdab81c00"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
@@ -3382,9 +3355,9 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.9.5"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
checksum = "d8f6b75b17576b792bef0db1bcc4b8b8bcdf9506744cf34b974195487af6cff2"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
@@ -3569,12 +3542,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "str-buf"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]]
name = "stream-cipher"
version = "0.7.1"
@@ -3593,15 +3560,15 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "strum"
version = "0.21.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
[[package]]
name = "strum_macros"
version = "0.21.1"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
dependencies = [
"heck",
"proc-macro2",
@@ -3636,9 +3603,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.73"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2",
"quote",
@@ -3691,18 +3658,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.25"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.25"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote",
@@ -3804,9 +3771,9 @@ dependencies = [
[[package]]
name = "trust-dns-proto"
version = "0.20.3"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0d7f5db438199a6e2609debe3f69f808d074e0a2888ee0bccb45fe234d03f4"
checksum = "952a078337565ba39007de99b151770f41039253a31846f0a3d5cd5a4ac8eedf"
dependencies = [
"async-trait",
"cfg-if 1.0.0",
@@ -3828,9 +3795,9 @@ dependencies = [
[[package]]
name = "trust-dns-resolver"
version = "0.20.3"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ad17b608a64bd0735e67bde16b0636f8aa8591f831a25d18443ed00a699770"
checksum = "da9c97f7d103e0f94dbe384a57908833505ae5870126492f166821b7cf685589"
dependencies = [
"cfg-if 1.0.0",
"futures-util",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.56.0"
version = "1.55.0"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL-2.0"
@@ -15,11 +15,11 @@ lto = true
deltachat_derive = { path = "./deltachat_derive" }
ansi_term = { version = "0.12.1", optional = true }
anyhow = "1.0.41"
anyhow = "1.0.40"
async-imap = "0.5.0"
async-native-tls = { version = "0.3.3" }
async-smtp = { git = "https://github.com/async-email/async-smtp", rev="2275fd8d13e39b2c58d6605c786ff06ff9e05708" }
async-std-resolver = "0.20.3"
async-std-resolver = "0.20.2"
async-std = { version = "~1.9.0", features = ["unstable"] }
async-tar = "0.3.0"
async-trait = "0.1.50"
@@ -33,21 +33,21 @@ dirs = { version = "3.0.2", optional=true }
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
escaper = "0.1.1"
futures = "0.3.15"
futures = "0.3.14"
hex = "0.4.0"
image = { version = "0.23.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
indexmap = "1.3.0"
itertools = "0.10.1"
itertools = "0.10.0"
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2.97"
libc = "0.2.51"
log = {version = "0.4.8", optional = true }
mailparse = "0.13.4"
native-tls = "0.2.3"
num_cpus = "1.13.0"
num-derive = "0.3.0"
num-traits = "0.2.6"
once_cell = "1.8.0"
once_cell = "1.4.1"
percent-encoding = "2.0"
pgp = { version = "0.7.0", default-features = false }
pretty_env_logger = { version = "0.4.0", optional = true }
@@ -58,18 +58,18 @@ rand = "0.7.0"
regex = "1.4.6"
rusqlite = "0.25"
rust-hsluv = "0.1.4"
rustyline = { version = "8.2.0", optional = true }
rustyline = { version = "8.0.0", optional = true }
sanitize-filename = "0.3.0"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
sha-1 = "0.9.6"
sha2 = "0.9.5"
sha-1 = "0.9.5"
sha2 = "0.9.4"
smallvec = "1.0.0"
stop-token = "0.2.0"
strum = "0.21.0"
strum_macros = "0.21.1"
strum = "0.20.0"
strum_macros = "0.20.1"
surf = { version = "2.0.0-alpha.4", default-features = false, features = ["h1-client"] }
thiserror = "1.0.25"
thiserror = "1.0.14"
toml = "0.5.6"
url = "2.2.2"
uuid = { version = "0.8", features = ["serde", "v4"] }
@@ -78,7 +78,7 @@ uuid = { version = "0.8", features = ["serde", "v4"] }
ansi_term = "0.12.0"
async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
criterion = "0.3"
futures-lite = "1.12.0"
futures-lite = "1.7.0"
log = "0.4.11"
pretty_assertions = "0.7.2"
pretty_env_logger = "0.4.0"

View File

@@ -3,6 +3,7 @@
> Deltachat-core written in Rust
[![Rust CI](https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml/badge.svg)](https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml)
[![Remote tests](https://github.com/deltachat/deltachat-core-rust/actions/workflows/remote_tests.yml/badge.svg)](https://github.com/deltachat/deltachat-core-rust/actions/workflows/remote_tests.yml)
[![CircleCI](https://circleci.com/gh/deltachat/deltachat-core-rust.svg?style=shield)](https://circleci.com/gh/deltachat/deltachat-core-rust/)
## Installing Rust and Cargo

View File

@@ -17,7 +17,7 @@ async fn address_book_benchmark(n: u32, read_count: u32) {
.collect::<Vec<String>>()
.join("");
Contact::add_address_book(&context, &book).await.unwrap();
Contact::add_address_book(&context, book).await.unwrap();
let query: Option<&str> = None;
for _ in 0..read_count {

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.56.0"
version = "1.55.0"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
@@ -21,8 +21,8 @@ human-panic = "1.0.1"
num-traits = "0.2.6"
serde_json = "1.0"
async-std = "1.9.0"
anyhow = "1.0.41"
thiserror = "1.0.25"
anyhow = "1.0.40"
thiserror = "1.0.14"
rand = "0.7.3"
[features]

View File

@@ -1737,13 +1737,9 @@ pub unsafe extern "C" fn dc_block_contact(
let ctx = &*context;
block_on(async move {
if block == 0 {
Contact::unblock(&ctx, contact_id)
.await
.ok_or_log_msg(&ctx, "Can't unblock contact");
Contact::unblock(&ctx, contact_id).await;
} else {
Contact::block(&ctx, contact_id)
.await
.ok_or_log_msg(&ctx, "Can't block contact");
Contact::block(&ctx, contact_id).await;
}
});
}

View File

@@ -9,5 +9,5 @@ license = "MPL-2.0"
proc-macro = true
[dependencies]
syn = "1.0.73"
syn = "1.0.72"
quote = "1.0.2"

View File

@@ -372,7 +372,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
getlocations [<contact-id>]\n\
send <text>\n\
sendimage <file> [<text>]\n\
sendsticker <file> [<text>]\n\
sendfile <file> [<text>]\n\
sendhtml <file for html-part> [<text for plain-part>]\n\
videochat\n\
@@ -866,14 +865,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(sel_chat.is_some(), "No chat selected.");
chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), "".into()).await?;
}
"sendimage" | "sendsticker" | "sendfile" => {
"sendimage" | "sendfile" => {
ensure!(sel_chat.is_some(), "No chat selected.");
ensure!(!arg1.is_empty(), "No file given.");
let mut msg = Message::new(if arg0 == "sendimage" {
Viewtype::Image
} else if arg0 == "sendsticker" {
Viewtype::Sticker
} else {
Viewtype::File
});
@@ -1137,12 +1134,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"block" => {
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id = arg1.parse()?;
Contact::block(&context, contact_id).await?;
Contact::block(&context, contact_id).await;
}
"unblock" => {
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id = arg1.parse()?;
Contact::unblock(&context, contact_id).await?;
Contact::unblock(&context, contact_id).await;
}
"listblocked" => {
let contacts = Contact::get_all_blocked(&context).await?;

View File

@@ -251,16 +251,7 @@ class DirectImap:
return res
def append(self, folder, msg):
"""Upload a message to *folder*.
Trailing whitespace or a linebreak at the beginning will be removed automatically.
"""
if msg.startswith("\n"):
msg = msg[1:]
msg = '\n'.join([s.lstrip() for s in msg.splitlines()])
self.conn.append(folder, msg)
def get_uid_by_message_id(self, message_id):
msgs = self.conn.search(['HEADER', 'MESSAGE-ID', message_id])
if len(msgs) == 0:
raise Exception("Did not find message " + message_id + ", maybe you forgot to select the correct folder?")
return msgs[0]

View File

@@ -18,6 +18,8 @@ from .capi import lib
from .events import FFIEventLogger, FFIEventTracker
from _pytest._code import Source
from deltachat import direct_imap
from deltachat.account import parseaddr
import deltachat
@@ -36,6 +38,9 @@ def pytest_addoption(parser):
"--strict-tls", action="store_true",
help="Never accept invalid TLS certificates for test accounts",
)
parser.addoption(
"--provider-file", "-P", default=None,
help="file which contains config settings for real-world providers")
def pytest_configure(config):
@@ -125,17 +130,30 @@ def pytest_report_header(config, startdir):
return summary
def parse_accountfile(fn):
if fn is None:
return []
for line in open(fn):
if line.strip() and not line.strip().startswith('#'):
config_dict = {}
for part in line.split():
name, value = part.split("=")
config_dict[name] = value
yield config_dict
def pytest_generate_tests(metafunc):
if "real_provider_config" in metafunc.fixturenames:
account_configs = list(parse_accountfile(metafunc.config.getoption("--provider-file")))
ids = [parseaddr(cfg["addr"])[1] for cfg in account_configs]
metafunc.parametrize("real_provider_config", account_configs, ids=ids)
class SessionLiveConfigFromFile:
def __init__(self, fn):
self.fn = fn
self.configlist = []
for line in open(fn):
if line.strip() and not line.strip().startswith('#'):
d = {}
for part in line.split():
name, value = part.split("=")
d[name] = value
self.configlist.append(d)
self.configlist = list(parse_accountfile(fn))
def get(self, index):
return self.configlist[index]
@@ -232,6 +250,15 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
acc.disable_logging()
deltachat.unregister_global_plugin(direct_imap)
def make_account_from_real_config(self, provider_config):
configdict = provider_config
addr = parseaddr(configdict["addr"])[1]
domain = addr.split("@")[1]
if "e2ee_enabled" not in configdict:
configdict["e2ee_enabled"] = "1"
tmpdb = tmpdir.join(domain)
return self.make_account(tmpdb.strpath, logid=domain)
def make_account(self, path, logid, quiet=False):
ac = Account(path, logging=self._logging)
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
@@ -361,6 +388,11 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
accounts = self._accounts[:]
started_accounts = []
for acc in accounts:
if hasattr(acc, "_configtracker"):
acc._configtracker.wait_finish()
acc._evtracker.consume_events()
acc.get_device_chat().mark_noticed()
del acc._configtracker
if acc not in started_accounts:
self.wait_configure(acc)
acc.set_config("bcc_self", "0")
@@ -369,6 +401,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
started_accounts.append(acc)
print("{}: {} account was started".format(
acc.get_config("displayname"), acc.get_config("addr")))
for acc in started_accounts:
acc._evtracker.wait_all_initial_fetches()

View File

@@ -1,17 +1,15 @@
import os
import platform
import subprocess
import sys
import subprocess
if __name__ == "__main__":
assert len(sys.argv) == 2
workspacedir = sys.argv[1]
arch = platform.machine()
for relpath in os.listdir(workspacedir):
if relpath.startswith("deltachat"):
p = os.path.join(workspacedir, relpath)
subprocess.check_call(
["auditwheel", "repair", p, "-w", workspacedir,
"--plat", "manylinux2014_" + arch])
"--plat", "manylinux2014_x86_64"])

View File

@@ -0,0 +1,72 @@
import pytest
@pytest.fixture
def acprovider(acfactory, real_provider_config):
ac = acfactory.make_account_from_real_config(real_provider_config)
ac.update_config(real_provider_config)
ac._configtracker = ac.configure()
return ac
@pytest.fixture
def actest(acfactory):
return acfactory.get_online_configuring_account()
def test_configure_success(acfactory, acprovider, lp):
lp.sec("waiting for successful configuration of provider account")
acfactory.wait_configure_and_start_io()
assert acprovider.is_configured()
for name in ("inbox", "mvbox", "sentbox"):
folder = acprovider.get_config("configured_" + name + "_folder")
if not folder:
lp.sec("found no {} folder".format(name))
continue
lp.sec("removing provider account IMAP folder {}".format(folder))
acprovider.direct_imap.select_folder(folder)
acprovider.direct_imap.delete("1:*")
def test_basic_send_receive(acprovider, actest, acfactory, lp):
acfactory.wait_configure_and_start_io()
lp.sec("sending message from test account to provider account")
chat = actest.create_chat(acprovider)
chat.send_text("hello")
lp.sec("receiving message with the provider account")
msg = acprovider._evtracker.wait_next_messages_changed()
assert msg.chat.is_deaddrop() and not msg.is_encrypted()
lp.sec("sending message back from provider to test account")
back_chat = acprovider.create_chat(actest)
back_chat.send_text("world")
lp.sec("waiting with test account for provider mail")
msg = actest._evtracker.wait_next_incoming_message()
assert msg.text == "world"
assert msg.is_encrypted()
def test_group_messages(acprovider, actest, acfactory, lp):
acfactory.wait_configure_and_start_io()
lp.sec("sending message from test account to provider account")
chat = actest.create_chat(acprovider)
chat.send_text("hello")
lp.sec("receiving message with the provider account")
msg = acprovider._evtracker.wait_next_messages_changed()
assert msg.chat.is_deaddrop() and not msg.is_encrypted()
lp.sec("sending message back from provider to test account")
back_chat = acprovider.create_chat(actest)
back_chat.send_text("world")
lp.sec("waiting with test account for provider mail")
msg = actest._evtracker.wait_next_incoming_message()
assert msg.text == "world"
assert msg.is_encrypted()

View File

@@ -983,7 +983,7 @@ class TestOnlineAccount:
chat2 = msg2.chat
assert msg2 in chat2.get_messages()
assert chat2.is_deaddrop()
assert chat2.count_fresh_messages() == 1
assert chat2.count_fresh_messages() == 0
assert msg2.time_received >= msg1.time_sent
lp.sec("create new chat with contact and verify it's proper")
@@ -1033,33 +1033,6 @@ class TestOnlineAccount:
except queue.Empty:
pass # mark_seen_messages() has generated events before it returns
def test_moved_markseen(self, acfactory, lp):
"""Test that message already moved to DeltaChat folder is marked as seen."""
ac1 = acfactory.get_online_configuring_account(mvbox=True, config={"inbox_watch": "0"})
ac2 = acfactory.get_online_configuring_account()
acfactory.wait_configure_and_start_io([ac1, ac2])
ac1.set_config("bcc_self", "1")
ac1.direct_imap.idle_start()
ac1.create_chat(ac2).send_text("Hello!")
ac1.direct_imap.idle_check(terminate=True)
ac1.stop_io()
# Emulate moving of the message to DeltaChat folder by Sieve rule.
# mailcow server contains this rule by default.
ac1.direct_imap.conn.move(["*"], "DeltaChat")
ac1.direct_imap.select_folder("DeltaChat")
ac1.direct_imap.idle_start()
ac1.start_io()
ac1.direct_imap.idle_wait_for_seen()
ac1.direct_imap.idle_done()
fetch = list(ac1.direct_imap.conn.fetch("*", b'FLAGS').values())
flags = fetch[-1][b'FLAGS']
is_seen = b'\\Seen' in flags
assert is_seen
def test_message_override_sender_name(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = acfactory.get_accepted_chat(ac1, ac2)
@@ -1098,10 +1071,10 @@ class TestOnlineAccount:
# We had so many problems with markseen, if in doubt, rather create another test, it can't harm.
ac1 = acfactory.get_online_configuring_account(move=mvbox_move, mvbox=mvbox_move)
ac2 = acfactory.get_online_configuring_account(move=mvbox_move, mvbox=mvbox_move)
acfactory.wait_configure_and_start_io()
# Do not send BCC to self, we only want to test MDN on ac1.
ac1.set_config("bcc_self", "0")
acfactory.get_accepted_chat(ac1, ac2).send_text("hi")
msg = ac2._evtracker.wait_next_incoming_message()
folder = "mvbox" if mvbox_move else "inbox"
ac1.direct_imap.select_config_folder(folder)
@@ -1109,9 +1082,6 @@ class TestOnlineAccount:
ac1.direct_imap.idle_start()
ac2.direct_imap.idle_start()
acfactory.get_accepted_chat(ac1, ac2).send_text("hi")
msg = ac2._evtracker.wait_next_incoming_message()
ac2.mark_seen_messages([msg])
ac1.direct_imap.idle_wait_for_seen() # Check that the mdn is marked as seen
@@ -1319,11 +1289,9 @@ class TestOnlineAccount:
assert not device_chat.can_send()
assert device_chat.get_draft() is None
def test_dont_show_emails_in_draft_folder(self, acfactory, lp):
def test_dont_show_emails_in_draft_folder(self, acfactory):
"""Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them.
So: If it's outgoing AND there is no Received header AND it's not in the sentbox, then ignore the email.
If the draft email is sent out later (i.e. moved to "Sent"), it must be shown."""
So: If it's outgoing AND there is no Received header AND it's not in the sentbox, then ignore the email."""
ac1 = acfactory.get_online_configuring_account()
ac1.set_config("show_emails", "2")
ac1.create_contact("alice@example.com").create_chat()
@@ -1344,7 +1312,7 @@ class TestOnlineAccount:
Message-ID: <aepiors@example.org>
Content-Type: text/plain; charset=utf-8
message in Drafts that is moved to Sent later
message in Drafts
""".format(ac1.get_config("configured_addr")))
ac1.direct_imap.append("Sent", """
From: ac1 <{}>
@@ -1357,7 +1325,6 @@ class TestOnlineAccount:
""".format(ac1.get_config("configured_addr")))
ac1.set_config("scan_all_folders_debounce_secs", "0")
lp.sec("All prepared, now let DC find the message")
ac1.start_io()
msg = ac1._evtracker.wait_next_messages_changed()
@@ -1368,18 +1335,6 @@ class TestOnlineAccount:
assert msg.text == "subj message in Sent"
assert len(msg.chat.get_messages()) == 1
ac1.stop_io()
lp.sec("'Send out' the draft, i.e. move it to the Sent folder, and wait for DC to display it this time")
ac1.direct_imap.select_folder("Drafts")
uid = ac1.direct_imap.get_uid_by_message_id("aepiors@example.org")
ac1.direct_imap.conn.move(uid, "Sent")
ac1.start_io()
msg2 = ac1._evtracker.wait_next_messages_changed()
assert msg2.text == "subj message in Drafts that is moved to Sent later"
assert len(msg.chat.get_messages()) == 2
def test_prefer_encrypt(self, acfactory, lp):
"""Test quorum rule for encryption preference in 1:1 and group chat."""
ac1, ac2, ac3 = acfactory.get_many_online_accounts(3)
@@ -2441,31 +2396,26 @@ class TestOnlineAccount:
assert received_reply.quoted_text == "hello"
assert received_reply.quote.id == out_msg.id
@pytest.mark.parametrize("folder,move,expected_destination,inbox_watch,", [
("xyz", False, "xyz", "1"), # Test that emails are recognized in a random folder but not moved
("xyz", True, "DeltaChat", "1"), # ...emails are found in a random folder and moved to DeltaChat
("Spam", False, "INBOX", "1"), # ...emails are moved from the spam folder to the Inbox
("INBOX", False, "INBOX", "0"), # ...emails are found in the `Inbox` folder even if `inbox_watch` is "0"
@pytest.mark.parametrize("folder,move,expected_destination,", [
("xyz", False, "xyz"), # Test that emails are recognized in a random folder but not moved
("xyz", True, "DeltaChat"), # ...emails are found in a random folder and moved to DeltaChat
("Spam", False, "INBOX") # ...emails are moved from the spam folder to the Inbox
])
# Testrun.org does not support the CREATE-SPECIAL-USE capability, which means that we can't create a folder with
# the "\Junk" flag (see https://tools.ietf.org/html/rfc6154). So, we can't test spam folder detection by flag.
def test_scan_folders(self, acfactory, lp, folder, move, expected_destination, inbox_watch):
def test_scan_folders(self, acfactory, lp, folder, move, expected_destination):
"""Delta Chat periodically scans all folders for new messages to make sure we don't miss any."""
variant = folder + "-" + str(move) + "-" + expected_destination
lp.sec("Testing variant " + variant)
ac1 = acfactory.get_online_configuring_account(move=move)
ac2 = acfactory.get_online_configuring_account()
ac1.set_config("inbox_watch", inbox_watch)
acfactory.wait_configure(ac1)
ac1.direct_imap.create_folder(folder)
acfactory.wait_configure_and_start_io()
# Wait until each folder was selected once and we are IDLEing:
if inbox_watch == "1":
ac1._evtracker.get_info_contains("INBOX: Idle entering wait-on-remote state")
else:
ac1._evtracker.get_info_contains("IMAP-fake-IDLE: no folder, waiting for interrupt")
ac1._evtracker.get_info_contains("INBOX: Idle entering wait-on-remote state")
ac1.stop_io()
# Send a message to ac1 and move it to the mvbox:
@@ -2482,10 +2432,7 @@ class TestOnlineAccount:
assert msg.text == "hello"
# Wait until the message was moved (if at all) and we are IDLEing again:
if inbox_watch == "1":
ac1._evtracker.get_info_contains("INBOX: Idle entering wait-on-remote state")
else:
ac1._evtracker.get_info_contains("IMAP-fake-IDLE: no folder, waiting for interrupt")
ac1._evtracker.get_info_contains("INBOX: Idle entering wait-on-remote state")
ac1.direct_imap.select_folder(expected_destination)
assert len(ac1.direct_imap.get_all_messages()) == 1
if folder != expected_destination:

View File

@@ -48,7 +48,3 @@ to avoid the relatively large download::
cd scripts # where all CI things are
docker build -t deltachat/coredeps docker-coredeps
docker build -t deltachat/doxygen docker-doxygen
Additionally, you can install qemu and build arm64 docker image:
apt-get install qemu binfmt-support qemu-user-static
docker build -t deltachat/coredeps-arm64 docker-coredeps-arm64

View File

@@ -1,21 +0,0 @@
FROM quay.io/pypa/manylinux2014_aarch64
# Configure ld.so/ldconfig and pkg-config
RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
# Install a recent Perl, needed to install the openssl crate
ADD deps/build_perl.sh /builder/build_perl.sh
RUN rm /usr/bin/perl
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1

View File

@@ -1,21 +0,0 @@
#!/bin/bash
set -e -x
OPENSSL_VERSION=1.1.1a
OPENSSL_SHA256=fc20130f8b7cbd2fb918b2f14e2f429e109c31ddd0fb38fc5d71d9ffed3f9f41
curl -O https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz
echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c -
tar xzf openssl-${OPENSSL_VERSION}.tar.gz
cd openssl-${OPENSSL_VERSION}
./config shared no-ssl2 no-ssl3 -fPIC --prefix=/usr/local
sed -i "s/^SHLIB_MAJOR=.*/SHLIB_MAJOR=200/" Makefile && \
sed -i "s/^SHLIB_MINOR=.*/SHLIB_MINOR=0.0/" Makefile && \
sed -i "s/^SHLIB_VERSION_NUMBER=.*/SHLIB_VERSION_NUMBER=200.0.0/" Makefile
make depend
make
make install_sw install_ssldirs
ldconfig -v | grep ssl

View File

@@ -1,12 +0,0 @@
#!/bin/bash
PERL_VERSION=5.30.0
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar -xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
./Configure -de
make
make install

View File

@@ -1,14 +0,0 @@
#!/bin/bash
set -x -e
# we use the python3.6 environment as the base environment
/opt/python/cp36-cp36m/bin/pip install tox devpi-client auditwheel
pushd /usr/bin
ln -s /opt/_internal/cpython-3.6.*/bin/tox
ln -s /opt/_internal/cpython-3.6.*/bin/devpi
ln -s /opt/_internal/cpython-3.6.*/bin/auditwheel
popd

View File

@@ -1,16 +0,0 @@
#!/bin/bash
set -e -x
# Install Rust
#
# Path from https://forge.rust-lang.org/infra/other-installation-methods.html
#
# Avoid using rustup here as it depends on reading /proc/self/exe and
# has problems running under QEMU.
curl "https://static.rust-lang.org/dist/rust-1.52.1-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
cd "rust-1.52.1-$(uname -m)-unknown-linux-gnu"
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
rustc --version
cd ..
rm -fr "rust-1.52.1-$(uname -m)-unknown-linux-gnu"

View File

@@ -5,7 +5,7 @@ RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
# Install a recent Perl, needed to install the openssl crate
# Install a recent Perl, needed to install the openssl crate
ADD deps/build_perl.sh /builder/build_perl.sh
RUN rm /usr/bin/perl
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1

View File

@@ -2,13 +2,13 @@
set -x -e
# we use the python3.6 environment as the base environment
/opt/python/cp36-cp36m/bin/pip install tox devpi-client auditwheel
# we use the python3.5 environment as the base environment
/opt/python/cp35-cp35m/bin/pip install tox devpi-client auditwheel
pushd /usr/bin
ln -s /opt/_internal/cpython-3.6.*/bin/tox
ln -s /opt/_internal/cpython-3.6.*/bin/devpi
ln -s /opt/_internal/cpython-3.6.*/bin/auditwheel
ln -s /opt/_internal/cpython-3.5.*/bin/tox
ln -s /opt/_internal/cpython-3.5.*/bin/devpi
ln -s /opt/_internal/cpython-3.5.*/bin/auditwheel
popd

View File

@@ -3,9 +3,9 @@
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain "1.50.0-$(uname -m)-unknown-linux-gnu" -y
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain 1.50.0-x86_64-unknown-linux-gnu -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf "/root/.rustup/toolchains/1.50.0-$(uname -m)-unknown-linux-gnu/share"
rm -rf /root/.rustup/toolchains/1.50.0-x86_64-unknown-linux-gnu/share

View File

@@ -15,7 +15,6 @@ git ls-files >.rsynclist
# we seem to need .git for setuptools_scm versioning
find .git >>.rsynclist
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR-arm64"
set +x
@@ -27,41 +26,24 @@ set +x
# everything is terminated/cleaned up and we have no orphaned
# useless still-running docker-containers consuming resources.
for arch in "" "-arm64"; do
ssh $SSHTARGET bash -c "cat >${BUILDDIR}${arch}/exec_docker_run" <<_HERE
ssh $SSHTARGET bash -c "cat >$BUILDDIR/exec_docker_run" <<_HERE
set +x -e
shopt -s huponexit
cd ${BUILDDIR}${arch}
cd $BUILDDIR
export DCC_NEW_TMP_EMAIL=$DCC_NEW_TMP_EMAIL
set -x
# run everything else inside docker
docker run -e DCC_NEW_TMP_EMAIL \
--rm -it -v \$(pwd):/mnt -w /mnt \
deltachat/coredeps${arch} scripts/run_all.sh
deltachat/coredeps scripts/run_all.sh
_HERE
done
echo "--- Running $CIRCLE_JOB remotely"
echo "--- Building aarch64 wheels"
ssh -o ServerAliveInterval=30 -t $SSHTARGET bash "$BUILDDIR-arm64/exec_docker_run"
echo "--- Building x86_64 wheels"
ssh -o ServerAliveInterval=30 -t $SSHTARGET bash "$BUILDDIR/exec_docker_run"
ssh -t $SSHTARGET bash "$BUILDDIR/exec_docker_run"
mkdir -p workspace
# Wheels
for arch in "" "-arm64"; do
rsync -avz "$SSHTARGET:$BUILDDIR${arch}/python/.docker-tox/wheelhouse/*manylinux201*" workspace/wheelhouse/
done
# Source packages
rsync -avz "$SSHTARGET:$BUILDDIR${arch}/python/.docker-tox/dist/*" workspace/wheelhouse/
# Documentation
rsync -avz "$SSHTARGET:$BUILDDIR/python/.docker-tox/wheelhouse/*manylinux201*" workspace/wheelhouse/
rsync -avz "$SSHTARGET:$BUILDDIR/python/.docker-tox/dist/*" workspace/wheelhouse/
rsync -avz "$SSHTARGET:$BUILDDIR/python/doc/_build/" workspace/py-docs

View File

@@ -19,17 +19,14 @@ export DCC_RS_TARGET=release
# Configure access to a base python and to several python interpreters
# needed by tox below.
export PATH=$PATH:/opt/python/cp36-cp36m/bin
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd /bin
rm -f python3.6
rm -f python3.5
ln -s /opt/python/cp35-cp35m/bin/python3.5
ln -s /opt/python/cp36-cp36m/bin/python3.6
rm -f python3.7
ln -s /opt/python/cp37-cp37m/bin/python3.7
rm -f python3.8
ln -s /opt/python/cp38-cp38/bin/python3.8
rm -f python3.9
ln -s /opt/python/cp39-cp39/bin/python3.9
popd
pushd python
@@ -43,7 +40,7 @@ mkdir -p $TOXWORKDIR
# Note that the independent remote_tests_python step does all kinds of
# live-testing already.
unset DCC_NEW_TMP_EMAIL
tox --workdir "$TOXWORKDIR" -e py37,py38,py39,auditwheels
tox --workdir "$TOXWORKDIR" -e py35,py36,py37,py38,auditwheels
popd

63
spec.md
View File

@@ -1,6 +1,6 @@
# chat-mail specification
Version: 0.33.0
Version: 0.32.0
Status: In-progress
Format: [Semantic Line Breaks](https://sembr.org/)
@@ -301,9 +301,9 @@ to add a `Chat-Group-Avatar` only on image changes.
A user MAY have a profile-image that MAY be distributed to their contacts.
To change or set the profile-image,
the messenger MUST add the header `Chat-User-Avatar: base64:IMAGEDATA`.
To bypass limits of headers, it is recommended not to use the outer header
and to limit the size to 20k.
the messenger MUST attach an image file to a message
and MUST add the header `Chat-User-Avatar`
with the value set to the image name.
To remove the profile-image,
the messenger MUST add the header `Chat-User-Avatar: 0`.
@@ -320,14 +320,19 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
From: sender@domain
To: rcpt@domain
Chat-Version: 1.0
Chat-User-Avatar: photo.jpg
Subject: Chat: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="
--==break==
Content-Type: text/plain
Chat-User-Avatar: base64:AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQY ...
Hello, I've changed my profile image.
--==break==
Content-Type: image/jpeg
Content-Disposition: attachment; filename="photo.jpg"
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
--==break==--
The image format SHOULD be image/jpeg or image/png.
@@ -337,11 +342,6 @@ in the same message.
To save data, it is RECOMMENDED to add a `Chat-User-Avatar` header
only on image changes.
In older specs, the profile-image was sent as an attachment
and `Chat-User-Avatar:` specified its name.
However, it turned out that these attachements are kind of unuexpected to users,
therefore the profile-image go to the header now.
# Locations
@@ -401,41 +401,9 @@ it is fine if the location is detected on forwarding etc.
</kml>
# Stickers
# Miscellaneous
Stickers are send as normal images
with the additional header `Chat-Content: sticker`.
It is discouraged to send stickers together with user generated text,
however, stickers can be used as a reply to a message
and also the footer should be set as usual.
From: alice@example.org
To: bob@example.com
Chat-Version: 1.0
Chat-Content: sticker
Message-ID: Mr.12345uvwxyZ.0005@example.org
Subject: Message from Alice
Content-Type: multipart/mixed; boundary="==break=="
--==break==
Content-Type: text/plain
--
Hi there! I am using this new messenger!
--==break==
Content-Type: image/png
Content-Disposition: attachment; filename="sticker.png"
R0lGODlhpAGkAfe9AP+zd2eQkZhrI//z9v++PMb///+scrdDT3BtbtrZ2f/LQSsREcdIVf9 ...
--==break==--
Typical sticker formats are `image/png`, `image/gif` and `image/webp`.
Animated stickers are supported
by just using an image format that supports animation.
# Voice messages
Messengers SHOULD use the header `In-Reply-To` as usual.
Messengers SHOULD add a `Chat-Voice-message: 1` header
if an attached audio file is a voice message.
@@ -449,11 +417,6 @@ This allows the receiver to show the time without knowing the file format.
Chat-Voice-Message: 1
Chat-Duration: 10000
# Miscellaneous
Messengers SHOULD use the header `In-Reply-To` as usual.
Messengers MAY send and receive Message Disposition Notifications
(MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098),
[RFC 3503](https://tools.ietf.org/html/rfc3503))
@@ -474,4 +437,4 @@ as the sending time of the message as indicated by its Date header,
or the time of first receipt if that date is in the future or unavailable.
Copyright © 2017-2021 Delta Chat contributors.
Copyright © 2017-2020 Delta Chat contributors.

View File

@@ -452,7 +452,7 @@ impl<'a> BlobObject<'a> {
img.write_to(encoded, image::ImageFormat::Jpeg)?;
Ok(())
}
fn encoded_img_exceeds_bytes(
fn encode_img_exceeds_bytes(
context: &Context,
img: &DynamicImage,
max_bytes: Option<usize>,
@@ -477,7 +477,7 @@ impl<'a> BlobObject<'a> {
let exceeds_width = img.width() > img_wh || img.height() > img_wh;
let do_scale =
exceeds_width || encoded_img_exceeds_bytes(context, &img, max_bytes, &mut encoded)?;
exceeds_width || encode_img_exceeds_bytes(context, &img, max_bytes, &mut encoded)?;
let do_rotate = matches!(orientation, Ok(90) | Ok(180) | Ok(270));
if do_scale || do_rotate {
@@ -500,7 +500,7 @@ impl<'a> BlobObject<'a> {
loop {
let new_img = img.thumbnail(img_wh, img_wh);
if encoded_img_exceeds_bytes(context, &new_img, max_bytes, &mut encoded)? {
if encode_img_exceeds_bytes(context, &new_img, max_bytes, &mut encoded)? {
if img_wh < 20 {
return Err(format_err!(
"Failed to scale image to below {}B",
@@ -511,10 +511,6 @@ impl<'a> BlobObject<'a> {
img_wh = img_wh * 2 / 3;
} else {
if encoded.is_empty() {
encode_img(&new_img, &mut encoded)?;
}
info!(
context,
"Final scaled-down image size: {}B ({}px)",
@@ -621,8 +617,7 @@ mod tests {
use super::*;
use crate::{message::Message, test_utils::TestContext};
use image::Pixel;
use crate::test_utils::TestContext;
#[async_std::test]
async fn test_create() {
@@ -920,148 +915,4 @@ mod tests {
let avatar_cfg = t.get_config(Config::Selfavatar).await.unwrap();
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
}
#[async_std::test]
async fn test_recode_image() {
let bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
// BALANCED_IMAGE_SIZE > 1000, the original image size, so the image is not scaled down:
send_image_check_mediaquality(Some("0"), bytes, 1000, 1000, 0, 1000, 1000)
.await
.unwrap();
send_image_check_mediaquality(
Some("1"),
bytes,
1000,
1000,
0,
WORSE_IMAGE_SIZE,
WORSE_IMAGE_SIZE,
)
.await
.unwrap();
// The "-rotated" files are rotated by 270 degrees using the Exif metadata
let bytes = include_bytes!("../test-data/image/rectangle2000x1800-rotated.jpg");
let img_rotated = send_image_check_mediaquality(
Some("0"),
bytes,
2000,
1800,
270,
BALANCED_IMAGE_SIZE * 1800 / 2000,
BALANCED_IMAGE_SIZE,
)
.await
.unwrap();
assert_correct_rotation(&img_rotated);
let mut bytes = vec![];
img_rotated
.write_to(&mut bytes, image::ImageFormat::Jpeg)
.unwrap();
let img_rotated = send_image_check_mediaquality(
Some("0"),
&bytes,
BALANCED_IMAGE_SIZE * 1800 / 2000,
BALANCED_IMAGE_SIZE,
0,
BALANCED_IMAGE_SIZE * 1800 / 2000,
BALANCED_IMAGE_SIZE,
)
.await
.unwrap();
assert_correct_rotation(&img_rotated);
let img_rotated = send_image_check_mediaquality(
Some("1"),
&bytes,
BALANCED_IMAGE_SIZE * 1800 / 2000,
BALANCED_IMAGE_SIZE,
0,
WORSE_IMAGE_SIZE * 1800 / 2000,
WORSE_IMAGE_SIZE,
)
.await
.unwrap();
assert_correct_rotation(&img_rotated);
let bytes = include_bytes!("../test-data/image/rectangle200x180-rotated.jpg");
let img_rotated = send_image_check_mediaquality(Some("0"), bytes, 200, 180, 270, 180, 200)
.await
.unwrap();
assert_correct_rotation(&img_rotated);
let bytes = include_bytes!("../test-data/image/rectangle200x180-rotated.jpg");
let img_rotated = send_image_check_mediaquality(Some("1"), bytes, 200, 180, 270, 180, 200)
.await
.unwrap();
assert_correct_rotation(&img_rotated);
}
fn assert_correct_rotation(img: &DynamicImage) {
// The test images are black in the bottom left corner after correctly applying
// the EXIF orientation
let [luma] = img.get_pixel(10, 10).to_luma().0;
assert_eq!(luma, 255);
let [luma] = img.get_pixel(img.width() - 10, 10).to_luma().0;
assert_eq!(luma, 255);
let [luma] = img
.get_pixel(img.width() - 10, img.height() - 10)
.to_luma()
.0;
assert_eq!(luma, 255);
let [luma] = img.get_pixel(10, img.height() - 10).to_luma().0;
assert_eq!(luma, 0);
}
async fn send_image_check_mediaquality(
media_quality_config: Option<&str>,
bytes: &[u8],
original_width: u32,
original_height: u32,
orientation: i32,
compressed_width: u32,
compressed_height: u32,
) -> anyhow::Result<DynamicImage> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
alice
.set_config(Config::MediaQuality, media_quality_config)
.await?;
let file = alice.get_blobdir().join("file.jpg");
File::create(&file).await?.write_all(bytes).await?;
let img = image::open(&file)?;
assert_eq!(img.width(), original_width);
assert_eq!(img.height(), original_height);
let blob = BlobObject::new_from_path(&alice, &file).await?;
assert_eq!(blob.get_exif_orientation(&alice).unwrap_or(0), orientation);
let mut msg = Message::new(Viewtype::Image);
msg.set_file(file.to_str().unwrap(), None);
let chat = alice.create_chat(&bob).await;
let sent = alice.send_msg(chat.id, &mut msg).await;
let alice_msg = alice.get_last_msg().await;
assert_eq!(alice_msg.get_width() as u32, compressed_width);
assert_eq!(alice_msg.get_height() as u32, compressed_height);
let img = image::open(alice_msg.get_file(&alice).unwrap())?;
assert_eq!(img.width() as u32, compressed_width);
assert_eq!(img.height() as u32, compressed_height);
bob.recv_msg(&sent).await;
let bob_msg = bob.get_last_msg().await;
assert_eq!(bob_msg.get_width() as u32, compressed_width);
assert_eq!(bob_msg.get_height() as u32, compressed_height);
let file = bob_msg.get_file(&bob).unwrap();
let blob = BlobObject::new_from_path(&bob, &file).await?;
assert_eq!(blob.get_exif_orientation(&bob).unwrap_or(0), 0);
let img = image::open(file)?;
assert_eq!(img.width() as u32, compressed_width);
assert_eq!(img.height() as u32, compressed_height);
Ok(img)
}
}

View File

@@ -574,26 +574,10 @@ impl ChatId {
/// Returns number of messages in a chat.
pub async fn get_msg_cnt(self, context: &Context) -> Result<usize> {
let count = if self.is_deaddrop() {
context
.sql
.count(
"SELECT COUNT(*)
FROM msgs
WHERE hidden=0
AND chat_id IN (SELECT id FROM chats WHERE blocked=?)",
paramsv![Blocked::Deaddrop],
)
.await?
} else {
context
.sql
.count(
"SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?",
paramsv![self],
)
.await?
};
let count = context
.sql
.count("SELECT COUNT(*) FROM msgs WHERE chat_id=?", paramsv![self])
.await?;
Ok(count as usize)
}
@@ -608,31 +592,17 @@ impl ChatId {
// the times are average, no matter if there are fresh messages or not -
// and have to be multiplied by the number of items shown at once on the chatlist,
// so savings up to 2 seconds are possible on older devices - newer ones will feel "snappier" :)
let count = if self.is_deaddrop() {
context
.sql
.count(
"SELECT COUNT(*)
FROM msgs
WHERE state=?
AND hidden=0
AND chat_id IN (SELECT id FROM chats WHERE blocked=?)",
paramsv![MessageState::InFresh, Blocked::Deaddrop],
)
.await?
} else {
context
.sql
.count(
"SELECT COUNT(*)
let count = context
.sql
.count(
"SELECT COUNT(*)
FROM msgs
WHERE state=?
WHERE state=10
AND hidden=0
AND chat_id=?;",
paramsv![MessageState::InFresh, self],
)
.await?
};
paramsv![self],
)
.await?;
Ok(count as usize)
}
@@ -3096,8 +3066,6 @@ mod tests {
use crate::contact::Contact;
use crate::dc_receive_imf::dc_receive_imf;
use crate::test_utils::TestContext;
use async_std::fs::File;
use async_std::prelude::*;
#[async_std::test]
async fn test_chat_info() {
@@ -4035,8 +4003,6 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await?;
assert_eq!(chats.len(), 1);
assert_eq!(chats.get_chat_id(0), DC_CHAT_ID_DEADDROP);
assert_eq!(DC_CHAT_ID_DEADDROP.get_msg_cnt(&t).await?, 1);
assert_eq!(DC_CHAT_ID_DEADDROP.get_fresh_msg_cnt(&t).await?, 1);
let msgs = get_chat_msgs(&t, DC_CHAT_ID_DEADDROP, 0, None).await?;
assert_eq!(msgs.len(), 1);
let msg_id = match msgs.first().unwrap() {
@@ -4057,65 +4023,4 @@ mod tests {
Ok(())
}
async fn test_sticker(filename: &str, bytes: &[u8], w: i32, h: i32) -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
let alice_chat = alice.create_chat(&bob).await;
let bob_chat = bob.create_chat(&alice).await;
let file = alice.get_blobdir().join(filename);
File::create(&file).await?.write_all(bytes).await?;
let mut msg = Message::new(Viewtype::Sticker);
msg.set_file(file.to_str().unwrap(), None);
let sent_msg = alice.send_msg(alice_chat.id, &mut msg).await;
let mime = sent_msg.payload();
assert_eq!(mime.match_indices("Chat-Content: sticker").count(), 1);
bob.recv_msg(&sent_msg).await;
let msg = bob.get_last_msg().await;
assert_eq!(msg.chat_id, bob_chat.id);
assert_eq!(msg.get_viewtype(), Viewtype::Sticker);
assert_eq!(msg.get_filename(), Some(filename.to_string()));
assert_eq!(msg.get_width(), w);
assert_eq!(msg.get_height(), h);
assert!(msg.get_filebytes(&bob).await > 250);
Ok(())
}
#[async_std::test]
async fn test_sticker_png() -> Result<()> {
test_sticker(
"sticker.png",
include_bytes!("../test-data/image/avatar64x64.png"),
64,
64,
)
.await
}
#[async_std::test]
async fn test_sticker_jpeg() -> Result<()> {
test_sticker(
"sticker.jpg",
include_bytes!("../test-data/image/avatar1000x1000.jpg"),
1000,
1000,
)
.await
}
#[async_std::test]
async fn test_sticker_gif() -> Result<()> {
test_sticker(
"sticker.gif",
include_bytes!("../test-data/image/image100x50.gif"),
100,
50,
)
.await
}
}

View File

@@ -18,18 +18,7 @@ use crate::stock_str;
/// The available configuration keys.
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Display,
EnumString,
AsRefStr,
EnumIter,
EnumProperty,
PartialOrd,
Ord,
Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
)]
#[strum(serialize_all = "snake_case")]
pub enum Config {

View File

@@ -345,8 +345,10 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
progress!(ctx, 600);
// Configure IMAP
let (_s, r) = async_std::channel::bounded(1);
let mut imap = Imap::new(r);
let mut imap: Option<Imap> = None;
let mut imap_configured = false;
let imap_servers: Vec<&ServerParams> = servers
.iter()
.filter(|params| params.protocol == Protocol::Imap)
@@ -359,9 +361,18 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
param.imap.port = imap_server.port;
param.imap.security = imap_server.socket;
match try_imap_one_param(ctx, &param.imap, &param.addr, oauth2, provider_strict_tls).await {
Ok(configured_imap) => {
imap = Some(configured_imap);
match try_imap_one_param(
ctx,
&param.imap,
&param.addr,
oauth2,
provider_strict_tls,
&mut imap,
)
.await
{
Ok(_) => {
imap_configured = true;
break;
}
Err(e) => errors.push(e),
@@ -371,10 +382,9 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
600 + (800 - 600) * (1 + imap_server_index) / imap_servers_count
);
}
let mut imap = match imap {
Some(imap) => imap,
None => bail!(nicer_configuration_error(ctx, errors).await),
};
if !imap_configured {
bail!(nicer_configuration_error(ctx, errors).await);
}
progress!(ctx, 850);
@@ -510,38 +520,26 @@ async fn try_imap_one_param(
addr: &str,
oauth2: bool,
provider_strict_tls: bool,
) -> Result<Imap, ConfigurationError> {
imap: &mut Imap,
) -> Result<(), ConfigurationError> {
let inf = format!(
"imap: {}@{}:{} security={} certificate_checks={} oauth2={}",
param.user, param.server, param.port, param.security, param.certificate_checks, oauth2
);
info!(context, "Trying: {}", inf);
let (_s, r) = async_std::channel::bounded(1);
let mut imap = match Imap::new(param, addr, oauth2, provider_strict_tls, r).await {
Err(err) => {
info!(context, "failure: {}", err);
return Err(ConfigurationError {
config: inf,
msg: err.to_string(),
});
}
Ok(imap) => imap,
};
match imap.connect(context).await {
Err(err) => {
info!(context, "failure: {}", err);
return Err(ConfigurationError {
config: inf,
msg: err.to_string(),
});
}
Ok(()) => {
info!(context, "success: {}", inf);
return Ok(imap);
}
if let Err(err) = imap
.connect(context, param, addr, oauth2, provider_strict_tls)
.await
{
info!(context, "failure: {}", err);
Err(ConfigurationError {
config: inf,
msg: err.to_string(),
})
} else {
info!(context, "success: {}", inf);
Ok(())
}
}

View File

@@ -236,13 +236,13 @@ impl Contact {
}
/// Block the given contact.
pub async fn block(context: &Context, id: u32) -> Result<()> {
set_block_contact(context, id, true).await
pub async fn block(context: &Context, id: u32) {
set_block_contact(context, id, true).await;
}
/// Unblock the given contact.
pub async fn unblock(context: &Context, id: u32) -> Result<()> {
set_block_contact(context, id, false).await
pub async fn unblock(context: &Context, id: u32) {
set_block_contact(context, id, false).await;
}
/// Add a single contact as a result of an _explicit_ user action.
@@ -270,7 +270,7 @@ impl Contact {
}
}
if blocked {
Contact::unblock(context, contact_id).await?;
Contact::unblock(context, contact_id).await;
}
Ok(contact_id)
@@ -541,7 +541,7 @@ impl Contact {
{
row_id = u32::try_from(new_row_id)?;
sth_modified = Modifier::Created;
info!(context, "added contact id={} addr={}", row_id, &addr);
info!(context, "added contact id={} addr={} origin={:?}", row_id, &addr, &origin);
} else {
error!(context, "Cannot add contact.");
}
@@ -860,7 +860,7 @@ impl Contact {
"Can not delete special contact"
);
let count_chats = context
let count_contacts = context
.sql
.count(
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
@@ -868,7 +868,19 @@ impl Contact {
)
.await?;
if count_chats == 0 {
let count_msgs = if count_contacts > 0 {
context
.sql
.count(
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
paramsv![contact_id as i32, contact_id as i32],
)
.await?
} else {
0
};
if count_msgs == 0 {
match context
.sql
.execute(
@@ -890,9 +902,9 @@ impl Contact {
info!(
context,
"could not delete contact {}, there are {} chats with it", contact_id, count_chats
"could not delete contact {}, there are {} messages with it", contact_id, count_msgs
);
bail!("Could not delete contact with ongoing chats");
bail!("Could not delete contact with messages in it");
}
/// Get a single contact object. For a list, see eg. dc_get_contacts().
@@ -1162,58 +1174,56 @@ fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) {
}
}
async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) -> Result<()> {
ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
"Can't block special contact {}",
contact_id
);
async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return;
}
let contact = Contact::load_from_db(context, contact_id).await?;
if contact.blocked != new_blocking {
context
.sql
.execute(
"UPDATE contacts SET blocked=? WHERE id=?;",
paramsv![new_blocking as i32, contact_id as i32],
)
.await?;
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
// non-destructive blocking->unblocking.
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...)
if context
.sql
.execute(
r#"
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
if contact.blocked != new_blocking
&& context
.sql
.execute(
"UPDATE contacts SET blocked=? WHERE id=?;",
paramsv![new_blocking as i32, contact_id as i32],
)
.await
.is_ok()
{
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
// non-destructive blocking->unblocking.
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...)
if context
.sql
.execute(
r#"
UPDATE chats
SET blocked=?
WHERE type=? AND id IN (
SELECT chat_id FROM chats_contacts WHERE contact_id=?
);
"#,
paramsv![new_blocking, Chattype::Single, contact_id],
)
.await
.is_ok()
{
Contact::mark_noticed(context, contact_id).await;
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
}
paramsv![new_blocking, Chattype::Single, contact_id],
)
.await
.is_ok()
{
Contact::mark_noticed(context, contact_id).await;
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
}
// also unblock mailinglist
// if the contact is a mailinglist address explicitly created to allow unblocking
if !new_blocking && contact.origin == Origin::MailinglistAddress {
if let Ok((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await {
chat_id.set_blocked(context, Blocked::Not).await;
// also unblock mailinglist
// if the contact is a mailinglist address explicitly created to allow unblocking
if !new_blocking && contact.origin == Origin::MailinglistAddress {
if let Ok((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await
{
chat_id.set_blocked(context, Blocked::Not).await;
}
}
}
}
Ok(())
}
/// Set profile image for a contact.
@@ -1608,34 +1618,6 @@ mod tests {
assert!(!contact.is_blocked());
}
#[async_std::test]
async fn test_delete() -> Result<()> {
let alice = TestContext::new_alice().await;
assert!(Contact::delete(&alice, DC_CONTACT_ID_SELF).await.is_err());
// Create Bob contact
let (contact_id, _) =
Contact::add_or_lookup(&alice, "Bob", "bob@example.net", Origin::ManuallyCreated)
.await
.unwrap();
let chat = alice
.create_chat_with_contact("Bob", "bob@example.net")
.await;
// Can't delete a contact with ongoing chats.
assert!(Contact::delete(&alice, contact_id).await.is_err());
// Delete chat.
chat.get_id().delete(&alice).await?;
// Can delete contact now.
Contact::delete(&alice, contact_id).await?;
Ok(())
}
#[async_std::test]
async fn test_remote_authnames() {
let t = TestContext::new().await;

View File

@@ -161,9 +161,7 @@ impl Context {
{
let l = &mut *self.inner.scheduler.write().await;
if let Err(err) = l.start(self.clone()).await {
error!(self, "Failed to start IO: {}", err)
}
l.start(self.clone()).await;
}
}
@@ -540,16 +538,19 @@ impl Context {
pub async fn is_sentbox(&self, folder_name: &str) -> Result<bool> {
let sentbox = self.get_config(Config::ConfiguredSentboxFolder).await?;
Ok(sentbox.as_deref() == Some(folder_name))
}
pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
Ok(mvbox.as_deref() == Some(folder_name))
}
pub async fn is_spam_folder(&self, folder_name: &str) -> Result<bool> {
let spam = self.get_config(Config::ConfiguredSpamFolder).await?;
Ok(spam.as_deref() == Some(folder_name))
}

View File

@@ -685,26 +685,11 @@ async fn add_parts(
}
}
// If the message is outgoing AND there is no Received header AND it's not in the sentbox,
// then ignore the email.
//
// We only apply this heuristic to classical emails, as it is not reliable (some servers
// such as systemli.org in June 2021 remove their own Received headers on incoming mails)
// and we know Delta Chat never stores drafts on IMAP servers.
let is_draft = !context.is_sentbox(server_folder).await?
if !context.is_sentbox(server_folder).await?
&& mime_parser.get(HeaderDef::Received).is_none()
&& mime_parser.get(HeaderDef::ChatVersion).is_none();
// Mozilla Thunderbird does not set \Draft flag on "Templates", but sets
// X-Mozilla-Draft-Info header, which can be used to detect both drafts and templates
// created by Thunderbird.
//
// This check is not necessary now, but may become useful if the `Received:` header check
// is removed completely later.
let is_draft = is_draft || mime_parser.get(HeaderDef::XMozillaDraftInfo).is_some();
if is_draft {
{
// Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
// So: If it's outgoing AND there is no Received header AND it's not in the sentbox, then ignore the email.
info!(context, "Email is probably just a draft (TRASH)");
*chat_id = DC_CHAT_ID_TRASH;
allow_creation = false;
@@ -2757,14 +2742,11 @@ mod tests {
// Check that the ndn would be downloaded:
let headers = mailparse::parse_mail(raw_ndn).unwrap().headers;
assert!(crate::imap::prefetch_should_download(
&t,
&headers,
std::iter::empty(),
ShowEmails::Off
)
.await
.unwrap());
assert!(
crate::imap::prefetch_should_download(&t, &headers, ShowEmails::Off)
.await
.unwrap()
);
dc_receive_imf(&t, raw_ndn, "INBOX", 1, false)
.await
@@ -3033,9 +3015,7 @@ mod tests {
assert_eq!(msgs.len(), 0);
// Unblock contact and check if the next message arrives in real chat
Contact::unblock(&t, *blocked.first().unwrap())
.await
.unwrap();
Contact::unblock(&t, *blocked.first().unwrap()).await;
let blocked = Contact::get_all_blocked(&t).await.unwrap();
assert_eq!(blocked.len(), 0);
@@ -4006,33 +3986,4 @@ YEAAAAAA!.
.await,
);
}
#[async_std::test]
async fn test_dont_show_all_outgoing_msgs_in_self_chat() {
// Regression test for https://github.com/deltachat/deltachat-android/issues/1940:
// Some servers add a `Bcc: <Self>` header, which caused all outgoing messages to
// be shown in the self-chat.
let t = TestContext::new_alice().await;
dc_receive_imf(
&t,
b"Bcc: alice@example.com
Received: from [127.0.0.1]
Subject: s
Chat-Version: 1.0
Message-ID: <abcd@gmail.com>
To: <me@other.maildomain.com>
From: <alice@example.com>
Message content",
"Inbox",
1,
false,
)
.await
.unwrap();
let msg = t.get_last_msg().await;
assert_ne!(msg.chat_id, t.get_self_chat().await.id);
}
}

View File

@@ -25,12 +25,6 @@ pub enum HeaderDef {
/// we need to check that header as well.
XMicrosoftOriginalMessageId,
/// Thunderbird header used to store Draft information.
///
/// Thunderbird 78.11.0 does not set \Draft flag on messages saved as "Template", but sets this
/// header, so it can be used to ignore such messages.
XMozillaDraftInfo,
ListId,
References,
InReplyTo,

View File

@@ -8,12 +8,13 @@ use std::{cmp, cmp::max, collections::BTreeMap};
use anyhow::{bail, format_err, Context as _, Result};
use async_imap::{
error::Result as ImapResult,
types::{Fetch, Flag, Mailbox, Name, NameAttribute},
types::{Capability, Fetch, Flag, Mailbox, Name, NameAttribute},
};
use async_std::channel::Receiver;
use async_std::prelude::*;
use num_traits::FromPrimitive;
use crate::chat;
use crate::config::Config;
use crate::constants::{
Chattype, ShowEmails, Viewtype, DC_FETCH_EXISTING_MSGS_COUNT, DC_FOLDERS_CONFIGURED_VERSION,
@@ -35,7 +36,6 @@ use crate::param::Params;
use crate::provider::Socket;
use crate::scheduler::InterruptInfo;
use crate::stock_str;
use crate::{chat, constants::DC_CONTACT_ID_SELF};
mod client;
mod idle;
@@ -92,10 +92,6 @@ pub struct Imap {
interrupt: Option<stop_token::StopSource>,
should_reconnect: bool,
login_failed_once: bool,
/// True if CAPABILITY command was run successfully once and config.can_* contain correct
/// values.
capabilities_determined: bool,
}
#[derive(Debug)]
@@ -115,27 +111,14 @@ impl async_imap::Authenticator for OAuth2 {
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq)]
enum FolderMeaning {
Unknown,
Spam,
Sent,
Drafts,
SentObjects,
Other,
}
impl FolderMeaning {
fn to_config(self) -> Option<Config> {
match self {
FolderMeaning::Unknown => None,
FolderMeaning::Spam => Some(Config::ConfiguredSpamFolder),
FolderMeaning::Sent => Some(Config::ConfiguredSentboxFolder),
FolderMeaning::Drafts => None,
FolderMeaning::Other => None,
}
}
}
#[derive(Debug)]
struct ImapConfig {
pub addr: String,
@@ -145,7 +128,6 @@ struct ImapConfig {
pub selected_folder: Option<String>,
pub selected_mailbox: Option<Mailbox>,
pub selected_folder_needs_expunge: bool,
pub can_idle: bool,
/// True if the server has MOVE capability as defined in
@@ -153,93 +135,58 @@ struct ImapConfig {
pub can_move: bool,
}
impl Imap {
/// Creates new disconnected IMAP client using the specific login parameters.
///
/// `addr` is used to renew token if OAuth2 authentication is used.
pub async fn new(
lp: &ServerLoginParam,
addr: &str,
oauth2: bool,
provider_strict_tls: bool,
idle_interrupt: Receiver<InterruptInfo>,
) -> Result<Self> {
if lp.server.is_empty() || lp.user.is_empty() || lp.password.is_empty() {
bail!("Incomplete IMAP connection parameters");
}
let strict_tls = match lp.certificate_checks {
CertificateChecks::Automatic => provider_strict_tls,
CertificateChecks::Strict => true,
CertificateChecks::AcceptInvalidCertificates
| CertificateChecks::AcceptInvalidCertificates2 => false,
};
let config = ImapConfig {
addr: addr.to_string(),
lp: lp.clone(),
strict_tls,
oauth2,
impl Default for ImapConfig {
fn default() -> Self {
ImapConfig {
addr: "".into(),
lp: Default::default(),
strict_tls: false,
oauth2: false,
selected_folder: None,
selected_mailbox: None,
selected_folder_needs_expunge: false,
can_idle: false,
can_move: false,
};
}
}
}
let imap = Imap {
impl Imap {
pub fn new(idle_interrupt: Receiver<InterruptInfo>) -> Self {
Imap {
idle_interrupt,
config,
session: None,
connected: false,
interrupt: None,
should_reconnect: false,
login_failed_once: false,
capabilities_determined: false,
};
Ok(imap)
config: Default::default(),
session: Default::default(),
connected: Default::default(),
interrupt: Default::default(),
should_reconnect: Default::default(),
login_failed_once: Default::default(),
}
}
/// Creates new disconnected IMAP client using configured parameters.
pub async fn new_configured(
context: &Context,
idle_interrupt: Receiver<InterruptInfo>,
) -> Result<Self> {
if !context.is_configured().await? {
bail!("IMAP Connect without configured params");
}
pub fn is_connected(&self) -> bool {
self.connected
}
let param = LoginParam::from_database(context, "configured_").await?;
// the trailing underscore is correct
pub fn should_reconnect(&self) -> bool {
self.should_reconnect
}
let imap = Self::new(
&param.imap,
&param.addr,
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
param.provider.map_or(false, |provider| provider.strict_tls),
idle_interrupt,
)
.await?;
Ok(imap)
pub fn trigger_reconnect(&mut self) {
self.should_reconnect = true;
}
/// Connects or reconnects if needed.
///
/// It is safe to call this function if already connected, actions are performed only as needed.
///
/// Does not emit network errors, can be used to try various parameters during
/// autoconfiguration.
///
/// Calling this function is not enough to perform IMAP operations. Use [`Imap::prepare`]
/// instead if you are going to actually use connection rather than trying connection
/// parameters.
pub async fn connect(&mut self, context: &Context) -> Result<()> {
/// It is safe to call this function if already connected, actions
/// are performed only as needed.
async fn try_setup_handle(&mut self, context: &Context) -> Result<()> {
if self.config.lp.server.is_empty() {
bail!("IMAP operation attempted while it is torn down");
}
if self.should_reconnect() {
self.disconnect(context).await;
self.unsetup_handle(context).await;
self.should_reconnect = false;
} else if self.is_connected() {
return Ok(());
@@ -309,10 +256,6 @@ impl Imap {
self.connected = true;
self.session = Some(session);
self.login_failed_once = false;
emit_event!(
context,
EventType::ImapConnected(format!("IMAP-LOGIN as {}", self.config.lp.user))
);
Ok(())
}
@@ -349,49 +292,20 @@ impl Imap {
}
}
/// Determine server capabilities if not done yet.
async fn determine_capabilities(&mut self) -> Result<()> {
if self.capabilities_determined {
return Ok(());
}
match &mut self.session {
Some(ref mut session) => match session.capabilities().await {
Ok(caps) => {
self.config.can_idle = caps.has_str("IDLE");
self.config.can_move = caps.has_str("MOVE");
self.capabilities_determined = true;
Ok(())
}
Err(err) => {
bail!("CAPABILITY command error: {}", err);
}
},
None => {
bail!("Can't determine server capabilities because connection was not established")
}
}
}
/// Prepare for IMAP operation.
/// Connects or reconnects if not already connected.
///
/// Ensure that IMAP client is connected, folders are created and IMAP capabilities are
/// determined.
///
/// This function emits network error if it fails. It should not be used during configuration
/// to avoid showing failed attempt errors to the user.
pub async fn prepare(&mut self, context: &Context) -> Result<()> {
let res = self.connect(context).await;
/// This function emits network error if it fails. It should not
/// be used during configuration to avoid showing failed attempt
/// errors to the user.
async fn setup_handle(&mut self, context: &Context) -> Result<()> {
let res = self.try_setup_handle(context).await;
if let Err(ref err) = res {
emit_event!(context, EventType::ErrorNetwork(err.to_string()));
}
self.ensure_configured_folders(context, true).await?;
self.determine_capabilities().await?;
res
}
async fn disconnect(&mut self, context: &Context) {
async fn unsetup_handle(&mut self, context: &Context) {
// Close folder if messages should be expunged
if let Err(err) = self.close_folder(context).await {
warn!(context, "failed to close folder: {:?}", err);
@@ -404,21 +318,139 @@ impl Imap {
}
}
self.connected = false;
self.capabilities_determined = false;
self.config.selected_folder = None;
self.config.selected_mailbox = None;
}
pub fn is_connected(&self) -> bool {
self.connected
async fn free_connect_params(&mut self) {
let mut cfg = &mut self.config;
cfg.addr = "".into();
cfg.lp = Default::default();
cfg.can_idle = false;
cfg.can_move = false;
}
pub fn should_reconnect(&self) -> bool {
self.should_reconnect
/// Connects to IMAP account using already-configured parameters.
///
/// Emits network error if connection fails.
pub async fn connect_configured(&mut self, context: &Context) -> Result<()> {
if self.is_connected() && !self.should_reconnect() {
return Ok(());
}
if !context.is_configured().await? {
bail!("IMAP Connect without configured params");
}
let param = LoginParam::from_database(context, "configured_").await?;
// the trailing underscore is correct
if let Err(err) = self
.connect(
context,
&param.imap,
&param.addr,
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
param.provider.map_or(false, |provider| provider.strict_tls),
)
.await
{
bail!("IMAP Connection Failed with params {}: {}", param, err);
} else {
self.ensure_configured_folders(context, true).await
}
}
pub fn trigger_reconnect(&mut self) {
self.should_reconnect = true;
/// Tries connecting to imap account using the specific login parameters.
///
/// `addr` is used to renew token if OAuth2 authentication is used.
///
/// Does not emit network errors, can be used to try various
/// parameters during autoconfiguration.
pub async fn connect(
&mut self,
context: &Context,
lp: &ServerLoginParam,
addr: &str,
oauth2: bool,
provider_strict_tls: bool,
) -> Result<()> {
if lp.server.is_empty() || lp.user.is_empty() || lp.password.is_empty() {
bail!("Incomplete IMAP connection parameters");
}
{
let mut config = &mut self.config;
config.addr = addr.to_string();
config.lp = lp.clone();
config.strict_tls = match lp.certificate_checks {
CertificateChecks::Automatic => provider_strict_tls,
CertificateChecks::Strict => true,
CertificateChecks::AcceptInvalidCertificates
| CertificateChecks::AcceptInvalidCertificates2 => false,
};
config.oauth2 = oauth2;
}
if let Err(err) = self.try_setup_handle(context).await {
warn!(context, "try_setup_handle: {}", err);
self.free_connect_params().await;
return Err(err);
}
let teardown = match &mut self.session {
Some(ref mut session) => match session.capabilities().await {
Ok(caps) => {
if !context.sql.is_open().await {
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.user,);
true
} else {
let can_idle = caps.has_str("IDLE");
let can_move = caps.has_str("MOVE");
let caps_list = caps.iter().fold(String::new(), |s, c| {
if let Capability::Atom(x) = c {
s + &format!(" {}", x)
} else {
s + &format!(" {:?}", c)
}
});
self.config.can_idle = can_idle;
self.config.can_move = can_move;
self.connected = true;
emit_event!(
context,
EventType::ImapConnected(format!(
"IMAP-LOGIN as {}, capabilities: {}",
lp.user, caps_list,
))
);
false
}
}
Err(err) => {
info!(context, "CAPABILITY command error: {}", err);
true
}
},
None => true,
};
if teardown {
self.disconnect(context).await;
warn!(
context,
"IMAP disconnected immediately after connecting due to error"
);
}
Ok(())
}
pub async fn disconnect(&mut self, context: &Context) {
self.unsetup_handle(context).await;
self.free_connect_params().await;
}
pub async fn fetch(&mut self, context: &Context, watch_folder: &str) -> Result<()> {
@@ -426,7 +458,7 @@ impl Imap {
// probably shutdown
bail!("IMAP operation attempted while it is torn down");
}
self.prepare(context).await?;
self.setup_handle(context).await?;
while self
.fetch_new_messages(context, &watch_folder, false)
@@ -664,7 +696,6 @@ impl Imap {
current_uid,
&headers,
&msg_id,
msg.flags(),
folder,
show_emails,
)
@@ -943,6 +974,10 @@ impl Imap {
(last_uid, read_errors)
}
pub async fn can_move(&self) -> bool {
self.config.can_move
}
pub async fn mv(
&mut self,
context: &Context,
@@ -967,7 +1002,7 @@ impl Imap {
let set = format!("{}", uid);
let display_folder_id = format!("{}/{}", folder, uid);
if self.config.can_move {
if self.can_move().await {
if let Some(ref mut session) = &mut self.session {
match session.uid_mv(&set, &dest_folder).await {
Ok(_) => {
@@ -1093,7 +1128,7 @@ impl Imap {
// 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.prepare(context).await {
if let Err(err) = self.connect_configured(context).await {
warn!(context, "prepare_imap_op failed: {}", err);
return Some(ImapActionResult::RetryLater);
}
@@ -1265,8 +1300,9 @@ impl Imap {
let mut delimiter = ".".to_string();
let mut delimiter_is_default = true;
let mut sentbox_folder = None;
let mut spam_folder = None;
let mut mvbox_folder = None;
let mut folder_configs = BTreeMap::new();
let mut fallback_folder = get_fallback_folder(&delimiter);
while let Some(folder) = folders.next().await {
@@ -1285,26 +1321,31 @@ impl Imap {
let folder_meaning = get_folder_meaning(&folder);
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
if folder.name() == "DeltaChat" {
// Always takes precedence
// Always takes precendent
mvbox_folder = Some(folder.name().to_string());
} else if folder.name() == fallback_folder {
// only set if none has been already set
// only set iff none has been already set
if mvbox_folder.is_none() {
mvbox_folder = Some(folder.name().to_string());
}
} else if let Some(config) = folder_meaning.to_config() {
// Always takes precedence
folder_configs.insert(config, folder.name().to_string());
} else if let Some(config) = folder_name_meaning.to_config() {
// only set if none has been already set
folder_configs
.entry(config)
.or_insert_with(|| folder.name().to_string());
} else if folder_meaning == FolderMeaning::SentObjects {
// Always takes precedent
sentbox_folder = Some(folder.name().to_string());
} else if folder_meaning == FolderMeaning::Spam {
spam_folder = Some(folder.name().to_string());
} else if folder_name_meaning == FolderMeaning::SentObjects {
// only set iff none has been already set
if sentbox_folder.is_none() {
sentbox_folder = Some(folder.name().to_string());
}
} else if folder_name_meaning == FolderMeaning::Spam && spam_folder.is_none() {
spam_folder = Some(folder.name().to_string());
}
}
drop(folders);
info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
info!(context, "sentbox folder is {:?}", sentbox_folder);
if mvbox_folder.is_none() && create_mvbox {
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
@@ -1352,8 +1393,15 @@ impl Imap {
.set_config(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
.await?;
}
for (config, name) in folder_configs {
context.set_config(config, Some(&name)).await?;
if let Some(ref sentbox_folder) = sentbox_folder {
context
.set_config(Config::ConfiguredSentboxFolder, Some(sentbox_folder))
.await?;
}
if let Some(ref spam_folder) = spam_folder {
context
.set_config(Config::ConfiguredSpamFolder, Some(spam_folder))
.await?;
}
context
.sql
@@ -1426,50 +1474,29 @@ fn get_folder_meaning_by_name(folder_name: &str) -> FolderMeaning {
"迷惑メール",
"스팸",
];
const DRAFT_NAMES: &[&str] = &[
"Drafts",
"Kladder",
"Entw?rfe",
"Borradores",
"Brouillons",
"Bozze",
"Concepten",
"Wersje robocze",
"Rascunhos",
"Entwürfe",
"Koncepty",
"Kopie robocze",
"Taslaklar",
"Utkast",
"Πρόχειρα",
"Черновики",
"下書き",
"草稿",
"임시보관함",
];
let lower = folder_name.to_lowercase();
if SENT_NAMES.iter().any(|s| s.to_lowercase() == lower) {
FolderMeaning::Sent
FolderMeaning::SentObjects
} else if SPAM_NAMES.iter().any(|s| s.to_lowercase() == lower) {
FolderMeaning::Spam
} else if DRAFT_NAMES.iter().any(|s| s.to_lowercase() == lower) {
FolderMeaning::Drafts
} else {
FolderMeaning::Unknown
}
}
fn get_folder_meaning(folder_name: &Name) -> FolderMeaning {
let special_names = vec!["\\Trash", "\\Drafts"];
for attr in folder_name.attributes() {
if let NameAttribute::Custom(ref label) = attr {
match label.as_ref() {
"\\Trash" => return FolderMeaning::Other,
"\\Sent" => return FolderMeaning::Sent,
"\\Spam" | "\\Junk" => return FolderMeaning::Spam,
"\\Drafts" => return FolderMeaning::Drafts,
_ => {}
};
if special_names.iter().any(|s| *s == label) {
return FolderMeaning::Other;
} else if label == "\\Sent" {
return FolderMeaning::SentObjects;
} else if label == "\\Spam" || label == "\\Junk" {
return FolderMeaning::Spam;
}
}
}
FolderMeaning::Unknown
@@ -1587,7 +1614,6 @@ fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Result<String>
pub(crate) async fn prefetch_should_download(
context: &Context,
headers: &[mailparse::MailHeader<'_>],
mut flags: impl Iterator<Item = Flag<'_>>,
show_emails: ShowEmails,
) -> Result<bool> {
let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion).is_some();
@@ -1625,17 +1651,12 @@ pub(crate) async fn prefetch_should_download(
.get_header_value(HeaderDef::AutocryptSetupMessage)
.is_some();
let (from_id, blocked_contact, origin) =
let (_contact_id, blocked_contact, origin) =
from_field_to_contact_id(context, &mimeparser::get_from(headers), true).await?;
// prevent_rename=true as this might be a mailing list message and in this case it would be bad if we rename the contact.
// (prevent_rename is the last argument of from_field_to_contact_id())
if flags.any(|f| f == Flag::Draft) && from_id == DC_CONTACT_ID_SELF {
info!(context, "Ignoring draft message");
return Ok(false);
}
let accepted_contact = origin.is_known();
let show = is_autocrypt_setup_message
|| match show_emails {
ShowEmails::Off => is_chat_message || is_reply_to_chat_message,
@@ -1654,7 +1675,6 @@ async fn message_needs_processing(
current_uid: u32,
headers: &[mailparse::MailHeader<'_>],
msg_id: &str,
flags: impl Iterator<Item = Flag<'_>>,
folder: &str,
show_emails: ShowEmails,
) -> bool {
@@ -1678,7 +1698,7 @@ async fn message_needs_processing(
// we do not know the message-id
// or the message-id is missing (in this case, we create one in the further process)
// or some other error happened
let show = match prefetch_should_download(context, headers, flags, show_emails).await {
let show = match prefetch_should_download(context, headers, show_emails).await {
Ok(show) => show,
Err(err) => {
warn!(context, "prefetch_should_download error: {}", err);
@@ -1841,16 +1861,25 @@ mod tests {
use crate::test_utils::TestContext;
#[test]
fn test_get_folder_meaning_by_name() {
assert_eq!(get_folder_meaning_by_name("Gesendet"), FolderMeaning::Sent);
assert_eq!(get_folder_meaning_by_name("GESENDET"), FolderMeaning::Sent);
assert_eq!(get_folder_meaning_by_name("gesendet"), FolderMeaning::Sent);
assert_eq!(
get_folder_meaning_by_name("Gesendet"),
FolderMeaning::SentObjects
);
assert_eq!(
get_folder_meaning_by_name("GESENDET"),
FolderMeaning::SentObjects
);
assert_eq!(
get_folder_meaning_by_name("gesendet"),
FolderMeaning::SentObjects
);
assert_eq!(
get_folder_meaning_by_name("Messages envoyés"),
FolderMeaning::Sent
FolderMeaning::SentObjects
);
assert_eq!(
get_folder_meaning_by_name("mEsSaGes envoyÉs"),
FolderMeaning::Sent
FolderMeaning::SentObjects
);
assert_eq!(get_folder_meaning_by_name("xxx"), FolderMeaning::Unknown);
assert_eq!(get_folder_meaning_by_name("SPAM"), FolderMeaning::Spam);

View File

@@ -25,7 +25,7 @@ impl Imap {
if !self.can_idle() {
bail!("IMAP server does not have IDLE capability");
}
self.prepare(context).await?;
self.setup_handle(context).await?;
self.select_folder(context, watch_folder.as_deref()).await?;
@@ -158,7 +158,7 @@ impl Imap {
// try to connect with proper login params
// (setup_handle_if_needed might not know about them if we
// never successfully connected)
if let Err(err) = self.prepare(context).await {
if let Err(err) = self.connect_configured(context).await {
warn!(context, "fake_idle: could not connect: {}", err);
continue;
}

View File

@@ -1,13 +1,13 @@
use std::{collections::BTreeMap, time::Instant};
use std::time::Instant;
use anyhow::{Context as _, Result};
use crate::config::Config;
use crate::context::Context;
use crate::imap::Imap;
use crate::{config::Config, log::LogExt};
use crate::{context::Context, imap::FolderMeaning};
use async_std::prelude::*;
use super::{get_folder_meaning, get_folder_meaning_by_name};
use super::{get_folder_meaning, get_folder_meaning_by_name, FolderMeaning};
impl Imap {
pub async fn scan_folders(&mut self, context: &Context) -> Result<()> {
@@ -25,13 +25,14 @@ impl Imap {
}
info!(context, "Starting full folder scan");
self.prepare(context).await?;
self.setup_handle(context).await?;
let session = self.session.as_mut();
let session = session.context("scan_folders(): IMAP No Connection established")?;
let folders: Vec<_> = session.list(Some(""), Some("*")).await?.collect().await;
let watched_folders = get_watched_folders(context).await;
let mut folder_configs = BTreeMap::new();
let mut sentbox_folder = None;
let mut spam_folder = None;
for folder in folders {
let folder = match folder {
@@ -42,41 +43,38 @@ impl Imap {
}
};
let foldername = folder.name();
let folder_meaning = get_folder_meaning(&folder);
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
let folder_name_meaning = get_folder_meaning_by_name(foldername);
if let Some(config) = folder_meaning.to_config() {
// Always takes precedence
folder_configs.insert(config, folder.name().to_string());
} else if let Some(config) = folder_name_meaning.to_config() {
// only set if none has been already set
folder_configs
.entry(config)
.or_insert_with(|| folder.name().to_string());
if folder_meaning == FolderMeaning::SentObjects {
// Always takes precedent
sentbox_folder = Some(folder.name().to_string());
} else if folder_meaning == FolderMeaning::Spam {
spam_folder = Some(folder.name().to_string());
} else if folder_name_meaning == FolderMeaning::SentObjects {
// only set iff none has been already set
if sentbox_folder.is_none() {
sentbox_folder = Some(folder.name().to_string());
}
} else if folder_name_meaning == FolderMeaning::Spam && spam_folder.is_none() {
spam_folder = Some(folder.name().to_string());
}
let is_drafts = folder_meaning == FolderMeaning::Drafts
|| (folder_meaning == FolderMeaning::Unknown
&& folder_name_meaning == FolderMeaning::Drafts);
// Don't scan folders that are watched anyway
if !watched_folders.contains(&folder.name().to_string()) && !is_drafts {
self.fetch_new_messages(context, folder.name(), false)
.await
.ok_or_log_msg(context, "Can't fetch new msgs in scanned folder");
if !watched_folders.contains(&foldername.to_string()) {
if let Err(e) = self.fetch_new_messages(context, foldername, false).await {
warn!(context, "Can't fetch new msgs in scanned folder: {:#}", e);
}
}
}
// We iterate over both folder meanings to make sure that if e.g. the "Sent" folder was deleted,
// `ConfiguredSentboxFolder` is set to `None`:
for config in &[
Config::ConfiguredSentboxFolder,
Config::ConfiguredSpamFolder,
] {
context
.set_config(*config, folder_configs.get(config).map(|s| s.as_str()))
.await?;
}
context
.set_config(Config::ConfiguredSentboxFolder, sentbox_folder.as_deref())
.await?;
context
.set_config(Config::ConfiguredSpamFolder, spam_folder.as_deref())
.await?;
last_scan.replace(Instant::now());
Ok(())

View File

@@ -612,13 +612,8 @@ async fn import_backup_old(context: &Context, backup_to_import: &Path) -> Result
let mut all_files_extracted = true;
for (processed_files_cnt, file_id) in file_ids.into_iter().enumerate() {
if context.shall_stop_ongoing().await {
all_files_extracted = false;
break;
}
// Load a single blob into memory
match context
let (file_name, file_blob) = context
.sql
.query_row(
"SELECT file_name, file_content FROM backup_blobs WHERE id = ?",
@@ -629,17 +624,12 @@ async fn import_backup_old(context: &Context, backup_to_import: &Path) -> Result
Ok((file_name, file_blob))
},
)
.await
{
Ok((file_name, file_blob)) => {
let path_filename = context.get_blobdir().join(file_name);
dc_write_file(context, &path_filename, &file_blob).await?;
}
Err(err) => {
error!(context, "Can't import file {}: {}", file_id, err);
}
}
.await?;
if context.shall_stop_ongoing().await {
all_files_extracted = false;
break;
}
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 {
permille = 10
@@ -648,6 +638,12 @@ async fn import_backup_old(context: &Context, backup_to_import: &Path) -> Result
permille = 990
}
context.emit_event(EventType::ImexProgress(permille));
if file_blob.is_empty() {
continue;
}
let path_filename = context.get_blobdir().join(file_name);
dc_write_file(context, &path_filename, &file_blob).await?;
}
if all_files_extracted {

View File

@@ -532,7 +532,7 @@ impl Job {
}
async fn move_msg(&mut self, context: &Context, imap: &mut Imap) -> Status {
if let Err(err) = imap.prepare(context).await {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
@@ -594,7 +594,7 @@ 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.prepare(context).await {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
@@ -682,7 +682,7 @@ impl Job {
if job_try!(context.get_config_bool(Config::Bot).await) {
return Status::Finished(Ok(())); // Bots don't want those messages
}
if let Err(err) = imap.prepare(context).await {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
@@ -755,7 +755,7 @@ impl Job {
/// Chat in contrast to the Sent folder, which is normally managed
/// by the user via webmail or another email client.
async fn resync_folders(&mut self, context: &Context, imap: &mut Imap) -> Status {
if let Err(err) = imap.prepare(context).await {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}
@@ -779,7 +779,7 @@ impl Job {
}
async fn markseen_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status {
if let Err(err) = imap.prepare(context).await {
if let Err(err) = imap.connect_configured(context).await {
warn!(context, "could not connect: {:?}", err);
return Status::RetryLater;
}

View File

@@ -27,7 +27,7 @@ use crate::events::EventType;
use crate::job::{self, Action};
use crate::log::LogExt;
use crate::lot::{Lot, LotState, Meaning};
use crate::mimeparser::{parse_message_id, FailureReport, SystemMessage};
use crate::mimeparser::{FailureReport, SystemMessage};
use crate::param::{Param, Params};
use crate::pgp::split_armored_data;
use crate::stock_str;
@@ -400,9 +400,7 @@ impl Message {
let msg = Message {
id: row.get("id")?,
rfc724_mid: row.get::<_, String>("rfc724mid")?,
in_reply_to: row
.get::<_, Option<String>>("mime_in_reply_to")?
.and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
in_reply_to: row.get::<_, Option<String>>("mime_in_reply_to")?,
server_folder: row.get::<_, Option<String>>("server_folder")?,
server_uid: row.get("server_uid")?,
chat_id: row.get("chat_id")?,
@@ -1196,11 +1194,7 @@ pub async fn decide_on_contact_request(
Err(e) => warn!(context, "decide_on_contact_request error: {}", e),
},
(Block, false) => {
if let Err(e) = Contact::block(context, msg.from_id).await {
warn!(context, "Can't block contact: {}", e);
}
}
(Block, false) => Contact::block(context, msg.from_id).await,
(Block, true) => {
if !msg.chat_id.set_blocked(context, Blocked::Manually).await {
warn!(context, "Block mailing list failed.")

View File

@@ -398,7 +398,7 @@ impl MimeMessage {
if part.typ == Viewtype::Audio && self.get(HeaderDef::ChatVoiceMessage).is_some() {
part.typ = Viewtype::Voice;
}
if part.typ == Viewtype::Image || part.typ == Viewtype::Gif {
if part.typ == Viewtype::Image {
if let Some(value) = self.get(HeaderDef::ChatContent) {
if value == "sticker" {
part.typ = Viewtype::Sticker;
@@ -1585,7 +1585,7 @@ fn get_attachment_filename(
/// Returned addresses are normalized and lowercased.
pub(crate) fn get_recipients(headers: &[MailHeader]) -> Vec<SingleInfo> {
get_all_addresses_from_header(headers, |header_key| {
header_key == "to" || header_key == "cc"
header_key == "to" || header_key == "cc" || header_key == "bcc"
})
}
@@ -2955,46 +2955,4 @@ On 2020-10-25, Bob wrote:
Some("Mr.6Dx7ITn4w38.n9j7epIcuQI@outlook.com".to_string())
);
}
#[async_std::test]
async fn test_long_in_reply_to() -> Result<()> {
let t = TestContext::new_alice().await;
// A message with a long Message-ID.
// Long message-IDs are generated by Mailjet.
let raw = br###"Date: Thu, 28 Jan 2021 00:26:57 +0000
Chat-Version: 1.0\n\
Message-ID: <ABCDEFGH.1234567_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@mailjet.com>
To: Bob <bob@example.org>
From: Alice <alice@example.org>
Subject: ...
Some quote.
"###;
dc_receive_imf(&t, raw, "INBOX", 1, false).await?;
// Delta Chat generates In-Reply-To with a starting tab when Message-ID is too long.
let raw = br###"In-Reply-To:
<ABCDEFGH.1234567_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@mailjet.com>
Date: Thu, 28 Jan 2021 00:26:57 +0000
Chat-Version: 1.0\n\
Message-ID: <foobar@example.org>
To: Alice <alice@example.org>
From: Bob <bob@example.org>
Subject: ...
> Some quote.
Some reply
"###;
dc_receive_imf(&t, raw, "INBOX", 2, false).await?;
let msg = t.get_last_msg().await;
assert_eq!(msg.get_text().unwrap(), "Some reply");
let quoted_message = msg.quoted_message(&t).await?.unwrap();
assert_eq!(quoted_message.get_text().unwrap(), "Some quote.");
Ok(())
}
}

View File

@@ -876,35 +876,6 @@ static P_ROGERS_COM: Lazy<Provider> = Lazy::new(|| Provider {
oauth2_authorizer: None,
});
// systemausfall.org.md: systemausfall.org, solidaris.me
static P_SYSTEMAUSFALL_ORG: Lazy<Provider> = Lazy::new(|| Provider {
id: "systemausfall.org",
status: Status::Ok,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/systemausfall-org",
server: vec![
Server {
protocol: Imap,
socket: Ssl,
hostname: "mail.systemausfall.org",
port: 993,
username_pattern: Email,
},
Server {
protocol: Smtp,
socket: Ssl,
hostname: "mail.systemausfall.org",
port: 465,
username_pattern: Email,
},
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
// systemli.org.md: systemli.org
static P_SYSTEMLI_ORG: Lazy<Provider> = Lazy::new(|| Provider {
id: "systemli.org",
@@ -1020,23 +991,6 @@ static P_TISCALI_IT: Lazy<Provider> = Lazy::new(|| Provider {
oauth2_authorizer: None,
});
// tutanota.md: tutanota.com, tutanota.de, tutamail.com, tuta.io, keemail.me
static P_TUTANOTA: Lazy<Provider> = Lazy::new(|| {
Provider {
id: "tutanota",
status: Status::Broken,
before_login_hint: "Tutanota does not offer the standard IMAP e-mail protocol, so you cannot log in with Delta Chat to Tutanota.",
after_login_hint: "",
overview_page: "https://providers.delta.chat/tutanota",
server: vec![
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
// ukr.net.md: ukr.net
static P_UKR_NET: Lazy<Provider> = Lazy::new(|| Provider {
id: "ukr.net",
@@ -1331,18 +1285,11 @@ pub(crate) static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>>
("protonmail.ch", &*P_PROTONMAIL),
("riseup.net", &*P_RISEUP_NET),
("rogers.com", &*P_ROGERS_COM),
("systemausfall.org", &*P_SYSTEMAUSFALL_ORG),
("solidaris.me", &*P_SYSTEMAUSFALL_ORG),
("systemli.org", &*P_SYSTEMLI_ORG),
("t-online.de", &*P_T_ONLINE),
("magenta.de", &*P_T_ONLINE),
("testrun.org", &*P_TESTRUN),
("tiscali.it", &*P_TISCALI_IT),
("tutanota.com", &*P_TUTANOTA),
("tutanota.de", &*P_TUTANOTA),
("tutamail.com", &*P_TUTANOTA),
("tuta.io", &*P_TUTANOTA),
("keemail.me", &*P_TUTANOTA),
("ukr.net", &*P_UKR_NET),
("undernet.uy", &*P_UNDERNET_UY),
("vfemail.net", &*P_VFEMAIL),
@@ -1442,12 +1389,10 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
("protonmail", &*P_PROTONMAIL),
("riseup.net", &*P_RISEUP_NET),
("rogers.com", &*P_ROGERS_COM),
("systemausfall.org", &*P_SYSTEMAUSFALL_ORG),
("systemli.org", &*P_SYSTEMLI_ORG),
("t-online", &*P_T_ONLINE),
("testrun", &*P_TESTRUN),
("tiscali.it", &*P_TISCALI_IT),
("tutanota", &*P_TUTANOTA),
("ukr.net", &*P_UKR_NET),
("undernet.uy", &*P_UNDERNET_UY),
("vfemail", &*P_VFEMAIL),
@@ -1463,4 +1408,4 @@ pub(crate) static PROVIDER_IDS: Lazy<HashMap<&'static str, &'static Provider>> =
});
pub static PROVIDER_UPDATED: Lazy<chrono::NaiveDate> =
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 6, 7));
Lazy::new(|| chrono::NaiveDate::from_ymd(2021, 4, 10));

View File

@@ -113,6 +113,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
None
};
// what is up with that param name?
let name = if let Some(encoded_name) = param.get("n") {
let encoded_name = encoded_name.replace("+", "%20"); // sometimes spaces are encoded as `+`
match percent_decode_str(&encoded_name).decode_utf8() {

View File

@@ -1,4 +1,3 @@
use anyhow::{bail, Result};
use async_std::prelude::*;
use async_std::{
channel::{self, Receiver, Sender},
@@ -131,7 +130,7 @@ 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 {
Ok(Some(watch_folder)) => {
if let Err(err) = connection.prepare(ctx).await {
if let Err(err) = connection.connect_configured(ctx).await {
error_network!(ctx, "{}", err);
return;
}
@@ -160,7 +159,7 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> Int
match ctx.get_config(folder).await {
Ok(Some(watch_folder)) => {
// connect and fake idle if unable to connect
if let Err(err) = connection.prepare(ctx).await {
if let Err(err) = connection.connect_configured(ctx).await {
warn!(ctx, "imap connection failed: {}", err);
return connection.fake_idle(ctx, Some(watch_folder)).await;
}
@@ -302,11 +301,11 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
impl Scheduler {
/// Start the scheduler, panics if it is already running.
pub async fn start(&mut self, ctx: Context) -> Result<()> {
let (mvbox, mvbox_handlers) = ImapConnectionState::new(&ctx).await?;
let (sentbox, sentbox_handlers) = ImapConnectionState::new(&ctx).await?;
pub async fn start(&mut self, ctx: Context) {
let (mvbox, mvbox_handlers) = ImapConnectionState::new();
let (sentbox, sentbox_handlers) = ImapConnectionState::new();
let (smtp, smtp_handlers) = SmtpConnectionState::new();
let (inbox, inbox_handlers) = ImapConnectionState::new(&ctx).await?;
let (inbox, inbox_handlers) = ImapConnectionState::new();
let (inbox_start_send, inbox_start_recv) = channel::bounded(1);
let (mvbox_start_send, mvbox_start_recv) = channel::bounded(1);
@@ -322,7 +321,11 @@ impl Scheduler {
}))
};
if ctx.get_config_bool(Config::MvboxWatch).await? {
if ctx
.get_config_bool(Config::MvboxWatch)
.await
.unwrap_or_default()
{
let ctx = ctx.clone();
mvbox_handle = Some(task::spawn(async move {
simple_imap_loop(
@@ -340,7 +343,11 @@ impl Scheduler {
.expect("mvbox start send, missing receiver");
}
if ctx.get_config_bool(Config::SentboxWatch).await? {
if ctx
.get_config_bool(Config::SentboxWatch)
.await
.unwrap_or_default()
{
let ctx = ctx.clone();
sentbox_handle = Some(task::spawn(async move {
simple_imap_loop(
@@ -384,11 +391,10 @@ impl Scheduler {
.try_join(smtp_start_recv.recv())
.await
{
bail!("failed to start scheduler: {}", err);
error!(ctx, "failed to start scheduler: {}", err);
}
info!(ctx, "scheduler is running");
Ok(())
}
async fn maybe_network(&self) {
@@ -582,13 +588,13 @@ pub(crate) struct ImapConnectionState {
impl ImapConnectionState {
/// Construct a new connection.
async fn new(context: &Context) -> Result<(Self, ImapConnectionHandlers)> {
fn new() -> (Self, ImapConnectionHandlers) {
let (stop_sender, stop_receiver) = channel::bounded(1);
let (shutdown_sender, shutdown_receiver) = channel::bounded(1);
let (idle_interrupt_sender, idle_interrupt_receiver) = channel::bounded(1);
let handlers = ImapConnectionHandlers {
connection: Imap::new_configured(context, idle_interrupt_receiver).await?,
connection: Imap::new(idle_interrupt_receiver),
stop_receiver,
shutdown_sender,
};
@@ -601,7 +607,7 @@ impl ImapConnectionState {
let conn = ImapConnectionState { state };
Ok((conn, handlers))
(conn, handlers)
}
/// Interrupt any form of idle.

View File

@@ -253,17 +253,17 @@ async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
#[derive(Debug, thiserror::Error)]
pub enum JoinError {
#[error("Unknown QR-code: {0}")]
#[error("Unknown QR-code")]
QrCode(#[from] QrError),
#[error("A setup-contact/secure-join protocol is already running")]
AlreadyRunning,
#[error("An \"ongoing\" process is already running")]
OngoingRunning,
#[error("Failed to send handshake message: {0}")]
#[error("Failed to send handshake message")]
SendMessage(#[from] SendMsgError),
// Note that this can currently only occur if there is a bug in the QR/Lot code as this
// is supposed to create a contact for us.
#[error("Unknown contact (this is a bug): {0}")]
#[error("Unknown contact (this is a bug)")]
UnknownContact(#[source] anyhow::Error),
// Note that this can only occur if we failed to create the chat correctly.
#[error("Ongoing sender dropped (this is a bug)")]

View File

@@ -198,7 +198,7 @@ impl Sql {
}
}
info!(context, "Opened database {:?}.", dbfile);
info!(context, "Opened {:?}.", dbfile);
Ok(())
}
@@ -641,12 +641,8 @@ async fn maybe_add_from_param(
paramsv![],
|row| row.get::<_, String>(0),
|rows| {
// Rows that can't be parsed, for example if they are
// not UTF-8 strings, are ignored. It is possible
// when upgrading from C core to Rust core, which
// guarantees UTF-8 strings everywhere.
for row in rows.filter_map(|row| row.ok()) {
let param: Params = row.parse().unwrap_or_default();
for row in rows {
let param: Params = row?.parse().unwrap_or_default();
if let Some(file) = param.get(param_id) {
maybe_add_file(files_in_use, file);
}
@@ -806,36 +802,4 @@ mod test {
Ok(())
}
#[async_std::test]
async fn test_migration_flags() -> Result<()> {
let t = TestContext::new().await;
t.evtracker.get_info_contains("Opened database").await;
// as migrations::run() was already executed on context creation,
// another call should not result in any action needed.
// this test catches some bugs where dbversion was forgotten to be persisted.
let (recalc_fingerprints, update_icons, disable_server_delete, recode_avatar) =
migrations::run(&t, &t.sql).await?;
assert!(!recalc_fingerprints);
assert!(!update_icons);
assert!(!disable_server_delete);
assert!(!recode_avatar);
info!(&t, "test_migration_flags: XXX");
loop {
if let EventType::Info(info) = t.evtracker.recv().await.unwrap() {
assert!(
!info.contains("[migration]"),
"Migrations were run twice, you probably forgot to update the db version"
);
if info.contains("test_migration_flags: XXX") {
break;
}
}
}
Ok(())
}
}

View File

@@ -167,7 +167,7 @@ CREATE TABLE tokens (
ALTER TABLE acpeerstates ADD COLUMN verified_key;
ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);"#,
39,
38,
)
.await?;
}
@@ -454,7 +454,7 @@ paramsv![]
info!(context, "[migration] v75");
sql.execute_migration(
"ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT '';",
75,
74,
)
.await?;
}
@@ -466,7 +466,6 @@ paramsv![]
if dbversion < 77 {
info!(context, "[migration] v77");
recode_avatar = true;
sql.set_db_version(77).await?;
}
Ok((

View File

@@ -9,7 +9,6 @@ use std::{collections::BTreeMap, panic};
use std::{fmt, thread};
use ansi_term::Color;
use async_std::channel::Receiver;
use async_std::path::PathBuf;
use async_std::sync::{Arc, RwLock};
use async_std::{channel, pin::Pin};
@@ -48,7 +47,6 @@ static CONTEXT_NAMES: Lazy<std::sync::RwLock<BTreeMap<u32, String>>> =
pub(crate) struct TestContext {
pub ctx: Context,
pub dir: TempDir,
pub evtracker: EvTracker,
/// Counter for fake IMAP UIDs in [recv_msg], for private use in that function only.
recv_idx: RwLock<u32>,
/// Functions to call for events received.
@@ -105,8 +103,6 @@ impl TestContext {
let event_sinks: Arc<RwLock<Vec<Box<EventSink>>>> = Arc::new(RwLock::new(Vec::new()));
let sinks = Arc::clone(&event_sinks);
let (poison_sender, poison_receiver) = channel::bounded(1);
let (evtracker_sender, evtracker_receiver) = channel::unbounded();
async_std::task::spawn(async move {
// Make sure that the test fails if there is a panic on this thread here:
let current_id = task::current().id();
@@ -126,15 +122,13 @@ impl TestContext {
sink(event.clone()).await;
}
}
receive_event(&event);
evtracker_sender.send(event.typ).await.ok();
receive_event(event);
}
});
Self {
ctx,
dir,
evtracker: EvTracker(evtracker_receiver),
recv_idx: RwLock::new(0),
event_sinks,
poison_receiver,
@@ -574,28 +568,6 @@ pub fn bob_keypair() -> key::KeyPair {
}
}
pub struct EvTracker(Receiver<EventType>);
impl EvTracker {
pub async fn get_info_contains(&self, s: &str) -> EventType {
loop {
let event = self.0.recv().await.unwrap();
if let EventType::Info(i) = &event {
if i.contains(s) {
return event;
}
}
}
}
}
impl Deref for EvTracker {
type Target = Receiver<EventType>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Gets a specific message from a chat and asserts that the chat has a specific length.
///
/// Panics if the length of the chat is not `asserted_msgs_count` or if the chat item at `index` is not a Message.
@@ -619,12 +591,12 @@ pub(crate) async fn get_chat_msg(
/// Pretty-print an event to stdout
///
/// Done during tests this is captured by `cargo test` and associated with the test itself.
fn receive_event(event: &Event) {
fn receive_event(event: Event) {
let green = Color::Green.normal();
let yellow = Color::Yellow.normal();
let red = Color::Red.normal();
let msg = match &event.typ {
let msg = match event.typ {
EventType::Info(msg) => format!("INFO: {}", msg),
EventType::SmtpConnected(msg) => format!("[SMTP_CONNECTED] {}", msg),
EventType::ImapConnected(msg) => format!("[IMAP_CONNECTED] {}", msg),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB