Compare commits

..

45 Commits

Author SHA1 Message Date
link2xt
4f364f0433 Use tracing crate for logging 2023-03-25 17:35:56 +00:00
link2xt
381af52886 lockfile 2023-03-25 13:56:50 +00:00
link2xt
8c660ad828 Explicitly emit INFO events in stop_io() 2023-03-25 13:56:49 +00:00
link2xt
dab288936a Move last_error from Context to Events 2023-03-25 12:38:56 +00:00
link2xt
fb093253c6 Update generated nodejs constants files 2023-03-25 10:20:17 +00:00
dependabot[bot]
1864be5c55 Merge pull request #4229 from deltachat/dependabot/cargo/fuzz/openssl-0.10.48 2023-03-25 01:06:43 +00:00
dependabot[bot]
7138d44083 build(deps): bump openssl from 0.10.45 to 0.10.48 in /fuzz
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.45 to 0.10.48.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.45...openssl-v0.10.48)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-25 00:31:14 +00:00
Floris Bruynooghe
04daff0608 Add comment 2023-03-24 23:20:14 +00:00
Floris Bruynooghe
801250a9e0 feat(imex): Sort IP addresses for getting from ticket
When trying IP addresses from the ticket, have a very rough sort order
in which to try them.  Basically assume most local wifi's are
somewhere on 192.168.0.0/16 so prefer those first.
2023-03-24 23:20:14 +00:00
link2xt
20319b5426 Add --version option to deltachat-rpc-server 2023-03-24 23:11:11 +00:00
link2xt
9cca34bba5 Remove .wait_for_seen() calls in flaky tests
This is a merge commit for <https://github.com/deltachat/deltachat-core-rust/pull/4226>
2023-03-24 23:09:44 +00:00
link2xt
530981119e Update Cargo.lock 2023-03-24 23:08:46 +00:00
adbenitez
6d0327d057 deltachat-rpc-client: fix bug in Chat.send_message() 2023-03-24 18:56:08 -04:00
adbenitez
580ce5a9e9 deltachat-rpc-client: fix bug in Chat.send_message() 2023-03-24 17:34:41 -04:00
link2xt
6f327c950d Cargo.toml: replace branch with rev in default-net patch entry
This makes `cargo install --path deltachat-rpc-server` work again.
2023-03-24 21:16:04 +00:00
link2xt
92ad843ff2 Reduce test code duplication 2023-03-24 20:59:31 +00:00
link2xt
a8059c6bff python: remove flaky .wait_for_seen() from test_fetch_existing() 2023-03-24 18:09:40 +00:00
link2xt
4b468a25fe Remove pytest-rerunfailures
It is not compatible with pytest-timeout anyway:
<https://github.com/pytest-dev/pytest-rerunfailures/issues/99>
2023-03-24 16:13:33 +00:00
link2xt
1e135b649c Do not use IDLE in test_send_and_receive_message_markseen
Fix flakyness in case Dovecot sends only EXISTS
but not FETCH unsolicited response.
2023-03-24 16:09:40 +00:00
link2xt
40d32f2d0c Do not use IDLE in test_fetch_existing_msgs_group_and_single
Makes the test less flaky,
as Dovecot sometimes sends only the EXISTS response,
but not the FETCH response for flags.
2023-03-24 16:09:28 +00:00
link2xt
c9ec087cd8 python: do not use IDLE in test_markseen_message_and_mdn test
Make the test less flaky in case Dovecot notifies only about EXISTS
but not flag updates.
2023-03-24 16:09:15 +00:00
link2xt
84d79e1479 Do not use IDLE in test_mdn_asymmetric
Fixes test flakyness.
2023-03-24 16:08:58 +00:00
link2xt
83af248068 Turn more log messages into proper sentences. 2023-03-24 12:15:42 +00:00
link2xt
4f25edb1a1 Update OpenSSL to fix RUSTSEC-2023-0022
<https://rustsec.org/advisories/RUSTSEC-2023-0022>
2023-03-24 09:56:00 +00:00
link2xt
ded1634b7d python: look for "Marked messages ... as seen." by regexp
Tests reuse accounts, so UIDs may start from the number other than 1.
2023-03-24 09:43:59 +00:00
link2xt
635c73ffc6 python: use locally installed auditwheel
`auditwheel` is a part of manylinux images,
there is no need to create a virtual environment
and install it again.
2023-03-23 23:00:46 +00:00
link2xt
fcc1fe73be Do not use .wait_for_seen() in test_one_account_send_bcc_setting
.wait_for_seen() is unreliable, because sometimes Dovecot
sends only EXISTS to the IDLE connection, but not the FETCH.
Dovecot sends updates like FETCH only if some
connection has already observed the message in previous state
without the \Seen flag.

To avoid this race condition, wait until the core sets the flag,
then FETCH the message manually and check that the flag is set.
2023-03-23 22:57:35 +00:00
link2xt
fa278d50f7 Simplify layout of the deltachat-rpc-server crate 2023-03-23 22:10:28 +00:00
link2xt
2f02be4c64 ci: test the core on macOS 2023-03-23 20:46:58 +00:00
link2xt
7add1c116c Attempt to fix deltachat-rpc-server binary release uploads 2023-03-23 17:19:28 +00:00
link2xt
124a0e90e1 Release 1.112.0 2023-03-23 13:34:24 +00:00
Floris Bruynooghe
1716cdf51c ref(ffi): dc_receive_backup should block (#4211)
The documentation says this blocks.  This should block because it also
means the error reporting is more accurate by calling set_last_error
just before returning.
2023-03-23 14:15:34 +01:00
link2xt
3fdcffb314 Increase reference count before spawning background tasks in dc_receive_backup()
Merged PR <https://github.com/deltachat/deltachat-core-rust/pull/4206>
2023-03-23 08:54:33 +00:00
link2xt
f033aae25c Move most of the dc_receive_backup() into a safe function 2023-03-23 08:47:58 +00:00
Floris Bruynooghe
c42d942460 explicitly move for good measure 2023-03-23 08:47:58 +00:00
Floris Bruynooghe
0ba8201797 fix(dc_receive_backup): Increase refcount before spawn
Otherwise it is possible for the context that is used in the spawn to
be unreferenced.  Really this should be caught by the borrow checker
that ensures we only spawn things with a 'static lifetime, but we're
handling raw pointers so it doesn't.
2023-03-23 08:47:49 +00:00
iequidoo
87252ab053 cargo: bump async_zip from 0.0.9 to 0.0.11
Bumps [async_zip](https://github.com/Majored/rs-async-zip) from 0.0.9 to 0.0.11.
- [Release notes](https://github.com/Majored/rs-async-zip/releases)
- [Commits](https://github.com/Majored/rs-async-zip/compare/v0.0.9...v0.0.11)

---
updated-dependencies:
- dependency-name: async_zip
  dependency-type: direct:production
  update-type: version-update:semver-patch
...
2023-03-23 00:33:25 +00:00
link2xt
53eec521dc Make get_all_addresses_from_header non-generic
This saves 1287 lines according to `cargo llvm-lines --release`.
2023-03-22 23:51:41 +00:00
link2xt
238570a7b9 deltachat-ffi: update rand to 0.8.5 2023-03-22 23:37:01 +00:00
link2xt
043ae48806 Set pytest-timeout on async python tests 2023-03-22 20:41:17 +00:00
link2xt
fb88f2e6ab Reintroduce pytest-rerunfailures
Tests on GitHub Actions are very flaky recently.
2023-03-22 20:39:35 +00:00
link2xt
5db867cd1b Add IMAP_INBOX_IDLE event 2023-03-22 20:20:37 +00:00
B. Petersen
ec00c160c6 add missing define 2023-03-22 20:54:52 +01:00
Floris Bruynooghe
616eabc613 feat: Make the IoPausedGuard a simple sender (#4184)
This replaces the mechanism by which the IoPauseGuard makes sure the
IO scheduler is resumed: it really is a drop guard now by sending a
single message on drop.

This makes it not have to hold on to anything like the context so
makes it a lot easier to use.

The trade-off is that a long-running task is spawned when the guard is
created, this task needs to receive the message from the drop guard in
order for the scheduler to resume.
2023-03-22 17:42:21 +01:00
Floris Bruynooghe
89b32e02c5 deps(iroh): switch back to iroh main branch (#4202) 2023-03-22 16:05:55 +01:00
91 changed files with 3435 additions and 2023 deletions

View File

@@ -77,6 +77,9 @@ jobs:
- os: windows-latest
rust: 1.68.0
python: false # Python bindings compilation on Windows is not supported.
- os: macos-latest
rust: 1.68.0
python: 3.9
# Minimum Supported Rust Version = 1.64.0
#

View File

@@ -84,35 +84,15 @@ jobs:
contents: write
runs-on: "ubuntu-latest"
steps:
- name: Download deltachat-rpc-server-x86_64
- name: Download built binaries
uses: "actions/download-artifact@v3"
with:
name: "deltachat-rpc-server-x86_64"
path: "dist/deltachat-rpc-server-x86_64"
- name: Download deltachat-rpc-server-aarch64
uses: "actions/download-artifact@v3"
with:
name: "deltachat-rpc-server-aarch64"
path: "dist/deltachat-rpc-server-aarch64"
- name: Download deltachat-rpc-server-armv7
uses: "actions/download-artifact@v3"
with:
name: "deltachat-rpc-server-armv7"
path: "dist/deltachat-rpc-server-armv7"
- name: Download deltachat-rpc-server-win32.exe
uses: "actions/download-artifact@v3"
with:
name: "deltachat-rpc-server-win32.exe"
path: "dist/deltachat-rpc-server-win32.exe"
- name: Download deltachat-rpc-server-win64.exe
uses: "actions/download-artifact@v3"
with:
name: "deltachat-rpc-server-win64.exe"
path: "dist/deltachat-rpc-server-win64.exe"
- name: Compose dist/ directory
run: |
mkdir dist
for x in x86_64 aarch64 armv7 win32.exe win64.exe; do
mv "deltachat-rpc-server-$x"/* "dist/deltachat-rpc-server-$x"
done
- name: List downloaded artifacts
run: ls -l dist/
@@ -123,4 +103,4 @@ jobs:
run: |
gh release upload ${{ github.ref_name }} \
--repo ${{ github.repository }} \
dist/deltachat-rpc-server-*
dist/*

View File

@@ -3,16 +3,26 @@
## [Unreleased]
### Changes
- "full message view" not needed because of footers that go to contact status #4151
- Pick up system's light/dark mode in generated message HTML #4150
- Support non-persistent configuration with DELTACHAT_* env
- Print deltachat-repl errors with causes. #4166
- Add support for `--version` argument to `deltachat-rpc-server`. #4224
It can be used to check the installed version without starting the server.
### Fixes
- deltachat-rpc-client: fix bug in `Chat.send_message()`: invalid `MessageData` field `quotedMsg` instead of `quotedMsgId`
## [1.112.0] - 2023-03-23
### Changes
- Increase MSRV to 1.64. #4167
- Core takes care of stopping and re-starting IO itself where needed,
e.g. during backup creation. It is no longer needed to call
dc_stop_io(). dc_start_io() can now be called at any time without
harm. #4138
- More accurate maybe_add_bcc_self device message text #4175
e.g. during backup creation.
It is no longer needed to call `dc_stop_io()`.
`dc_start_io()` can now be called at any time without harm. #4138
- Pick up system's light/dark mode in generated message HTML. #4150
- More accurate `maybe_add_bcc_self` device message text. #4175
- "Full message view" not needed because of footers that go to contact status. #4151
- Support non-persistent configuration with `DELTACHAT_*` env. #4154
- Print deltachat-repl errors with causes. #4166
### Fixes
- Fix segmentation fault if `dc_context_unref()` is called during
@@ -23,6 +33,13 @@
- Do not emit "Failed to run incremental vacuum" warnings on success. #4160
- Ability to send backup over network and QR code to setup second device #4007
- Disable buffering during STARTTLS setup. #4190
- Add `DC_EVENT_IMAP_INBOX_IDLE` event to wait until the account
is ready for testing.
It is used to fix race condition between fetching
existing messages and starting the test. #4208
### API-Changes
- Use `tracing` crate for logging. #3960
## [1.111.0] - 2023-03-05
@@ -2314,5 +2331,6 @@ For a full list of changes, please see our closed Pull Requests:
https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[unreleased]: https://github.com/deltachat/deltachat-core-rust/compare/v1.111.0...HEAD
[unreleased]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.0...HEAD
[1.111.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.110.0...v1.111.0
[1.112.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.111.0...v1.112.0

57
Cargo.lock generated
View File

@@ -270,25 +270,16 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "async_io_utilities"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b20cffc5590f4bf33f05f97a3ea587feba9c50d20325b401daa096b92ff7da0"
dependencies = [
"tokio",
]
[[package]]
name = "async_zip"
version = "0.0.9"
version = "0.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a36d43bdefc7215b2b3a97edd03b1553b7969ad76551025eedd3b913c645f6e"
checksum = "c50d29ab7e2f9e808cca1a69ea56a36f4ff216f54a41a23aae1fd4afc05cc020"
dependencies = [
"async-compression",
"async_io_utilities",
"chrono",
"crc32fast",
"log",
"pin-project",
"thiserror",
"tokio",
]
@@ -1130,7 +1121,7 @@ dependencies = [
[[package]]
name = "default-net"
version = "0.13.1"
source = "git+https://github.com/dignifiedquire/default-net.git?branch=feat-android#7a257095bac009c4be0b93c2979801624fdd337b"
source = "git+https://github.com/dignifiedquire/default-net.git?rev=7a257095bac009c4be0b93c2979801624fdd337b#7a257095bac009c4be0b93c2979801624fdd337b"
dependencies = [
"dlopen",
"libc",
@@ -1145,7 +1136,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.111.0"
version = "1.112.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1174,7 +1165,6 @@ dependencies = [
"kamadak-exif",
"lettre_email",
"libc",
"log",
"mailparse",
"num-derive",
"num-traits",
@@ -1183,6 +1173,7 @@ dependencies = [
"parking_lot",
"percent-encoding",
"pgp",
"pin-project-lite",
"pretty_env_logger",
"proptest",
"qrcodegen",
@@ -1211,6 +1202,9 @@ dependencies = [
"tokio-stream",
"tokio-tar",
"toml",
"tracing",
"tracing-futures",
"tracing-subscriber",
"trust-dns-resolver",
"url",
"uuid",
@@ -1218,7 +1212,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.111.0"
version = "1.112.0"
dependencies = [
"anyhow",
"async-channel",
@@ -1241,7 +1235,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.111.0"
version = "1.112.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1252,20 +1246,24 @@ dependencies = [
"rusqlite",
"rustyline",
"tokio",
"tracing",
"tracing-log",
"tracing-subscriber",
]
[[package]]
name = "deltachat-rpc-server"
version = "1.111.0"
version = "1.112.0"
dependencies = [
"anyhow",
"deltachat",
"deltachat-jsonrpc",
"env_logger 0.10.0",
"futures-lite",
"log",
"serde",
"serde_json",
"tokio",
"tracing",
"tracing-subscriber",
"yerpc",
]
@@ -1279,7 +1277,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.111.0"
version = "1.112.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1288,10 +1286,12 @@ dependencies = [
"libc",
"num-traits",
"once_cell",
"rand 0.7.3",
"rand 0.8.5",
"serde_json",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
@@ -2427,7 +2427,7 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
[[package]]
name = "iroh"
version = "0.3.0"
source = "git+https://github.com/n0-computer/iroh?branch=flub/ticket-multiple-addrs#aacd1d84e11b218e55e9622c4500973224904fe9"
source = "git+https://github.com/n0-computer/iroh?branch=main#9ac4cf6e770879c8b2ec0dc6666fe531469e68e3"
dependencies = [
"abao",
"anyhow",
@@ -2465,6 +2465,7 @@ dependencies = [
"tracing",
"tracing-futures",
"tracing-subscriber",
"walkdir",
"webpki",
"x509-parser",
"zeroize",
@@ -3026,9 +3027,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.45"
version = "0.10.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
dependencies = [
"bitflags",
"cfg-if",
@@ -3067,9 +3068,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.80"
version = "0.9.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
dependencies = [
"autocfg",
"cc",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.111.0"
version = "1.112.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.64"
@@ -25,7 +25,7 @@ panic = 'abort'
opt-level = "z"
[patch.crates-io]
default-net = { git = "https://github.com/dignifiedquire/default-net.git", branch="feat-android" }
default-net = { git = "https://github.com/dignifiedquire/default-net.git", rev="7a257095bac009c4be0b93c2979801624fdd337b" }
quinn-udp = { git = "https://github.com/quinn-rs/quinn", branch="main" }
quinn-proto = { git = "https://github.com/quinn-rs/quinn", branch="main" }
@@ -39,7 +39,7 @@ async-channel = "1.8.0"
async-imap = { git = "https://github.com/async-email/async-imap", branch = "master", default-features = false, features = ["runtime-tokio"] }
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] }
async_zip = { version = "0.0.9", default-features = false, features = ["deflate"] }
async_zip = { version = "0.0.11", default-features = false, features = ["deflate", "fs"] }
backtrace = "0.3"
base64 = "0.21"
bitflags = "1.3"
@@ -54,7 +54,7 @@ hex = "0.4.0"
humansize = "2"
image = { version = "0.24.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
# iroh = { version = "0.3.0", default-features = false }
iroh = { git = 'https://github.com/n0-computer/iroh', branch = "flub/ticket-multiple-addrs" }
iroh = { git = 'https://github.com/n0-computer/iroh', branch = "main" }
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"
@@ -66,8 +66,12 @@ once_cell = "1.17.0"
percent-encoding = "2.2"
parking_lot = "0.12"
pgp = { version = "0.9", default-features = false }
pin-project-lite = { version = "0.2.9" }
pretty_env_logger = { version = "0.4", optional = true }
qrcodegen = "1.7.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", default-features=false, features = ["std", "env-filter", "registry", "fmt"] }
tracing-futures = "0.2"
quick-xml = "0.27"
rand = "0.8"
regex = "1.7"
@@ -98,8 +102,6 @@ uuid = { version = "1", features = ["serde", "v4"] }
ansi_term = "0.12.0"
criterion = { version = "0.4.0", features = ["async_tokio"] }
futures-lite = "1.12"
log = "0.4"
pretty_env_logger = "0.4"
proptest = { version = "1", default-features = false, features = ["std"] }
tempfile = "3"
testdir = "0.7.2"

View File

@@ -19,7 +19,7 @@ $ curl https://sh.rustup.rs -sSf | sh
Compile and run Delta Chat Core command line utility, using `cargo`:
```
$ RUST_LOG=deltachat_repl=info cargo run -p deltachat-repl -- ~/deltachat-db
$ cargo run -p deltachat-repl -- ~/deltachat-db
```
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.

View File

@@ -2,15 +2,15 @@ use criterion::{criterion_group, criterion_main, Criterion};
use deltachat::context::Context;
use deltachat::stock_str::StockStrings;
use deltachat::{info, Event, EventType, Events};
use deltachat::{Event, EventType, Events};
use tempfile::tempdir;
async fn send_events_benchmark(context: &Context) {
let emitter = context.get_event_emitter();
for _i in 0..1_000_000 {
info!(context, "interesting event...");
context.emit_event(EventType::Info("interesting event...".to_string()));
}
info!(context, "DONE");
context.emit_event(EventType::Info("DONE".to_string()));
loop {
match emitter.recv().await.unwrap() {

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.111.0"
version = "1.112.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
@@ -23,8 +23,10 @@ serde_json = "1.0"
tokio = { version = "1", features = ["rt-multi-thread"] }
anyhow = "1"
thiserror = "1"
rand = "0.7"
rand = "0.8"
once_cell = "1.17.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", default-features=false, features = ["std", "registry"] }
[features]
default = ["vendored"]

View File

@@ -5705,6 +5705,14 @@ void dc_event_unref(dc_event_t* event);
*/
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
/**
* Emitted before going into IDLE on the Inbox folder.
*
* @param data1 0
* @param data2 0
*/
#define DC_EVENT_IMAP_INBOX_IDLE 106
/**
* Emitted when a new blob file was successfully written
*
@@ -6990,6 +6998,11 @@ void dc_event_unref(dc_event_t* event);
/// `%1$s` will be replaced by name and address of the contact.
#define DC_STR_PROTECTION_DISABLED_BY_OTHER 161
/// "Scan to set up second device for %1$s"
///
/// `%1$s` will be replaced by name and address of the account.
#define DC_STR_BACKUP_TRANSFER_QR 162
/**
* @}
*/

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.111.0"
version = "1.112.0"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"

View File

@@ -47,6 +47,9 @@ pub enum JSONRPCEventType {
msg: String,
},
/// Emitted before going into IDLE on the Inbox folder.
ImapInboxIdle,
/// Emitted when an new file in the $BLOBDIR was created
NewBlobFile {
file: String,
@@ -293,6 +296,7 @@ impl From<EventType> for JSONRPCEventType {
EventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
EventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
EventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
EventType::ImapInboxIdle => ImapInboxIdle,
EventType::NewBlobFile(file) => NewBlobFile { file },
EventType::DeletedBlobFile(file) => DeletedBlobFile { file },
EventType::Warning(msg) => Warning { msg },

File diff suppressed because it is too large Load Diff

View File

@@ -55,5 +55,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.111.0"
"version": "1.112.0"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.111.0"
version = "1.112.0"
license = "MPL-2.0"
edition = "2021"
@@ -14,6 +14,9 @@ pretty_env_logger = "0.4"
rusqlite = "0.28"
rustyline = "11"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", default-features=false, features = ["std", "env-filter", "registry", "fmt"] }
tracing-log = "0.1.3"
[features]
default = ["vendored"]

View File

@@ -465,7 +465,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
continue_key_transfer(&context, MsgId::new(arg1.parse()?), arg2).await?;
}
"has-backup" => {
has_backup(&context, blobdir).await?;
has_backup(blobdir).await?;
}
"export-backup" => {
let dir = dirs::home_dir().unwrap_or_default();
@@ -508,7 +508,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
imex(&context, ImexMode::ImportSelfKeys, arg1.as_ref(), None).await?;
}
"export-setup" => {
let setup_code = create_setup_code(&context);
let setup_code = create_setup_code();
let file_name = blobdir.join("autocrypt-setup-message.html");
let file_content = render_setup_file(&context, &setup_code).await?;
fs::write(&file_name, file_content).await?;
@@ -1244,7 +1244,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let socks5_enabled = context
.get_config_bool(config::Config::Socks5Enabled)
.await?;
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
match provider::get_provider_info(arg1, socks5_enabled).await {
Some(info) => {
println!("Information for provider belonging to {arg1}:");
println!("status: {}", info.status as u32);

View File

@@ -22,7 +22,6 @@ use deltachat::qr_code_generator::get_securejoin_qr_svg;
use deltachat::securejoin::*;
use deltachat::stock_str::StockStrings;
use deltachat::{EventType, Events};
use log::{error, info, warn};
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
@@ -33,6 +32,9 @@ use rustyline::{
};
use tokio::fs;
use tokio::runtime::Handle;
use tracing::{error, info, warn};
use tracing_log::LogTracer;
use tracing_subscriber::{fmt, EnvFilter};
mod cmdline;
use self::cmdline::*;
@@ -481,7 +483,12 @@ async fn handle_cmd(
#[tokio::main]
async fn main() -> Result<(), Error> {
let _ = pretty_env_logger::try_init();
// Convert `log` records into `tracing` events.
LogTracer::init()?;
// Setup `tracing` subscriber according to `RUST_LOG` environment variable.
let filter = EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("info"))?;
fmt().with_env_filter(filter).with_writer(io::stderr).init();
let args = std::env::args().collect();
start(args).await?;

View File

@@ -126,7 +126,7 @@ class Chat:
"file": file,
"location": location,
"overrideSenderName": override_sender_name,
"quotedMsg": quoted_msg,
"quotedMessageId": quoted_msg,
}
msg_id = await self._rpc.send_msg(self.account.id, self.id, draft)
return Message(self.account, msg_id)

View File

@@ -31,6 +31,7 @@ class EventType(str, Enum):
SMTP_MESSAGE_SENT = "SmtpMessageSent"
IMAP_MESSAGE_DELETED = "ImapMessageDeleted"
IMAP_MESSAGE_MOVED = "ImapMessageMoved"
IMAP_INBOX_IDLE = "ImapInboxIdle"
NEW_BLOB_FILE = "NewBlobFile"
DELETED_BLOB_FILE = "DeletedBlobFile"
WARNING = "Warning"

View File

@@ -55,6 +55,11 @@ class ACFactory:
async def get_online_account(self) -> Account:
account = await self.new_configured_account()
await account.start_io()
while True:
event = await account.wait_for_event()
print(event)
if event.type == EventType.IMAP_INBOX_IDLE:
break
return account
async def get_online_accounts(self, num: int) -> List[Account]:

View File

@@ -6,7 +6,7 @@ envlist =
[testenv]
commands =
pytest --exitfirst {posargs}
pytest {posargs}
setenv =
# Avoid stack overflow when Rust core is built without optimizations.
RUST_MIN_STACK=8388608
@@ -15,6 +15,7 @@ passenv =
deps =
pytest
pytest-asyncio
pytest-timeout
aiohttp
aiodns
@@ -27,3 +28,6 @@ deps =
commands =
black --quiet --check --diff src/ examples/ tests/
ruff src/ examples/ tests/
[pytest]
timeout = 60

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.111.0"
version = "1.112.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -9,19 +9,17 @@ license = "MPL-2.0"
keywords = ["deltachat", "chat", "openpgp", "email", "encryption"]
categories = ["cryptography", "std", "email"]
[[bin]]
name = "deltachat-rpc-server"
[dependencies]
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", default-features = false }
deltachat = { path = "..", default-features = false }
anyhow = "1"
env_logger = { version = "0.10.0" }
futures-lite = "1.12.0"
log = "0.4"
serde_json = "1.0.91"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.25.0", features = ["io-std"] }
tracing-subscriber = { version = "0.3", default-features=false, features = ["std", "env-filter", "fmt"] }
tracing = { version = "0.1" }
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
[features]

View File

@@ -1,26 +1,51 @@
use std::env;
///! Delta Chat core RPC server.
///!
///! It speaks JSON Lines over stdio.
use std::path::PathBuf;
use anyhow::Result;
use anyhow::{anyhow, Context as _, Result};
use deltachat::constants::DC_VERSION_STR;
use deltachat_jsonrpc::api::events::event_to_json_rpc_notification;
use deltachat_jsonrpc::api::{Accounts, CommandApi};
use futures_lite::stream::StreamExt;
use tokio::io::{self, AsyncBufReadExt, BufReader};
use tokio::task::JoinHandle;
use tracing::{info, trace};
use tracing_subscriber::{fmt, EnvFilter};
use yerpc::{RpcClient, RpcSession};
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let mut args = env::args_os();
let _program_name = args.next().context("no command line arguments found")?;
if let Some(first_arg) = args.next() {
if first_arg.to_str() == Some("--version") {
if let Some(arg) = args.next() {
return Err(anyhow!("Unrecognized argument {:?}", arg));
}
eprintln!("{}", &*DC_VERSION_STR);
return Ok(());
} else {
return Err(anyhow!("Unrecognized option {:?}", first_arg));
}
}
if let Some(arg) = args.next() {
return Err(anyhow!("Unrecognized argument {:?}", arg));
}
let filter = EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("info"))?;
fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.init();
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
log::info!("Starting with accounts directory `{}`.", path);
info!("Starting with accounts directory `{}`.", path);
let accounts = Accounts::new(PathBuf::from(&path)).await?;
let events = accounts.get_event_emitter();
log::info!("Creating JSON-RPC API.");
info!("Creating JSON-RPC API.");
let state = CommandApi::new(accounts);
let (client, mut out_receiver) = RpcClient::new();
@@ -39,7 +64,7 @@ async fn main() -> Result<()> {
let send_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
while let Some(message) = out_receiver.next().await {
let message = serde_json::to_string(&message)?;
log::trace!("RPC send {}", message);
trace!("RPC send {message}");
println!("{message}");
}
Ok(())
@@ -50,13 +75,13 @@ async fn main() -> Result<()> {
let stdin = io::stdin();
let mut lines = BufReader::new(stdin).lines();
while let Some(message) = lines.next_line().await? {
log::trace!("RPC recv {}", message);
trace!("RPC recv {}", message);
let session = session.clone();
tokio::spawn(async move {
session.handle_incoming(&message).await;
});
}
log::info!("EOF reached on stdin");
info!("EOF reached on stdin");
Ok(())
});

View File

@@ -1,3 +1,5 @@
use tempfile::tempdir;
use deltachat::chat::{self, ChatId};
use deltachat::chatlist::*;
use deltachat::config;
@@ -6,24 +8,25 @@ use deltachat::context::*;
use deltachat::message::Message;
use deltachat::stock_str::StockStrings;
use deltachat::{EventType, Events};
use tempfile::tempdir;
use tracing::{error, info, warn};
use tracing_subscriber::{fmt, EnvFilter};
fn cb(event: EventType) {
match event {
EventType::ConfigureProgress { progress, .. } => {
log::info!("progress: {}", progress);
info!("progress: {progress}");
}
EventType::Info(msg) => {
log::info!("{}", msg);
info!("{msg}");
}
EventType::Warning(msg) => {
log::warn!("{}", msg);
warn!("{msg}");
}
EventType::Error(msg) => {
log::error!("{}", msg);
error!("{msg}");
}
event => {
log::info!("{:?}", event);
info!("{event:?}");
}
}
}
@@ -31,16 +34,22 @@ fn cb(event: EventType) {
/// Run with `RUST_LOG=simple=info cargo run --release --example simple -- email pw`.
#[tokio::main]
async fn main() {
pretty_env_logger::try_init_timed().ok();
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.init();
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
log::info!("creating database {:?}", dbfile);
info!("creating database {:?}", dbfile);
let ctx = Context::new(&dbfile, 0, Events::new(), StockStrings::new())
.await
.expect("Failed to create context");
let info = ctx.get_info().await;
log::info!("info: {:#?}", info);
info!("info: {:#?}", info);
let events = ctx.get_event_emitter();
let events_spawn = tokio::task::spawn(async move {
@@ -49,7 +58,7 @@ async fn main() {
}
});
log::info!("configuring");
info!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 3, "requires email password");
let email = args[1].clone();
@@ -63,9 +72,9 @@ async fn main() {
ctx.configure().await.unwrap();
log::info!("------ RUN ------");
info!("------ RUN ------");
ctx.start_io().await;
log::info!("--- SENDING A MESSAGE ---");
info!("--- SENDING A MESSAGE ---");
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com")
.await
@@ -73,7 +82,7 @@ async fn main() {
let chat_id = ChatId::create_for_contact(&ctx, contact_id).await.unwrap();
for i in 0..1 {
log::info!("sending message {}", i);
info!("sending message {}", i);
chat::send_text_msg(&ctx, chat_id, format!("Hi, here is my {i}nth message!"))
.await
.unwrap();
@@ -82,19 +91,19 @@ async fn main() {
// wait for the message to be sent out
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
log::info!("fetching chats..");
info!("fetching chats..");
let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap();
for i in 0..chats.len() {
let msg = Message::load_from_db(&ctx, chats.get_msg_id(i).unwrap().unwrap())
.await
.unwrap();
log::info!("[{}] msg: {:?}", i, msg);
info!("[{i}] msg: {msg:?}");
}
log::info!("stopping");
info!("stopping");
ctx.stop_io().await;
log::info!("closing");
info!("closing");
drop(ctx);
events_spawn.await.unwrap();
}

1285
fuzz/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,7 @@ module.exports = {
DC_EVENT_ERROR: 400,
DC_EVENT_ERROR_SELF_NOT_IN_GROUP: 410,
DC_EVENT_IMAP_CONNECTED: 102,
DC_EVENT_IMAP_INBOX_IDLE: 106,
DC_EVENT_IMAP_MESSAGE_DELETED: 104,
DC_EVENT_IMAP_MESSAGE_MOVED: 105,
DC_EVENT_IMEX_FILE_WRITTEN: 2052,
@@ -112,6 +113,7 @@ module.exports = {
DC_QR_ADDR: 320,
DC_QR_ASK_VERIFYCONTACT: 200,
DC_QR_ASK_VERIFYGROUP: 202,
DC_QR_BACKUP: 251,
DC_QR_ERROR: 400,
DC_QR_FPR_MISMATCH: 220,
DC_QR_FPR_OK: 210,
@@ -149,6 +151,7 @@ module.exports = {
DC_STR_AEAP_EXPLANATION_AND_LINK: 123,
DC_STR_ARCHIVEDCHATS: 40,
DC_STR_AUDIO: 11,
DC_STR_BACKUP_TRANSFER_QR: 162,
DC_STR_BAD_TIME_MSG_BODY: 85,
DC_STR_BROADCAST_LIST: 115,
DC_STR_CANNOT_LOGIN: 60,

View File

@@ -8,6 +8,7 @@ module.exports = {
103: 'DC_EVENT_SMTP_MESSAGE_SENT',
104: 'DC_EVENT_IMAP_MESSAGE_DELETED',
105: 'DC_EVENT_IMAP_MESSAGE_MOVED',
106: 'DC_EVENT_IMAP_INBOX_IDLE',
150: 'DC_EVENT_NEW_BLOB_FILE',
151: 'DC_EVENT_DELETED_BLOB_FILE',
300: 'DC_EVENT_WARNING',

View File

@@ -37,6 +37,7 @@ export enum C {
DC_EVENT_ERROR = 400,
DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410,
DC_EVENT_IMAP_CONNECTED = 102,
DC_EVENT_IMAP_INBOX_IDLE = 106,
DC_EVENT_IMAP_MESSAGE_DELETED = 104,
DC_EVENT_IMAP_MESSAGE_MOVED = 105,
DC_EVENT_IMEX_FILE_WRITTEN = 2052,
@@ -112,6 +113,7 @@ export enum C {
DC_QR_ADDR = 320,
DC_QR_ASK_VERIFYCONTACT = 200,
DC_QR_ASK_VERIFYGROUP = 202,
DC_QR_BACKUP = 251,
DC_QR_ERROR = 400,
DC_QR_FPR_MISMATCH = 220,
DC_QR_FPR_OK = 210,
@@ -149,6 +151,7 @@ export enum C {
DC_STR_AEAP_EXPLANATION_AND_LINK = 123,
DC_STR_ARCHIVEDCHATS = 40,
DC_STR_AUDIO = 11,
DC_STR_BACKUP_TRANSFER_QR = 162,
DC_STR_BAD_TIME_MSG_BODY = 85,
DC_STR_BROADCAST_LIST = 115,
DC_STR_CANNOT_LOGIN = 60,
@@ -289,6 +292,7 @@ export const EventId2EventName: { [key: number]: string } = {
103: 'DC_EVENT_SMTP_MESSAGE_SENT',
104: 'DC_EVENT_IMAP_MESSAGE_DELETED',
105: 'DC_EVENT_IMAP_MESSAGE_MOVED',
106: 'DC_EVENT_IMAP_INBOX_IDLE',
150: 'DC_EVENT_NEW_BLOB_FILE',
151: 'DC_EVENT_DELETED_BLOB_FILE',
300: 'DC_EVENT_WARNING',

View File

@@ -60,5 +60,5 @@
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.111.0"
"version": "1.112.0"
}

View File

@@ -95,6 +95,7 @@ class Account:
ptr,
lib.dc_context_unref,
)
self._id = lib.dc_get_id(self._dc_context)
self._shutdown_event = Event()
self._event_thread = EventThread(self)
@@ -139,6 +140,10 @@ class Account:
"""return dictionary of built config parameters."""
return get_dc_info_as_dict(self._dc_context)
def get_id(self) -> int:
"""Return account ID."""
return self._id
def dump_account_info(self, logfile):
def log(*args, **kwargs):
kwargs["file"] = logfile

View File

@@ -190,7 +190,7 @@ class FFIEventTracker:
- ac2 is still running FetchExsistingMsgs job and thinks it's an existing, old message
- therefore no DC_EVENT_INCOMING_MSG is sent
"""
self.get_info_contains("INBOX: Idle entering")
self.get_matching("DC_EVENT_IMAP_INBOX_IDLE")
def wait_next_incoming_message(self):
"""wait for and return next incoming message."""
@@ -263,6 +263,7 @@ class EventThread(threading.Thread):
self._process_event(event)
def _process_event(self, event) -> None:
account_id = lib.dc_event_get_account_id(event)
evt = lib.dc_event_get_id(event)
data1 = lib.dc_event_get_data1_int(event)
# the following code relates to the deltachat/_build.py's helper
@@ -272,6 +273,7 @@ class EventThread(threading.Thread):
data2 = from_optional_dc_charpointer(lib.dc_event_get_data2_str(event))
else:
data2 = lib.dc_event_get_data2_int(event)
assert account_id == self.account.get_id(), f"data2={data2}"
lib.dc_event_unref(event)
ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2)

View File

@@ -1,19 +0,0 @@
import os
import subprocess
import sys
if __name__ == "__main__":
assert len(sys.argv) == 2
workspacedir = sys.argv[1]
for relpath in os.listdir(workspacedir):
if relpath.startswith("deltachat"):
p = os.path.join(workspacedir, relpath)
subprocess.check_call(
[
"auditwheel",
"repair",
p,
"-w",
workspacedir,
],
)

View File

@@ -220,16 +220,16 @@ def test_fetch_existing(acfactory, lp, mvbox_move):
acfactory.bring_accounts_online()
assert_folders_configured(ac1)
assert ac1.direct_imap.select_config_folder("mvbox" if mvbox_move else "inbox")
with ac1.direct_imap.idle() as idle1:
lp.sec("send out message with bcc to ourselves")
ac1.set_config("bcc_self", "1")
chat = acfactory.get_accepted_chat(ac1, ac2)
chat.send_text("message text")
assert_folders_configured(ac1)
lp.sec("send out message with bcc to ourselves")
ac1.set_config("bcc_self", "1")
chat = acfactory.get_accepted_chat(ac1, ac2)
chat.send_text("message text")
lp.sec("wait until the bcc_self message arrives in correct folder and is marked seen")
assert idle1.wait_for_seen()
lp.sec("wait until the bcc_self message arrives in correct folder and is marked seen")
if mvbox_move:
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
else:
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
assert_folders_configured(ac1)
lp.sec("create a cloned ac1 and fetch contact history during configure")
@@ -271,12 +271,12 @@ def test_fetch_existing_msgs_group_and_single(acfactory, lp):
ac1._evtracker.wait_next_incoming_message()
lp.sec("send out message with bcc to ourselves")
with ac1.direct_imap.idle() as idle1:
ac1.set_config("bcc_self", "1")
ac1_ac2_chat = ac1.create_chat(ac2)
ac1_ac2_chat.send_text("outgoing, encrypted direct message, creating a chat")
# wait until the bcc_self message arrives
assert idle1.wait_for_seen()
ac1.set_config("bcc_self", "1")
ac1_ac2_chat = ac1.create_chat(ac2)
ac1_ac2_chat.send_text("outgoing, encrypted direct message, creating a chat")
# wait until the bcc_self message arrives
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
lp.sec("Clone online account and let it fetch the existing messages")
ac1_clone = acfactory.new_online_configuring_account(cloned_from=ac1)

View File

@@ -134,22 +134,27 @@ def test_one_account_send_bcc_setting(acfactory, lp):
ac1.set_config("bcc_self", "1")
lp.sec("send out message with bcc to ourselves")
with ac1.direct_imap.idle() as idle1:
msg_out = chat.send_text("message2")
msg_out = chat.send_text("message2")
# wait for send out (BCC)
ev = ac1._evtracker.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert ac1.get_config("bcc_self") == "1"
# wait for send out (BCC)
ev = ac1._evtracker.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
assert ac1.get_config("bcc_self") == "1"
# now make sure we are sending message to ourselves too
assert self_addr in ev.data2
assert other_addr in ev.data2
assert idle1.wait_for_seen()
# Second client receives only second message, but not the first
# Second client receives only second message, but not the first.
ev_msg = ac1_clone._evtracker.wait_next_messages_changed()
assert ev_msg.text == msg_out.text
# now make sure we are sending message to ourselves too
assert self_addr in ev.data2
assert other_addr in ev.data2
# BCC-self messages are marked as seen by the sender device.
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
# Check that the message is marked as seen on IMAP.
ac1.direct_imap.select_folder("Inbox")
assert len(list(ac1.direct_imap.conn.fetch(AND(seen=True)))) == 1
def test_send_file_twice_unicode_filename_mangling(tmpdir, acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
@@ -513,22 +518,22 @@ def test_send_and_receive_message_markseen(acfactory, lp):
msg4 = ac2._evtracker.wait_next_incoming_message()
lp.sec("mark messages as seen on ac2, wait for changes on ac1")
with ac1.direct_imap.idle() as idle1:
with ac2.direct_imap.idle() as idle2:
ac2.mark_seen_messages([msg2, msg4])
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_NOTICED")
assert msg2.chat.id == msg4.chat.id
assert ev.data1 == msg2.chat.id
assert ev.data2 == 0
idle2.wait_for_seen()
ac2.mark_seen_messages([msg2, msg4])
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_NOTICED")
assert msg2.chat.id == msg4.chat.id
assert ev.data1 == msg2.chat.id
assert ev.data2 == 0
ac2._evtracker.get_info_contains("Marked messages .* in folder INBOX as seen.")
lp.step("1")
for _i in range(2):
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL
lp.step("2")
idle1.wait_for_seen() # Check that ac1 marks the read receipt as read
lp.step("1")
for _i in range(2):
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL
lp.step("2")
# Check that ac1 marks the read receipt as read.
ac1._evtracker.get_info_contains("Marked messages .* in folder INBOX as seen.")
assert msg1.is_out_mdn_received()
assert msg3.is_out_mdn_received()
@@ -613,18 +618,24 @@ def test_markseen_message_and_mdn(acfactory, mvbox_move):
# 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()
ac2.mark_seen_messages([msg])
folder = "mvbox" if mvbox_move else "inbox"
for ac in [ac1, ac2]:
if mvbox_move:
ac._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
else:
ac._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
ac1.direct_imap.select_config_folder(folder)
ac2.direct_imap.select_config_folder(folder)
with ac1.direct_imap.idle() as idle1:
with ac2.direct_imap.idle() as idle2:
acfactory.get_accepted_chat(ac1, ac2).send_text("hi")
msg = ac2._evtracker.wait_next_incoming_message()
ac2.mark_seen_messages([msg])
idle2.wait_for_seen() # Check original message is marked as seen
idle1.wait_for_seen() # Check that the mdn is marked as seen
# Check that the mdn is marked as seen
assert len(list(ac1.direct_imap.conn.fetch(AND(seen=True)))) == 1
# Check original message is marked as seen
assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True)))) == 1
def test_reply_privately(acfactory):
@@ -678,23 +689,24 @@ def test_mdn_asymmetric(acfactory, lp):
assert len(msg.chat.get_messages()) == 1
ac1.direct_imap.select_config_folder("mvbox")
with ac1.direct_imap.idle() as idle1:
lp.sec("ac2: mark incoming message as seen")
ac2.mark_seen_messages([msg])
lp.sec("ac2: mark incoming message as seen")
ac2.mark_seen_messages([msg])
lp.sec("ac1: waiting for incoming activity")
# MDN should be moved even though MDNs are already disabled
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
lp.sec("ac1: waiting for incoming activity")
# MDN should be moved even though MDNs are already disabled
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
assert len(chat.get_messages()) == 1
assert len(chat.get_messages()) == 1
# Wait for the message to be marked as seen on IMAP.
assert idle1.wait_for_seen()
# Wait for the message to be marked as seen on IMAP.
ac1._evtracker.get_info_contains("Marked messages 1 in folder DeltaChat as seen.")
# MDN is received even though MDNs are already disabled
assert msg_out.is_out_mdn_received()
ac1.direct_imap.select_config_folder("mvbox")
assert len(list(ac1.direct_imap.conn.fetch(AND(seen=True)))) == 1
def test_send_and_receive_will_encrypt_decrypt(acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)

View File

@@ -8,7 +8,7 @@ envlist =
[testenv]
commands =
pytest -n6 --exitfirst --extra-info -v -rsXx --ignored --strict-tls {posargs: tests examples}
pytest -n6 --extra-info -v -rsXx --ignored --strict-tls {posargs: tests examples}
pip wheel . -w {toxworkdir}/wheelhouse --no-deps
setenv =
# Avoid stack overflow when Rust core is built without optimizations.
@@ -33,18 +33,6 @@ passenv =
CARGO_TARGET_DIR
RUSTC_WRAPPER
[testenv:auditwheels]
skipsdist = True
deps = auditwheel
passenv =
DCC_RS_DEV
DCC_RS_TARGET
AUDITWHEEL_ARCH
AUDITWHEEL_PLAT
AUDITWHEEL_POLICY
commands =
python tests/auditwheels.py {toxworkdir}/wheelhouse
[testenv:lint]
skipsdist = True
skip_install = True

View File

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

View File

@@ -93,7 +93,7 @@ def main():
if "alpha" not in newversion:
for line in open("CHANGELOG.md"):
## 1.25.0
if line.startswith("## ") and line[2:].strip().startswith(newversion):
if line.startswith("## [") and line[4:].strip().startswith(newversion):
break
else:
raise SystemExit(

View File

@@ -262,7 +262,7 @@ impl Accounts {
pub async fn stop_io(&self) {
// Sending an event here wakes up event loop even
// if there are no accounts.
info!(self, "Stopping IO for all accounts");
self.emit_event(EventType::Info("Stopping IO for all accounts.".to_string()));
for account in self.accounts.values() {
account.stop_io().await;
}
@@ -291,6 +291,11 @@ impl Accounts {
pub fn get_event_emitter(&self) -> EventEmitter {
self.events.get_emitter()
}
/// Returns event channel.
pub fn events(&self) -> Events {
self.events.clone()
}
}
/// Configuration file name.

View File

@@ -14,6 +14,7 @@ use num_traits::FromPrimitive;
use tokio::io::AsyncWriteExt;
use tokio::{fs, io};
use tokio_stream::wrappers::ReadDirStream;
use tracing::{error, info, warn};
use crate::config::Config;
use crate::constants::{
@@ -329,7 +330,7 @@ impl<'a> BlobObject<'a> {
// max_bytes is 20_000 bytes: Outlook servers don't allow headers larger than 32k.
// 32 / 4 * 3 = 24k if you account for base64 encoding. To be safe, we reduced this to 20k.
if let Some(new_name) = self.recode_to_size(context, blob_abs, img_wh, Some(20_000))? {
if let Some(new_name) = self.recode_to_size(blob_abs, img_wh, Some(20_000))? {
self.name = new_name;
}
Ok(())
@@ -351,10 +352,7 @@ impl<'a> BlobObject<'a> {
MediaQuality::Worse => WORSE_IMAGE_SIZE,
};
if self
.recode_to_size(context, blob_abs, img_wh, None)?
.is_some()
{
if self.recode_to_size(blob_abs, img_wh, None)?.is_some() {
return Err(format_err!(
"Internal error: recode_to_size(..., None) shouldn't change the name of the image"
));
@@ -364,21 +362,20 @@ impl<'a> BlobObject<'a> {
fn recode_to_size(
&self,
context: &Context,
mut blob_abs: PathBuf,
mut img_wh: u32,
max_bytes: Option<usize>,
) -> Result<Option<String>> {
tokio::task::block_in_place(move || {
let mut img = image::open(&blob_abs).context("image recode failure")?;
let orientation = self.get_exif_orientation(context);
let orientation = self.get_exif_orientation();
let mut encoded = Vec::new();
let mut changed_name = None;
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 || encoded_img_exceeds_bytes(&img, max_bytes, &mut encoded)?;
let do_rotate = matches!(orientation, Ok(90) | Ok(180) | Ok(270));
if do_scale || do_rotate {
@@ -401,7 +398,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 encoded_img_exceeds_bytes(&new_img, max_bytes, &mut encoded)? {
if img_wh < 20 {
return Err(format_err!(
"Failed to scale image to below {}B",
@@ -416,8 +413,7 @@ impl<'a> BlobObject<'a> {
}
info!(
context,
"Final scaled-down image size: {}B ({}px)",
"Final scaled-down image size: {}B ({}px).",
encoded.len(),
img_wh
);
@@ -446,7 +442,7 @@ impl<'a> BlobObject<'a> {
})
}
pub fn get_exif_orientation(&self, context: &Context) -> Result<i32> {
pub fn get_exif_orientation(&self) -> Result<i32> {
let file = std::fs::File::open(self.to_abs_path())?;
let mut bufreader = std::io::BufReader::new(&file);
let exifreader = exif::Reader::new();
@@ -458,7 +454,7 @@ impl<'a> BlobObject<'a> {
Some(3) => return Ok(180),
Some(6) => return Ok(90),
Some(8) => return Ok(270),
other => warn!(context, "exif orientation value ignored: {:?}", other),
other => warn!("Exif orientation value ignored: {other:?}."),
}
}
Ok(0)
@@ -490,7 +486,7 @@ impl<'a> BlobDirContents<'a> {
match entry {
Ok(entry) => Some(entry),
Err(err) => {
error!(context, "Failed to read blob file: {err}");
error!("Failed to read blob file: {err:#}.");
None
}
}
@@ -500,8 +496,7 @@ impl<'a> BlobDirContents<'a> {
true => Some(entry.path()),
false => {
warn!(
context,
"Export: Found blob dir entry {} that is not a file, ignoring",
"Export: Found blob dir entry {} that is not a file, ignoring.",
entry.path().display()
);
None
@@ -543,7 +538,7 @@ impl<'a> Iterator for BlobDirIter<'a> {
// silently skipping them is fine.
match BlobObject::from_path(self.context, path) {
Ok(blob) => return Some(blob),
Err(err) => warn!(self.context, "{err}"),
Err(err) => warn!("{err:#}"),
}
}
None
@@ -559,7 +554,6 @@ fn encode_img(img: &DynamicImage, encoded: &mut Vec<u8>) -> anyhow::Result<()> {
Ok(())
}
fn encoded_img_exceeds_bytes(
context: &Context,
img: &DynamicImage,
max_bytes: Option<usize>,
encoded: &mut Vec<u8>,
@@ -568,8 +562,7 @@ fn encoded_img_exceeds_bytes(
encode_img(img, encoded)?;
if encoded.len() > max_bytes {
info!(
context,
"image size {}B ({}x{}px) exceeds {}B, need to scale down",
"Image size {}B ({}x{}px) exceeds {}B, need to scale down.",
encoded.len(),
img.width(),
img.height(),
@@ -823,7 +816,7 @@ mod tests {
let blob = BlobObject::new_from_path(&t, &avatar_blob).await.unwrap();
blob.recode_to_size(&t, blob.to_abs_path(), 1000, Some(3000))
blob.recode_to_size(blob.to_abs_path(), 1000, Some(3000))
.unwrap();
assert!(file_size(&avatar_blob).await <= 3000);
assert!(file_size(&avatar_blob).await > 2000);
@@ -1007,7 +1000,7 @@ mod tests {
check_image_size(&file, original_width, original_height);
let blob = BlobObject::new_from_path(&alice, &file).await?;
assert_eq!(blob.get_exif_orientation(&alice).unwrap_or(0), orientation);
assert_eq!(blob.get_exif_orientation().unwrap_or(0), orientation);
let mut msg = Message::new(Viewtype::Image);
msg.set_file(file.to_str().unwrap(), None);
@@ -1028,7 +1021,7 @@ mod tests {
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);
assert_eq!(blob.get_exif_orientation().unwrap_or(0), 0);
let img = check_image_size(file, compressed_width, compressed_height);
Ok(img)

View File

@@ -10,6 +10,7 @@ use std::time::{Duration, SystemTime};
use anyhow::{bail, ensure, Context as _, Result};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use tracing::{error, info, warn};
use crate::aheader::EncryptPreference;
use crate::blob::BlobObject;
@@ -245,10 +246,7 @@ impl ChatId {
Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat).await?;
chat_id
} else {
warn!(
context,
"Cannot create chat, contact {} does not exist.", contact_id,
);
warn!("Cannot create chat, contact {contact_id} does not exist.");
bail!("Can not create chat for non-existing contact");
}
}
@@ -284,12 +282,7 @@ impl ChatId {
let chat_id = ChatId::new(u32::try_from(row_id)?);
info!(
context,
"Created group/mailinglist '{}' grpid={} as {}, blocked={}",
grpname,
grpid,
chat_id,
create_blocked,
"Created group/mailinglist '{grpname}' grpid={grpid} as {chat_id}, blocked={create_blocked}."
);
Ok(chat_id)
@@ -336,16 +329,13 @@ impl ChatId {
Chattype::Single => {
for contact_id in get_chat_contacts(context, self).await? {
if contact_id != ContactId::SELF {
info!(
context,
"Blocking the contact {} to block 1:1 chat", contact_id
);
info!("Blocking the contact {contact_id} to block 1:1 chat.");
Contact::block(context, contact_id).await?;
}
}
}
Chattype::Group => {
info!(context, "Can't block groups yet, deleting the chat");
info!("Can't block groups yet, deleting the chat.");
self.delete(context).await?;
}
Chattype::Mailinglist => {
@@ -410,7 +400,7 @@ impl ChatId {
let chat = Chat::load_from_db(context, self).await?;
if protect == chat.protected {
info!(context, "Protection status unchanged for {}.", self);
info!("Protection status unchanged for {self}.");
return Ok(());
}
@@ -500,7 +490,7 @@ impl ChatId {
let chat = Chat::load_from_db(context, self).await?;
if let Err(e) = self.inner_set_protection(context, protect).await {
error!(context, "Cannot set protection: {}", e); // make error user-visible
error!("Cannot set protection: {e:#}."); // make error user-visible
return Err(e);
}
@@ -1068,10 +1058,7 @@ impl ChatId {
!self.is_special(),
"can not set gossiped timestamp for special chats"
);
info!(
context,
"set gossiped_timestamp for chat {} to {}.", self, timestamp,
);
info!("Set gossiped_timestamp for chat {self} to {timestamp}.");
context
.sql
@@ -1209,10 +1196,7 @@ impl Chat {
}
}
Err(err) => {
error!(
context,
"failed to load contacts for {}: {:#}", chat.id, err
);
error!("Failed to load contacts for {}: {:#}.", chat.id, err);
}
}
chat.name = chat_name;
@@ -1475,10 +1459,7 @@ impl Chat {
{
to_id = id;
} else {
error!(
context,
"Cannot send message, contact for {} not found.", self.id,
);
error!("Cannot send message, contact for {} not found.", self.id,);
bail!("Cannot set message, contact for {} not found.", self.id);
}
} else if self.typ == Chattype::Group
@@ -2032,8 +2013,8 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
.with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
if msg.viewtype == Viewtype::Image {
if let Err(e) = blob.recode_to_image_size(context).await {
warn!(context, "Cannot recode image, using original data: {:?}", e);
if let Err(err) = blob.recode_to_image_size(context).await {
warn!("Cannot recode image, using original data: {err:#}.");
}
}
msg.param.set(Param::File, blob.as_name());
@@ -2073,7 +2054,6 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
}
info!(
context,
"Attaching \"{}\" for message type #{}.",
blob.to_abs_path().display(),
msg.viewtype
@@ -2256,7 +2236,7 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result<Option<
let attach_selfavatar = match shall_attach_selfavatar(context, msg.chat_id).await {
Ok(attach_selfavatar) => attach_selfavatar,
Err(err) => {
warn!(context, "job: cannot get selfavatar-state: {:#}", err);
warn!("SMTP job cannot get selfavatar-state: {err:#}.");
false
}
};
@@ -2281,10 +2261,7 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result<Option<
if recipients.is_empty() {
// may happen eg. for groups with only SELF and bcc_self disabled
info!(
context,
"message {} has no recipient, skipping smtp-send", msg_id
);
info!("Message {msg_id} has no recipient, skipping smtp-send.");
msg_id.set_delivered(context).await?;
return Ok(None);
}
@@ -2318,27 +2295,27 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result<Option<
if 0 != rendered_msg.last_added_location_id {
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()).await {
error!(context, "Failed to set kml sent_timestamp: {:#}", err);
error!("Failed to set kml sent_timestamp: {err:#}.");
}
if !msg.hidden {
if let Err(err) =
location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id)
.await
{
error!(context, "Failed to set msg_location_id: {:#}", err);
error!("Failed to set msg_location_id: {err:#}.");
}
}
}
if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
if let Err(err) = context.delete_sync_ids(sync_ids).await {
error!(context, "Failed to delete sync ids: {:#}", err);
error!("Failed to delete sync ids: {err:#}.");
}
}
if attach_selfavatar {
if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()).await {
error!(context, "Failed to set selfavatar timestamp: {:#}", err);
error!("Failed to set selfavatar timestamp: {err:#}.");
}
}
@@ -2692,9 +2669,7 @@ pub(crate) async fn mark_old_messages_as_noticed(
if !changed_chats.is_empty() {
info!(
context,
"Marking chats as noticed because there are newer outgoing messages: {:?}",
changed_chats
"Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
);
}
@@ -3013,10 +2988,7 @@ pub(crate) async fn add_contact_to_chat_ex(
if context.is_self_addr(contact.get_addr()).await? {
// ourself is added using ContactId::SELF, do not add this address explicitly.
// if SELF is not in the group, members cannot be added at all.
warn!(
context,
"invalid attempt to add self e-mail address to group"
);
warn!("Invalid attempt to add self e-mail address to group.");
return Ok(false);
}
@@ -3029,10 +3001,7 @@ pub(crate) async fn add_contact_to_chat_ex(
if chat.is_protected()
&& contact.is_verified(context).await? != VerifiedStatus::BidirectVerified
{
error!(
context,
"Only bidirectional verified contacts can be added to protected chats."
);
error!("Only bidirectional verified contacts can be added to protected chats.");
return Ok(false);
}
if is_contact_in_chat(context, chat_id, contact_id).await? {
@@ -3562,7 +3531,7 @@ pub async fn add_device_msg_with_importance(
if let Some(label) = label {
if was_device_msg_ever_added(context, label).await? {
info!(context, "device-message {} already added", label);
info!("Device-message {label} already added.");
return Ok(msg_id);
}
}

View File

@@ -1,6 +1,7 @@
//! # Chat list module.
use anyhow::{ensure, Context as _, Result};
use tracing::warn;
use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility};
use crate::constants::{
@@ -176,7 +177,7 @@ impl Chatlist {
// allow searching over special names that may change at any time
// when the ui calls set_stock_translation()
if let Err(err) = update_special_chat_names(context).await {
warn!(context, "cannot update special chat names: {:?}", err)
warn!("Cannot update special chat names: {err:#}.")
}
let str_like_cmd = format!("%{query}%");

View File

@@ -13,6 +13,7 @@ use futures_lite::FutureExt as _;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use server_params::{expand_param_vector, ServerParams};
use tokio::task;
use tracing::{info, warn};
use crate::config::Config;
use crate::contact::addr_cmp;
@@ -100,7 +101,7 @@ impl Context {
}
async fn inner_configure(&self) -> Result<()> {
info!(self, "Configure ...");
info!("Configure ...");
let mut param = LoginParam::load_candidate_params(self).await?;
let old_addr = self.get_config(Config::ConfiguredAddr).await?;
@@ -125,13 +126,10 @@ async fn on_configure_completed(
if let Some(config_defaults) = &provider.config_defaults {
for def in config_defaults.iter() {
if !context.config_exists(def.key).await? {
info!(context, "apply config_defaults {}={}", def.key, def.value);
info!("apply config_defaults {}={}", def.key, def.value);
context.set_config(def.key, Some(def.value)).await?;
} else {
info!(
context,
"skip already set config_defaults {}={}", def.key, def.value
);
info!("skip already set config_defaults {}={}", def.key, def.value);
}
}
}
@@ -143,7 +141,7 @@ async fn on_configure_completed(
.await
.is_err()
{
warn!(context, "cannot add after_login_hint as core-provider-info");
warn!("cannot add after_login_hint as core-provider-info");
}
}
}
@@ -186,7 +184,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
.await?
.and_then(|e| e.parse().ok())
{
info!(ctx, "Authorized address is {}", oauth2_addr);
info!("Authorized address is {oauth2_addr}");
param.addr = oauth2_addr;
ctx.sql
.set_raw_config("addr", Some(param.addr.as_str()))
@@ -215,22 +213,17 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
{
// no advanced parameters entered by the user: query provider-database or do Autoconfig
info!(
ctx,
"checking internal provider-info for offline autoconfig"
);
info!("checking internal provider-info for offline autoconfig");
if let Some(provider) =
provider::get_provider_info(ctx, &param_domain, socks5_enabled).await
{
if let Some(provider) = provider::get_provider_info(&param_domain, socks5_enabled).await {
param.provider = Some(provider);
match provider.status {
provider::Status::Ok | provider::Status::Preparation => {
if provider.server.is_empty() {
info!(ctx, "offline autoconfig found, but no servers defined");
info!("offline autoconfig found, but no servers defined");
param_autoconfig = None;
} else {
info!(ctx, "offline autoconfig found");
info!("offline autoconfig found");
let servers = provider
.server
.iter()
@@ -257,17 +250,17 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
}
}
provider::Status::Broken => {
info!(ctx, "offline autoconfig found, provider is broken");
info!("offline autoconfig found, provider is broken");
param_autoconfig = None;
}
}
} else {
// Try receiving autoconfig
info!(ctx, "no offline autoconfig found");
info!("no offline autoconfig found");
param_autoconfig = if socks5_enabled {
// Currently we can't do http requests through socks5, to not leak
// the ip, just don't do online autoconfig
info!(ctx, "socks5 enabled, skipping autoconfig");
info!("socks5 enabled, skipping autoconfig");
None
} else {
get_autoconfig(ctx, param, &param_domain, &param_addr_urlencoded).await
@@ -465,7 +458,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
progress!(ctx, 920);
e2ee::ensure_secret_key_exists(ctx).await?;
info!(ctx, "key generation completed");
info!("key generation completed");
ctx.set_config_bool(Config::FetchedExistingMsgs, false)
.await?;
@@ -578,13 +571,13 @@ async fn try_imap_one_param(
"None".to_string()
}
);
info!(context, "Trying: {}", inf);
info!("Trying: {}", inf);
let (_s, r) = async_channel::bounded(1);
let mut imap = match Imap::new(param, socks5_config.clone(), addr, provider_strict_tls, r) {
Err(err) => {
info!(context, "failure: {:#}", err);
info!("failure: {:#}", err);
return Err(ConfigurationError {
config: inf,
msg: format!("{err:#}"),
@@ -595,14 +588,14 @@ async fn try_imap_one_param(
match imap.connect(context).await {
Err(err) => {
info!(context, "failure: {:#}", err);
info!("failure: {:#}", err);
Err(ConfigurationError {
config: inf,
msg: format!("{err:#}"),
})
}
Ok(()) => {
info!(context, "success: {}", inf);
info!("success: {}", inf);
Ok(imap)
}
}
@@ -630,19 +623,19 @@ async fn try_smtp_one_param(
"None".to_string()
}
);
info!(context, "Trying: {}", inf);
info!("Trying: {inf}");
if let Err(err) = smtp
.connect(context, param, socks5_config, addr, provider_strict_tls)
.await
{
info!(context, "failure: {}", err);
info!("failure: {err:#}");
Err(ConfigurationError {
config: inf,
msg: format!("{err:#}"),
})
} else {
info!(context, "success: {}", inf);
info!("success: {inf}");
smtp.disconnect().await;
Ok(())
}

View File

@@ -1,10 +1,10 @@
//! # Thunderbird's Autoconfiguration implementation
//!
//! Documentation: <https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration>
use quick_xml::events::{BytesStart, Event};
use std::io::BufRead;
use std::str::FromStr;
use quick_xml::events::{BytesStart, Event};
use tracing::warn;
use super::read_url::read_url;
use super::{Error, ServerParams};
@@ -263,10 +263,7 @@ pub(crate) async fn moz_autoconfigure(
let res = parse_serverparams(&param_in.addr, &xml_raw);
if let Err(err) = &res {
warn!(
context,
"Failed to parse Thunderbird autoconfiguration XML: {}", err
);
warn!("Failed to parse Thunderbird autoconfiguration XML: {err:#}");
}
res
}

View File

@@ -6,6 +6,7 @@
use std::io::BufRead;
use quick_xml::events::Event;
use tracing::warn;
use super::read_url::read_url;
use super::{Error, ServerParams};
@@ -202,7 +203,7 @@ pub(crate) async fn outlk_autodiscover(
let xml_raw = read_url(context, &url).await?;
let res = parse_xml(&xml_raw);
if let Err(err) = &res {
warn!(context, "{}", err);
warn!("{err:#}");
}
match res? {
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,

View File

@@ -1,4 +1,5 @@
use anyhow::{anyhow, format_err};
use tracing::info;
use crate::context::Context;
use crate::socks::Socks5Config;
@@ -6,12 +7,12 @@ use crate::socks::Socks5Config;
pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
match read_url_inner(context, url).await {
Ok(s) => {
info!(context, "Successfully read url {}", url);
info!("Successfully read url {url}");
Ok(s)
}
Err(e) => {
info!(context, "Can't read URL {}: {:#}", url, e);
Err(format_err!("Can't read URL {}: {:#}", url, e))
info!("Can't read URL {url}: {e:#}");
Err(format_err!("Can't read URL {url}: {e:#}"))
}
}
}
@@ -32,7 +33,7 @@ pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result<Stri
.last()
.ok_or_else(|| anyhow!("Redirection doesn't have a target location"))?
.to_str()?;
info!(context, "Following redirect to {}", header);
info!("Following redirect to {header}");
url = header.to_string();
continue;
}

View File

@@ -17,12 +17,16 @@ use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
use tokio::task;
use tokio::time::{timeout, Duration};
use tracing::instrument::WithSubscriber;
use tracing::Instrument;
use tracing::{info, warn};
use crate::aheader::EncryptPreference;
use crate::chat::ChatId;
use crate::color::str_to_color;
use crate::config::Config;
use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY};
use crate::context::future::ContextIdFutureExt;
use crate::context::Context;
use crate::events::EventType;
use crate::key::{DcKey, SignedPublicKey};
@@ -546,7 +550,7 @@ impl Contact {
// Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
|| (addr.len() > 50 && addr.contains('+'))
{
info!(context, "hiding contact {}", addr);
info!("hiding contact {addr}");
origin = Origin::Hidden;
// For these kind of email addresses, sender and address often don't belong together
// (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
@@ -699,7 +703,7 @@ impl Contact {
sth_modified = Modifier::Created;
row_id = u32::try_from(transaction.last_insert_rowid())?;
info!(context, "added contact id={} addr={}", row_id, &addr);
info!("added contact id={} addr={}", row_id, &addr);
}
Ok(row_id)
}).await?;
@@ -739,15 +743,12 @@ impl Contact {
}
}
Err(err) => {
warn!(
context,
"Failed to add address {} from address book: {}", addr, err
);
warn!("Failed to add address {addr} from address book: {err:#}");
}
}
}
Err(err) => {
warn!(context, "{:#}.", err);
warn!("{err:#}.");
}
}
}
@@ -1055,7 +1056,7 @@ impl Contact {
}
/// Updates `param` column in the database.
pub async fn update_param(&self, context: &Context) -> Result<()> {
pub(crate) async fn update_param(&self, context: &Context) -> Result<()> {
context
.sql
.execute(
@@ -1067,7 +1068,7 @@ impl Contact {
}
/// Updates `status` column in the database.
pub async fn update_status(&self, context: &Context) -> Result<()> {
pub(crate) async fn update_status(&self, context: &Context) -> Result<()> {
context
.sql
.execute(
@@ -1388,7 +1389,7 @@ pub(crate) async fn set_profile_image(
.set_config(Config::Selfavatar, Some(profile_image))
.await?;
} else {
info!(context, "Do not use unencrypted selfavatar.");
info!("Do not use unencrypted selfavatar.");
}
} else {
contact.param.set(Param::ProfileImage, profile_image);
@@ -1400,7 +1401,7 @@ pub(crate) async fn set_profile_image(
if was_encrypted {
context.set_config(Config::Selfavatar, None).await?;
} else {
info!(context, "Do not use unencrypted selfavatar deletion.");
info!("Do not use unencrypted selfavatar deletion.");
}
} else {
contact.param.remove(Param::ProfileImage);
@@ -1555,7 +1556,12 @@ impl RecentlySeenLoop {
pub(crate) fn new(context: Context) -> Self {
let (interrupt_send, interrupt_recv) = channel::bounded(1);
let handle = task::spawn(Self::run(context, interrupt_recv));
let handle = task::spawn(
Self::run(context, interrupt_recv)
.with_current_subscriber()
.bind_current_context_id()
.in_current_span(),
);
Self {
handle,
interrupt_send,
@@ -1607,7 +1613,6 @@ impl RecentlySeenLoop {
if let Ok(duration) = until.duration_since(now) {
info!(
context,
"Recently seen loop waiting for {} or interrupt",
duration_to_str(duration)
);
@@ -1621,10 +1626,7 @@ impl RecentlySeenLoop {
}
}
Ok(Err(err)) => {
warn!(
context,
"Error receiving an interruption in recently seen loop: {}", err
);
warn!("Error receiving an interruption in recently seen loop: {err:#}.");
// Maybe the sender side is closed.
// Terminate the loop to avoid looping indefinitely.
return;
@@ -1638,10 +1640,7 @@ impl RecentlySeenLoop {
}
}
} else {
info!(
context,
"Recently seen loop is not waiting, event is already due."
);
info!("Recently seen loop is not waiting, event is already due.");
// Event is already in the past.
if let Some(contact_id) = contact_id {

View File

@@ -13,13 +13,14 @@ use async_channel::{self as channel, Receiver, Sender};
use ratelimit::Ratelimit;
use tokio::sync::{Mutex, RwLock};
use tokio::task;
use tracing::{info, warn};
use crate::chat::{get_chat_cnt, ChatId};
use crate::config::Config;
use crate::constants::DC_VERSION_STR;
use crate::contact::Contact;
use crate::debug_logging::DebugEventLogData;
use crate::events::{Event, EventEmitter, EventType, Events};
use crate::events::{Event, EventEmitter, EventLayer, EventType, Events};
use crate::key::{DcKey, SignedPublicKey};
use crate::login_param::LoginParam;
use crate::message::{self, MessageState, MsgId};
@@ -30,6 +31,8 @@ use crate::stock_str::StockStrings;
use crate::timesmearing::SmearedTimestamp;
use crate::tools::{duration_to_str, time};
pub mod future;
/// Builder for the [`Context`].
///
/// Many arguments to the [`Context`] are kind of optional and only needed to handle
@@ -233,12 +236,6 @@ pub struct InnerContext {
creation_time: SystemTime,
/// The text of the last error logged and emitted as an event.
/// If the ui wants to display an error after a failure,
/// `last_error` should be used to avoid races with the event thread.
pub(crate) last_error: std::sync::RwLock<String>,
/// If debug logging is enabled, this contains all necessary information
pub(crate) debug_logging: RwLock<Option<DebugLogging>>,
}
@@ -296,7 +293,7 @@ impl Context {
events: Events,
stock_strings: StockStrings,
) -> Result<Context> {
let context = Self::new_closed(dbfile, id, events, stock_strings).await?;
let context = Self::new_closed(dbfile, id, events.clone(), stock_strings).await?;
// Open the database if is not encrypted.
if context.check_passphrase("".to_string()).await? {
@@ -305,6 +302,11 @@ impl Context {
Ok(context)
}
/// Returns `tracing` subscriber layer.
pub fn to_layer(&self) -> EventLayer {
self.events.to_layer()
}
/// Creates new context without opening the database.
pub async fn new_closed(
dbfile: &Path,
@@ -382,7 +384,6 @@ impl Context {
server_id: RwLock::new(None),
creation_time: std::time::SystemTime::now(),
last_full_folder_scan: Mutex::new(None),
last_error: std::sync::RwLock::new("".to_string()),
debug_logging: RwLock::new(None),
};
@@ -396,7 +397,7 @@ impl Context {
/// Starts the IO scheduler.
pub async fn start_io(&self) {
if let Ok(false) = self.is_configured().await {
warn!(self, "can not start io on a context that is not configured");
warn!("can not start io on a context that is not configured");
return;
}
self.scheduler.start(self.clone()).await;
@@ -539,13 +540,13 @@ impl Context {
match &*s {
RunningState::Running { cancel_sender } => {
if let Err(err) = cancel_sender.send(()).await {
warn!(self, "could not cancel ongoing: {:#}", err);
warn!("could not cancel ongoing: {:#}", err);
}
info!(self, "Signaling the ongoing process to stop ASAP.",);
info!("Signaling the ongoing process to stop ASAP.",);
*s = RunningState::ShallStop;
}
RunningState::ShallStop | RunningState::Stopped => {
info!(self, "No ongoing process to stop.",);
info!("No ongoing process to stop.",);
}
}
}

83
src/context/future.rs Normal file
View File

@@ -0,0 +1,83 @@
//! Futures extensions to track current context ID.
use pin_project_lite::pin_project;
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::thread_local;
thread_local! {
static THREAD_CONTEXT_ID: RefCell<u32> = RefCell::new(0);
}
pub(crate) struct ContextIdGuard {
previous: u32,
}
pub(crate) fn current_context_id() -> u32 {
THREAD_CONTEXT_ID.with(|context_id| *context_id.borrow())
}
impl ContextIdGuard {
fn new(context_id: u32) -> Self {
let previous = THREAD_CONTEXT_ID.with(|prev_context_id| {
let ret = *prev_context_id.borrow();
*prev_context_id.borrow_mut() = context_id;
ret
});
Self { previous }
}
}
impl Drop for ContextIdGuard {
fn drop(&mut self) {
THREAD_CONTEXT_ID.with(|context_id| {
*context_id.borrow_mut() = self.previous;
})
}
}
pin_project! {
/// A future with attached context ID.
#[derive(Debug, Clone)]
pub struct ContextIdFuture<F> {
context_id: u32,
#[pin]
future: F,
}
}
impl<F> ContextIdFuture<F> {
/// Wraps a future.
pub fn new(context_id: u32, future: F) -> Self {
Self { context_id, future }
}
}
impl<F: Future> Future for ContextIdFuture<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let context_id = self.context_id;
let this = self.project();
let _guard = ContextIdGuard::new(context_id);
this.future.poll(cx)
}
}
/// Future extension to bind context ID.
pub trait ContextIdFutureExt: Sized {
/// Binds context ID to the future.
fn bind_context_id(self, context_id: u32) -> ContextIdFuture<Self> {
ContextIdFuture::new(context_id, self)
}
/// Binds current context ID to the future.
fn bind_current_context_id(self) -> ContextIdFuture<Self> {
self.bind_context_id(current_context_id())
}
}
impl<F> ContextIdFutureExt for F where F: Future {}

View File

@@ -12,6 +12,7 @@ use async_channel::{self as channel, Receiver};
use serde_json::json;
use std::path::PathBuf;
use tokio::task;
use tracing::info;
/// Store all information needed to log an event to a webxdc.
pub struct DebugEventLogData {
@@ -132,7 +133,7 @@ pub(crate) async fn set_debug_logging_xdc(ctx: &Context, id: Option<MsgId>) -> a
});
}
}
info!(ctx, "replacing logging webxdc");
info!("replacing logging webxdc");
}
// Delete current debug logging
None => {
@@ -140,7 +141,7 @@ pub(crate) async fn set_debug_logging_xdc(ctx: &Context, id: Option<MsgId>) -> a
.set_raw_config(Config::DebugLogging.as_ref(), None)
.await?;
*ctx.debug_logging.write().await = None;
info!(ctx, "removing logging webxdc");
info!("removing logging webxdc");
}
}
Ok(())

View File

@@ -5,6 +5,7 @@ use std::str::FromStr;
use anyhow::Result;
use mailparse::ParsedMail;
use tracing::{info, warn};
use crate::aheader::Aheader;
use crate::authres::handle_authres;
@@ -24,7 +25,6 @@ use crate::pgp;
///
/// If the message is wrongly signed, HashSet will be empty.
pub fn try_decrypt(
context: &Context,
mail: &ParsedMail<'_>,
private_keyring: &Keyring<SignedSecretKey>,
public_keyring_for_validate: &Keyring<SignedPublicKey>,
@@ -36,7 +36,7 @@ pub fn try_decrypt(
None => return Ok(None),
Some(res) => res,
};
info!(context, "Detected Autocrypt-mime message");
info!("Detected Autocrypt-mime message");
decrypt_part(
encrypted_data_part,
@@ -54,7 +54,6 @@ pub(crate) async fn prepare_decryption(
if mail.headers.get_header(HeaderDef::ListPost).is_some() {
if mail.headers.get_header(HeaderDef::Autocrypt).is_some() {
info!(
context,
"Ignoring autocrypt header since this is a mailing list message. \
NOTE: For privacy reasons, the mailing list software should remove Autocrypt headers."
);
@@ -78,13 +77,13 @@ pub(crate) async fn prepare_decryption(
Ok(header) if addr_cmp(&header.addr, from) => Some(header),
Ok(header) => {
warn!(
context,
"Autocrypt header address {:?} is not {:?}.", header.addr, from
"Autocrypt header address {:?} is not {:?}.",
header.addr, from
);
None
}
Err(err) => {
warn!(context, "Failed to parse Autocrypt header: {:#}.", err);
warn!("Failed to parse Autocrypt header: {:#}.", err);
None
}
}
@@ -319,8 +318,8 @@ pub(crate) async fn get_autocrypt_peerstate(
peerstate.save_to_db(&context.sql).await?;
} else {
info!(
context,
"Refusing to update existing peerstate of {}", &peerstate.addr
"Refusing to update existing peerstate of {}",
&peerstate.addr
);
}
}

View File

@@ -6,6 +6,7 @@ use std::collections::BTreeMap;
use anyhow::{anyhow, Result};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
use crate::config::Config;
use crate::context::Context;
@@ -124,7 +125,7 @@ impl Job {
/// Called in response to `Action::DownloadMsg`.
pub(crate) async fn download_msg(&self, context: &Context, imap: &mut Imap) -> Status {
if let Err(err) = imap.prepare(context).await {
warn!(context, "download: could not connect: {:#}", err);
warn!("download: could not connect: {:#}", err);
return Status::RetryNow;
}
@@ -195,7 +196,7 @@ impl Imap {
}
// we are connected, and the folder is selected
info!(context, "Downloading message {}/{} fully...", folder, uid);
info!("Downloading message {}/{} fully...", folder, uid);
let mut uid_message_ids: BTreeMap<u32, String> = BTreeMap::new();
uid_message_ids.insert(uid, rfc724_mid);
@@ -240,7 +241,7 @@ impl MimeMessage {
text += format!(" [{until}]").as_str();
};
info!(context, "Partial download: {}", text);
info!("Partial download: {}", text);
self.parts.push(Part {
typ: Viewtype::Text,

View File

@@ -2,6 +2,7 @@
use anyhow::{format_err, Context as _, Result};
use num_traits::FromPrimitive;
use tracing::info;
use crate::aheader::{Aheader, EncryptPreference};
use crate::config::Config;
@@ -51,7 +52,6 @@ impl EncryptHelper {
/// Returns an error if `e2ee_guaranteed` is true, but one or more keys are missing.
pub fn should_encrypt(
&self,
context: &Context,
e2ee_guaranteed: bool,
peerstates: &[(Option<Peerstate>, &str)],
) -> Result<bool> {
@@ -63,10 +63,7 @@ impl EncryptHelper {
for (peerstate, addr) in peerstates {
match peerstate {
Some(peerstate) => {
info!(
context,
"peerstate for {:?} is {}", addr, peerstate.prefer_encrypt
);
info!("peerstate for {:?} is {}", addr, peerstate.prefer_encrypt);
match peerstate.prefer_encrypt {
EncryptPreference::NoPreference => {}
EncryptPreference::Mutual => prefer_encrypt_count += 1,
@@ -82,7 +79,7 @@ impl EncryptHelper {
if e2ee_guaranteed {
return Err(format_err!("{}", msg));
} else {
info!(context, "{}", msg);
info!("{}", msg);
return Ok(false);
}
}
@@ -322,22 +319,22 @@ Sent with my Delta Chat Messenger: https://delta.chat";
// test with EncryptPreference::NoPreference:
// if e2ee_eguaranteed is unset, there is no encryption as not more than half of peers want encryption
let ps = new_peerstates(EncryptPreference::NoPreference);
assert!(encrypt_helper.should_encrypt(&t, true, &ps).unwrap());
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).unwrap());
assert!(encrypt_helper.should_encrypt(true, &ps).unwrap());
assert!(!encrypt_helper.should_encrypt(false, &ps).unwrap());
// test with EncryptPreference::Reset
let ps = new_peerstates(EncryptPreference::Reset);
assert!(encrypt_helper.should_encrypt(&t, true, &ps).unwrap());
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).unwrap());
assert!(encrypt_helper.should_encrypt(true, &ps).unwrap());
assert!(!encrypt_helper.should_encrypt(false, &ps).unwrap());
// test with EncryptPreference::Mutual (self is also Mutual)
let ps = new_peerstates(EncryptPreference::Mutual);
assert!(encrypt_helper.should_encrypt(&t, true, &ps).unwrap());
assert!(encrypt_helper.should_encrypt(&t, false, &ps).unwrap());
assert!(encrypt_helper.should_encrypt(true, &ps).unwrap());
assert!(encrypt_helper.should_encrypt(false, &ps).unwrap());
// test with missing peerstate
let ps = vec![(None, "bob@foo.bar")];
assert!(encrypt_helper.should_encrypt(&t, true, &ps).is_err());
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).unwrap());
assert!(encrypt_helper.should_encrypt(true, &ps).is_err());
assert!(!encrypt_helper.should_encrypt(false, &ps).unwrap());
}
}

View File

@@ -72,6 +72,7 @@ use anyhow::{ensure, Result};
use async_channel::Receiver;
use serde::{Deserialize, Serialize};
use tokio::time::timeout;
use tracing::{error, info, warn};
use crate::chat::{send_msg, ChatId};
use crate::constants::{DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH};
@@ -225,8 +226,8 @@ impl ChatId {
msg.param.set_cmd(SystemMessage::EphemeralTimerChanged);
if let Err(err) = send_msg(context, self, &mut msg).await {
error!(
context,
"Failed to send a message about ephemeral message timer change: {:?}", err
"Failed to send a message about ephemeral message timer change: {:?}",
err
);
}
}
@@ -433,7 +434,7 @@ pub(crate) async fn delete_expired_messages(context: &Context, now: i64) -> Resu
let rows = select_expired_messages(context, now).await?;
if !rows.is_empty() {
info!(context, "Attempting to delete {} messages.", rows.len());
info!("Attempting to delete {} messages.", rows.len());
let (msgs_changed, webxdc_deleted) = context
.sql
@@ -523,7 +524,7 @@ async fn next_expiration_timestamp(context: &Context) -> Option<i64> {
.await
{
Err(err) => {
warn!(context, "Can't calculate next ephemeral timeout: {}", err);
warn!("Can't calculate next ephemeral timeout: {}", err);
None
}
Ok(ephemeral_timestamp) => ephemeral_timestamp,
@@ -533,8 +534,8 @@ async fn next_expiration_timestamp(context: &Context) -> Option<i64> {
match next_delete_device_after_timestamp(context).await {
Err(err) => {
warn!(
context,
"Can't calculate timestamp of the next message expiration: {}", err
"Can't calculate timestamp of the next message expiration: {}",
err
);
None
}
@@ -563,7 +564,6 @@ pub(crate) async fn ephemeral_loop(context: &Context, interrupt_receiver: Receiv
if let Ok(duration) = until.duration_since(now) {
info!(
context,
"Ephemeral loop waiting for deletion in {} or interrupt",
duration_to_str(duration)
);

View File

@@ -1,10 +1,17 @@
#![allow(missing_docs)]
//! # Events specification.
use std::fmt::Write;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use async_channel::{self as channel, Receiver, Sender, TrySendError};
use serde::Serialize;
use tracing::Level;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Layer;
use crate::chat::ChatId;
use crate::contact::ContactId;
use crate::ephemeral::Timer as EphemeralTimer;
@@ -16,6 +23,11 @@ use crate::webxdc::StatusUpdateSerial;
pub struct Events {
receiver: Receiver<Event>,
sender: Sender<Event>,
/// The text of the last error logged and emitted as an event.
/// If the ui wants to display an error after a failure,
/// `last_error` should be used to avoid races with the event thread.
pub(crate) last_error: Arc<RwLock<String>>,
}
impl Default for Events {
@@ -29,11 +41,26 @@ impl Events {
pub fn new() -> Self {
let (sender, receiver) = channel::bounded(1_000);
Self { receiver, sender }
Self {
receiver,
sender,
last_error: Arc::new(RwLock::new("".to_string())),
}
}
/// Emits an event.
/// Set last error string.
/// Implemented as blocking as used from macros in different, not always async blocks.
pub fn set_last_error(&self, error: &str) {
let error = error.to_string();
let mut last_error = self.last_error.write().unwrap();
*last_error = error;
}
/// Emits an event into event channel.
pub fn emit(&self, event: Event) {
if let EventType::Error(formatted) = &event.typ {
self.set_last_error(formatted);
}
match self.sender.try_send(event) {
Ok(()) => {}
Err(TrySendError::Full(event)) => {
@@ -53,6 +80,89 @@ impl Events {
pub fn get_emitter(&self) -> EventEmitter {
EventEmitter(self.receiver.clone())
}
/// Returns `tracing` subscriber layer.
pub fn to_layer(&self) -> EventLayer {
EventLayer::new(self.sender.clone(), self.last_error.clone())
}
}
#[derive(Debug)]
struct MessageStorage<'a>(&'a mut String);
impl tracing::field::Visit for MessageStorage<'_> {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
write!(self.0, "{value:?}").ok();
}
}
}
/// Tracing subscriber layer converting logs into Delta Chat events.
#[derive(Debug)]
pub struct EventLayer {
/// Event channel for event submission.
sender: Sender<Event>,
last_error: Arc<RwLock<String>>,
}
impl EventLayer {
pub(crate) fn new(sender: Sender<Event>, last_error: Arc<RwLock<String>>) -> Self {
Self { sender, last_error }
}
fn set_last_error(&self, error: &str) {
let error = error.to_string();
let mut last_error = self.last_error.write().unwrap();
*last_error = error;
}
}
impl<S> Layer<S> for EventLayer
where
S: tracing::Subscriber + for<'a> LookupSpan<'a>,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let context_id = crate::context::future::current_context_id();
let &level = event.metadata().level();
let mut message = "".to_string();
let mut visitor = MessageStorage(&mut message);
event.record(&mut visitor);
match level {
Level::ERROR => {
self.set_last_error(&message);
self.sender
.try_send(Event {
id: context_id,
typ: EventType::Error(message),
})
.ok();
}
Level::WARN => {
self.sender
.try_send(Event {
id: context_id,
typ: EventType::Warning(message),
})
.ok();
}
Level::INFO => {
self.sender
.try_send(Event {
id: context_id,
typ: EventType::Info(message),
})
.ok();
}
Level::TRACE | Level::DEBUG => {}
}
}
}
/// A receiver of events from a [`Context`].
@@ -133,6 +243,9 @@ pub enum EventType {
/// Emitted when an IMAP message has been moved
ImapMessageMoved(String),
/// Emitted before going into IDLE on the Inbox folder.
ImapInboxIdle,
/// Emitted when an new file in the $BLOBDIR was created
NewBlobFile(String),

View File

@@ -16,6 +16,7 @@ use futures::future::FutureExt;
use lettre_email::mime::{self, Mime};
use lettre_email::PartBuilder;
use mailparse::ParsedContentType;
use tracing::warn;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::message::{Message, MsgId};
@@ -87,7 +88,7 @@ impl HtmlMsgParser {
/// Function takes a raw mime-message string,
/// searches for the main-text part
/// and returns that as parser.html
pub async fn from_bytes(context: &Context, rawmime: &[u8]) -> Result<Self> {
pub async fn from_bytes(rawmime: &[u8]) -> Result<Self> {
let mut parser = HtmlMsgParser {
html: "".to_string(),
plain: None,
@@ -102,7 +103,7 @@ impl HtmlMsgParser {
parser.html = plain.to_html();
}
} else {
parser.cid_to_data_recursive(context, &parsedmail).await?;
parser.cid_to_data_recursive(&parsedmail).await?;
}
Ok(parser)
@@ -173,7 +174,6 @@ impl HtmlMsgParser {
/// This allows the final html-file to be self-contained.
fn cid_to_data_recursive<'a>(
&'a mut self,
context: &'a Context,
mail: &'a mailparse::ParsedMail<'a>,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
// Boxed future to deal with recursion
@@ -181,7 +181,7 @@ impl HtmlMsgParser {
match get_mime_multipart_type(&mail.ctype) {
MimeMultipartType::Multiple => {
for cur_data in &mail.subparts {
self.cid_to_data_recursive(context, cur_data).await?;
self.cid_to_data_recursive(cur_data).await?;
}
Ok(())
}
@@ -191,7 +191,7 @@ impl HtmlMsgParser {
return Ok(());
}
let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
self.cid_to_data_recursive(context, &mail).await
self.cid_to_data_recursive(&mail).await
}
MimeMultipartType::Single => {
let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
@@ -214,10 +214,8 @@ impl HtmlMsgParser {
.to_string()
}
Err(e) => warn!(
context,
"Cannot create regex for cid: {} throws {}",
re_string,
e
re_string, e
),
}
}
@@ -249,15 +247,15 @@ impl MsgId {
let rawmime = message::get_mime_headers(context, self).await?;
if !rawmime.is_empty() {
match HtmlMsgParser::from_bytes(context, &rawmime).await {
match HtmlMsgParser::from_bytes(&rawmime).await {
Err(err) => {
warn!(context, "get_html: parser error: {:#}", err);
warn!("get_html: parser error: {:#}", err);
Ok(None)
}
Ok(parser) => Ok(Some(parser.html)),
}
} else {
warn!(context, "get_html: no mime for {}", self);
warn!("get_html: no mime for {}", self);
Ok(None)
}
}
@@ -286,9 +284,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_plain_unspecified() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_plain_unspecified.eml");
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
assert_eq!(
parser.html,
r##"<!DOCTYPE html>
@@ -305,9 +302,8 @@ This message does not have Content-Type nor Subject.<br/>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_plain_iso88591() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_plain_iso88591.eml");
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
assert_eq!(
parser.html,
r##"<!DOCTYPE html>
@@ -324,9 +320,8 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_plain_flowed() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_plain_flowed.eml");
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
assert!(parser.plain.unwrap().flowed);
assert_eq!(
parser.html,
@@ -347,9 +342,8 @@ and will be wrapped as usual.<br/>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_alt_plain() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_alt_plain.eml");
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
assert_eq!(
parser.html,
r##"<!DOCTYPE html>
@@ -369,9 +363,8 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_html() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_html.eml");
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
// on windows, `\r\n` linends are returned from mimeparser,
// however, rust multiline-strings use just `\n`;
@@ -387,9 +380,8 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_alt_html() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_alt_html.eml");
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
assert_eq!(
parser.html.replace('\r', ""), // see comment in test_htmlparse_html()
r##"<html>
@@ -402,9 +394,8 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_htmlparse_alt_plain_html() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
assert_eq!(
parser.html.replace('\r', ""), // see comment in test_htmlparse_html()
r##"<html>
@@ -421,7 +412,6 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
async fn test_htmlparse_apple_cid_jpg() {
// load raw mime html-data with related image-part (cid:)
// and make sure, Content-Id has angle-brackets that are removed correctly.
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/apple_cid_jpg.eml");
let test = String::from_utf8_lossy(raw);
assert!(test.contains("Content-Id: <8AE052EF-BC90-486F-BB78-58D3590308EC@fritz.box>"));
@@ -429,7 +419,7 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
assert!(test.find("data:").is_none());
// parsing converts cid: to data:
let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
let parser = HtmlMsgParser::from_bytes(raw).await.unwrap();
assert!(parser.html.contains("<html>"));
assert!(!parser.html.contains("Content-Id:"));
assert!(parser.html.contains("data:image/jpeg;base64,/9j/4AAQ"));

View File

@@ -16,6 +16,7 @@ use async_channel::Receiver;
use async_imap::types::{Fetch, Flag, Name, NameAttribute, UnsolicitedResponse};
use futures::StreamExt;
use num_traits::FromPrimitive;
use tracing::{error, info, warn};
use crate::chat::{self, ChatId, ChatIdBlocked};
use crate::config::Config;
@@ -306,7 +307,7 @@ impl Imap {
let oauth2 = self.config.lp.oauth2;
info!(context, "Connecting to IMAP server");
info!("Connecting to IMAP server");
let connection_res: Result<Client> = if self.config.lp.security == Socket::Starttls
|| self.config.lp.security == Socket::Plain
{
@@ -363,7 +364,7 @@ impl Imap {
let imap_pw: &str = config.lp.password.as_ref();
let login_res = if oauth2 {
info!(context, "Logging into IMAP server with OAuth 2");
info!("Logging into IMAP server with OAuth 2");
let addr: &str = config.addr.as_ref();
let token = get_oauth2_access_token(context, addr, imap_pw, true)
@@ -375,7 +376,7 @@ impl Imap {
};
client.authenticate("XOAUTH2", auth).await
} else {
info!(context, "Logging into IMAP server with LOGIN");
info!("Logging into IMAP server with LOGIN");
client.login(imap_user, imap_pw).await
};
@@ -391,7 +392,7 @@ impl Imap {
"IMAP-LOGIN as {}",
self.config.lp.user
)));
info!(context, "Successfully logged into IMAP server");
info!("Successfully logged into IMAP server");
Ok(())
}
@@ -399,7 +400,7 @@ impl Imap {
let imap_user = self.config.lp.user.to_owned();
let message = stock_str::cannot_login(context, &imap_user).await;
warn!(context, "{} ({:#})", message, err);
warn!("{} ({:#})", message, err);
let lock = context.wrong_pw_warning_mutex.lock().await;
if self.login_failed_once
@@ -407,7 +408,7 @@ impl Imap {
&& context.get_config_bool(Config::NotifyAboutWrongPw).await?
{
if let Err(e) = context.set_config(Config::NotifyAboutWrongPw, None).await {
warn!(context, "{:#}", e);
warn!("{:#}", e);
}
drop(lock);
@@ -417,7 +418,7 @@ impl Imap {
chat::add_device_msg_with_importance(context, None, Some(&mut msg), true)
.await
{
warn!(context, "{:#}", e);
warn!("{:#}", e);
}
} else {
self.login_failed_once = true;
@@ -445,8 +446,8 @@ impl Imap {
/// Drops the session without disconnecting properly.
/// Useful in case of an IMAP error, when it's unclear if it's in a correct state and it's
/// easier to setup a new connection.
pub fn trigger_reconnect(&mut self, context: &Context) {
info!(context, "Dropping an IMAP connection.");
pub fn trigger_reconnect(&mut self) {
info!("Dropping an IMAP connection.");
self.session = None;
}
@@ -510,7 +511,7 @@ impl Imap {
.as_mut()
.context("IMAP No connection established")?;
session.select_folder(context, Some(folder)).await?;
session.select_folder(Some(folder)).await?;
let mut list = session
.uid_fetch("1:*", RFC724MID_UID)
@@ -521,7 +522,7 @@ impl Imap {
let headers = match get_fetch_headers(&fetch) {
Ok(headers) => headers,
Err(err) => {
warn!(context, "Failed to parse FETCH headers: {}", err);
warn!("Failed to parse FETCH headers: {}", err);
continue;
}
};
@@ -539,7 +540,6 @@ impl Imap {
}
info!(
context,
"Resync: collected {} message IDs in folder {}",
msgs.len(),
folder,
@@ -582,7 +582,7 @@ impl Imap {
) -> Result<bool> {
let session = self.session.as_mut().context("no session")?;
let newly_selected = session
.select_or_create_folder(context, folder)
.select_or_create_folder(folder)
.await
.with_context(|| format!("failed to select or create folder {folder}"))?;
let mailbox = session
@@ -611,7 +611,6 @@ impl Imap {
} else if let Some(uid_next) = mailbox.uid_next {
if uid_next < old_uid_next {
warn!(
context,
"The server illegally decreased the uid_next of folder {} from {} to {} without changing validity ({}), resyncing UIDs...",
folder, old_uid_next, uid_next, new_uid_validity,
);
@@ -629,7 +628,7 @@ impl Imap {
set_modseq(context, folder, 0).await?;
if mailbox.exists == 0 {
info!(context, "Folder \"{}\" is empty.", folder);
info!("Folder \"{}\" is empty.", folder);
// set uid_next=1 for empty folders.
// If we do not do this here, we'll miss the first message
@@ -645,10 +644,7 @@ impl Imap {
let new_uid_next = match mailbox.uid_next {
Some(uid_next) => uid_next,
None => {
warn!(
context,
"IMAP folder has no uid_next, fall back to fetching"
);
warn!("IMAP folder has no uid_next, fall back to fetching");
// note that we use fetch by sequence number
// and thus we only need to get exactly the
// last-index message.
@@ -685,13 +681,8 @@ impl Imap {
job::schedule_resync(context).await?;
}
info!(
context,
"uid/validity change folder {}: new {}/{} previous {}/{}",
folder,
new_uid_next,
new_uid_validity,
old_uid_next,
old_uid_validity,
folder, new_uid_next, new_uid_validity, old_uid_next, old_uid_validity,
);
Ok(false)
}
@@ -707,7 +698,7 @@ impl Imap {
fetch_existing_msgs: bool,
) -> Result<bool> {
if should_ignore_folder(context, folder, folder_meaning).await? {
info!(context, "Not fetching from {}", folder);
info!("Not fetching from {}", folder);
return Ok(false);
}
@@ -717,7 +708,7 @@ impl Imap {
.with_context(|| format!("failed to select folder {folder}"))?;
if !new_emails && !fetch_existing_msgs {
info!(context, "No new emails in folder {}", folder);
info!("No new emails in folder {}", folder);
return Ok(false);
}
@@ -743,7 +734,7 @@ impl Imap {
let headers = match get_fetch_headers(fetch_response) {
Ok(headers) => headers,
Err(err) => {
warn!(context, "Failed to parse FETCH headers: {}", err);
warn!("Failed to parse FETCH headers: {}", err);
continue;
}
};
@@ -843,7 +834,7 @@ impl Imap {
set_uid_next(context, folder, new_uid_next).await?;
}
info!(context, "{} mails read from \"{}\".", read_cnt, folder);
info!("{} mails read from \"{}\".", read_cnt, folder);
let msg_ids: Vec<MsgId> = received_msgs
.iter()
@@ -890,10 +881,7 @@ impl Imap {
None => continue,
};
if let Some(folder) = context.get_config(config).await? {
info!(
context,
"Fetching existing messages from folder \"{}\"", folder
);
info!("Fetching existing messages from folder \"{}\"", folder);
self.fetch_new_messages(context, &folder, meaning, true)
.await
.context("could not fetch existing messages")?;
@@ -901,7 +889,7 @@ impl Imap {
}
}
info!(context, "Done fetching existing messages.");
info!("Done fetching existing messages.");
Ok(())
}
@@ -986,21 +974,15 @@ impl Session {
Err(err) => {
if context.should_delete_to_trash().await? {
error!(
context,
"Cannot move messages {} to {}, no fallback to COPY/DELETE because \
delete_to_trash is set. Error: {:#}",
set,
target,
err,
set, target, err,
);
return Err(err.into());
}
warn!(
context,
"Cannot move messages, fallback to COPY/DELETE {} to {}: {}",
set,
target,
err
set, target, err
);
}
}
@@ -1011,14 +993,14 @@ impl Session {
let copy = !context.is_trash(target).await?;
if copy {
info!(
context,
"Server does not support MOVE, fallback to COPY/DELETE {} to {}", set, target
"Server does not support MOVE, fallback to COPY/DELETE {} to {}",
set, target
);
self.uid_copy(&set, &target).await?;
} else {
error!(
context,
"Server does not support MOVE, fallback to DELETE {} to {}", set, target,
"Server does not support MOVE, fallback to DELETE {} to {}",
set, target,
);
}
context
@@ -1067,7 +1049,7 @@ impl Session {
// MOVE/DELETE operations. This does not result in multiple SELECT commands
// being sent because `select_folder()` does nothing if the folder is already
// selected.
self.select_folder(context, Some(folder)).await?;
self.select_folder(Some(folder)).await?;
// Empty target folder name means messages should be deleted.
if target.is_empty() {
@@ -1088,8 +1070,8 @@ impl Session {
// Expunge folder if needed, e.g. if some jobs have
// deleted messages on the server.
if let Err(err) = self.maybe_close_folder(context).await {
warn!(context, "failed to close folder: {:?}", err);
if let Err(err) = self.maybe_close_folder().await {
warn!("failed to close folder: {:?}", err);
}
Ok(())
@@ -1115,23 +1097,17 @@ impl Session {
.await?;
for (folder, rowid_set, uid_set) in UidGrouper::from(rows) {
self.select_folder(context, Some(&folder))
self.select_folder(Some(&folder))
.await
.context("failed to select folder")?;
if let Err(err) = self.add_flag_finalized_with_set(&uid_set, "\\Seen").await {
warn!(
context,
"Cannot mark messages {} in folder {} as seen, will retry later: {}.",
uid_set,
folder,
err
uid_set, folder, err
);
} else {
info!(
context,
"Marked messages {} in folder {} as seen.", uid_set, folder
);
info!("Marked messages {} in folder {} as seen.", uid_set, folder);
context
.sql
.execute(
@@ -1159,15 +1135,12 @@ impl Imap {
.with_context(|| format!("No IMAP connection established, folder: {folder}"))?;
if !session.can_condstore() {
info!(
context,
"Server does not support CONDSTORE, skipping flag synchronization."
);
info!("Server does not support CONDSTORE, skipping flag synchronization.");
return Ok(());
}
session
.select_folder(context, Some(folder))
.select_folder(Some(folder))
.await
.context("failed to select folder")?;
@@ -1180,8 +1153,8 @@ impl Imap {
// We are not interested in actual value of HIGHESTMODSEQ.
if mailbox.highest_modseq.is_none() {
info!(
context,
"Mailbox {} does not support mod-sequences, skipping flag synchronization.", folder
"Mailbox {} does not support mod-sequences, skipping flag synchronization.",
folder
);
return Ok(());
}
@@ -1203,7 +1176,7 @@ impl Imap {
let uid = if let Some(uid) = fetch.uid {
uid
} else {
info!(context, "FETCH result contains no UID, skipping");
info!("FETCH result contains no UID, skipping");
continue;
};
let is_seen = fetch.flags().any(|flag| flag == Flag::Seen);
@@ -1223,7 +1196,7 @@ impl Imap {
highest_modseq = modseq;
}
} else {
warn!(context, "FETCH result contains no MODSEQ");
warn!("FETCH result contains no MODSEQ");
}
}
@@ -1269,7 +1242,7 @@ impl Imap {
}
}
Err(err) => {
warn!(context, "{}", err);
warn!("{}", err);
continue;
}
};
@@ -1366,7 +1339,6 @@ impl Imap {
let session = self.session.as_mut().context("no IMAP session")?;
for (request_uids, set) in build_sequence_sets(&request_uids)? {
info!(
context,
"Starting a {} FETCH of message set \"{}\".",
if fetch_partially { "partial" } else { "full" },
set
@@ -1407,7 +1379,7 @@ impl Imap {
let next_fetch_response = match next_fetch_response {
Ok(next_fetch_response) => next_fetch_response,
Err(err) => {
warn!(context, "Failed to process IMAP FETCH result: {}.", err);
warn!("Failed to process IMAP FETCH result: {}.", err);
continue;
}
};
@@ -1423,24 +1395,21 @@ impl Imap {
// another client changes \Seen flag on a message after we do a prefetch but
// before fetch. It's not an error if we receive such unsolicited response.
info!(
context,
"Skipping not requested FETCH response for UID {}.", next_uid
"Skipping not requested FETCH response for UID {}.",
next_uid
);
} else if uid_msgs.insert(next_uid, next_fetch_response).is_some() {
warn!(context, "Got duplicated UID {}.", next_uid);
warn!("Got duplicated UID {}.", next_uid);
}
} else {
info!(context, "Skipping FETCH response without UID.");
info!("Skipping FETCH response without UID.");
}
}
let fetch_response = match fetch_response {
Some(fetch) => fetch,
None => {
warn!(
context,
"Missed UID {} in the server response.", request_uid
);
warn!("Missed UID {} in the server response.", request_uid);
continue;
}
};
@@ -1454,7 +1423,7 @@ impl Imap {
};
if is_deleted {
info!(context, "Not processing deleted msg {}.", request_uid);
info!("Not processing deleted msg {}.", request_uid);
last_uid = Some(request_uid);
continue;
}
@@ -1462,10 +1431,7 @@ impl Imap {
let body = if let Some(body) = body {
body
} else {
info!(
context,
"Not processing message {} without a BODY.", request_uid
);
info!("Not processing message {} without a BODY.", request_uid);
last_uid = Some(request_uid);
continue;
};
@@ -1476,17 +1442,13 @@ impl Imap {
rfc724_mid
} else {
error!(
context,
"No Message-ID corresponding to UID {} passed in uid_messsage_ids.",
request_uid
);
continue;
};
info!(
context,
"Passing message UID {} to receive_imf().", request_uid
);
info!("Passing message UID {} to receive_imf().", request_uid);
match receive_imf_inner(
context,
rfc724_mid,
@@ -1503,7 +1465,7 @@ impl Imap {
}
}
Err(err) => {
warn!(context, "receive_imf error: {:#}.", err);
warn!("receive_imf error: {:#}.", err);
}
};
last_uid = Some(request_uid)
@@ -1515,18 +1477,13 @@ impl Imap {
if count != request_uids.len() {
warn!(
context,
"Failed to fetch all UIDs: got {}, requested {}, we requested the UIDs {:?}.",
count,
request_uids.len(),
request_uids,
);
} else {
info!(
context,
"Successfully received {} UIDs.",
request_uids.len()
);
info!("Successfully received {} UIDs.", request_uids.len());
}
}
@@ -1567,7 +1524,7 @@ impl Imap {
return Some(ImapActionResult::RetryLater);
}
if let Err(err) = self.prepare(context).await {
warn!(context, "prepare_imap_op failed: {}", err);
warn!("prepare_imap_op failed: {}", err);
return Some(ImapActionResult::RetryLater);
}
@@ -1577,24 +1534,24 @@ impl Imap {
.context("no IMAP connection established")
{
Err(err) => {
error!(context, "Failed to prepare IMAP operation: {:#}", err);
error!("Failed to prepare IMAP operation: {:#}", err);
return Some(ImapActionResult::Failed);
}
Ok(session) => session,
};
match session.select_folder(context, Some(folder)).await {
match session.select_folder(Some(folder)).await {
Ok(_) => None,
Err(select_folder::Error::ConnectionLost) => {
warn!(context, "Lost imap connection");
warn!("Lost imap connection");
Some(ImapActionResult::RetryLater)
}
Err(select_folder::Error::BadFolderName(folder_name)) => {
warn!(context, "invalid folder name: {:?}", folder_name);
warn!("invalid folder name: {:?}", folder_name);
Some(ImapActionResult::Failed)
}
Err(err) => {
warn!(context, "failed to select folder {:?}: {:#}", folder, err);
warn!("failed to select folder {:?}: {:#}", folder, err);
Some(ImapActionResult::RetryLater)
}
}
@@ -1623,7 +1580,6 @@ impl Imap {
/// Returns first found or created folder name.
async fn configure_mvbox<'a>(
&mut self,
context: &Context,
folders: &[&'a str],
create_mvbox: bool,
) -> Result<Option<&'a str>> {
@@ -1634,15 +1590,15 @@ impl Imap {
// Close currently selected folder if needed.
// We are going to select folders using low-level EXAMINE operations below.
session.select_folder(context, None).await?;
session.select_folder(None).await?;
for folder in folders {
info!(context, "Looking for MVBOX-folder \"{}\"...", &folder);
info!("Looking for MVBOX-folder \"{}\"...", &folder);
let res = session.examine(&folder).await;
if res.is_ok() {
info!(
context,
"MVBOX-folder {:?} successfully selected, using it.", &folder
"MVBOX-folder {:?} successfully selected, using it.",
&folder
);
session.close().await?;
return Ok(Some(folder));
@@ -1653,11 +1609,11 @@ impl Imap {
for folder in folders {
match session.create(&folder).await {
Ok(_) => {
info!(context, "MVBOX-folder {} created.", &folder);
info!("MVBOX-folder {} created.", &folder);
return Ok(Some(folder));
}
Err(err) => {
warn!(context, "Cannot create MVBOX-folder {:?}: {}", &folder, err);
warn!("Cannot create MVBOX-folder {:?}: {}", &folder, err);
}
}
}
@@ -1682,7 +1638,7 @@ impl Imap {
while let Some(folder) = folders.next().await {
let folder = folder?;
info!(context, "Scanning folder: {:?}", folder);
info!("Scanning folder: {:?}", folder);
// Update the delimiter iff there is a different one, but only once.
if let Some(d) = folder.delimiter() {
@@ -1706,11 +1662,11 @@ impl Imap {
}
drop(folders);
info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
info!("Using \"{}\" as folder-delimiter.", delimiter);
let fallback_folder = format!("INBOX{delimiter}DeltaChat");
let mvbox_folder = self
.configure_mvbox(context, &["DeltaChat", &fallback_folder], create_mvbox)
.configure_mvbox(&["DeltaChat", &fallback_folder], create_mvbox)
.await
.context("failed to configure mvbox")?;
@@ -1718,7 +1674,7 @@ impl Imap {
.set_config(Config::ConfiguredInboxFolder, Some("INBOX"))
.await?;
if let Some(mvbox_folder) = mvbox_folder {
info!(context, "Setting MVBOX FOLDER TO {}", &mvbox_folder);
info!("Setting MVBOX FOLDER TO {}", &mvbox_folder);
context
.set_config(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
.await?;
@@ -1731,7 +1687,7 @@ impl Imap {
.set_raw_config_int("folders_configured", DC_FOLDERS_CONFIGURED_VERSION)
.await?;
info!(context, "FINISHED configuring IMAP-folders.");
info!("FINISHED configuring IMAP-folders.");
Ok(())
}
}
@@ -1741,18 +1697,15 @@ impl Session {
/// Drains all responses from `session.unsolicited_responses` in the process.
/// If this returns `true`, this means that new emails arrived and you should
/// fetch again, even if you just fetched.
fn server_sent_unsolicited_exists(&self, context: &Context) -> Result<bool> {
fn server_sent_unsolicited_exists(&self) -> Result<bool> {
let mut unsolicited_exists = false;
while let Ok(response) = self.unsolicited_responses.try_recv() {
match response {
UnsolicitedResponse::Exists(_) => {
info!(
context,
"Need to fetch again, got unsolicited EXISTS {:?}", response
);
info!("Need to fetch again, got unsolicited EXISTS {:?}", response);
unsolicited_exists = true;
}
_ => info!(context, "ignoring unsolicited response {:?}", response),
_ => info!("ignoring unsolicited response {:?}", response),
}
}
Ok(unsolicited_exists)
@@ -1796,8 +1749,8 @@ async fn should_move_out_of_spam(
Some(res) => res,
None => {
warn!(
context,
"Contact with From address {:?} cannot exist, not moving out of spam", from
"Contact with From address {:?} cannot exist, not moving out of spam",
from
);
return Ok(false);
}
@@ -2120,7 +2073,7 @@ pub(crate) async fn prefetch_should_download(
// (prevent_rename is the last argument of from_field_to_contact_id())
if flags.any(|f| f == Flag::Draft) {
info!(context, "Ignoring draft message");
info!("Ignoring draft message");
return Ok(false);
}
@@ -2425,8 +2378,8 @@ async fn add_all_recipients_as_contacts(
m
} else {
info!(
context,
"Folder {} is not configured, skipping fetching contacts from it.", folder
"Folder {} is not configured, skipping fetching contacts from it.",
folder
);
return Ok(());
};
@@ -2450,10 +2403,8 @@ async fn add_all_recipients_as_contacts(
let recipient_addr = match ContactAddress::new(&recipient.addr) {
Err(err) => {
warn!(
context,
"Could not add contact for recipient with address {:?}: {:#}",
recipient.addr,
err
recipient.addr, err
);
continue;
}

View File

@@ -4,6 +4,7 @@ use anyhow::{bail, Context as _, Result};
use async_channel::Receiver;
use async_imap::extensions::idle::IdleResponse;
use futures_lite::FutureExt;
use tracing::{error, info, warn};
use super::session::Session;
use super::Imap;
@@ -15,7 +16,6 @@ const IDLE_TIMEOUT: Duration = Duration::from_secs(23 * 60);
impl Session {
pub async fn idle(
mut self,
context: &Context,
idle_interrupt_receiver: Receiver<InterruptInfo>,
watch_folder: Option<String>,
) -> Result<(Self, InterruptInfo)> {
@@ -27,14 +27,14 @@ impl Session {
let mut info = Default::default();
self.select_folder(context, watch_folder.as_deref()).await?;
self.select_folder(watch_folder.as_deref()).await?;
if self.server_sent_unsolicited_exists(context)? {
if self.server_sent_unsolicited_exists()? {
return Ok((self, info));
}
if let Ok(info) = idle_interrupt_receiver.try_recv() {
info!(context, "skip idle, got interrupt {:?}", info);
info!("skip idle, got interrupt {:?}", info);
return Ok((self, info));
}
@@ -56,10 +56,7 @@ impl Session {
}
let folder_name = watch_folder.as_deref().unwrap_or("None");
info!(
context,
"{}: Idle entering wait-on-remote state", folder_name
);
info!("{}: Idle entering wait-on-remote state", folder_name);
let fut = idle_wait.map(|ev| ev.map(Event::IdleResponse)).race(async {
let info = idle_interrupt_receiver.recv().await;
@@ -71,29 +68,20 @@ impl Session {
match fut.await {
Ok(Event::IdleResponse(IdleResponse::NewData(x))) => {
info!(context, "{}: Idle has NewData {:?}", folder_name, x);
info!("{}: Idle has NewData {:?}", folder_name, x);
}
Ok(Event::IdleResponse(IdleResponse::Timeout)) => {
info!(
context,
"{}: Idle-wait timeout or interruption", folder_name
);
info!("{}: Idle-wait timeout or interruption", folder_name);
}
Ok(Event::IdleResponse(IdleResponse::ManualInterrupt)) => {
info!(
context,
"{}: Idle wait was interrupted manually", folder_name
);
info!("{}: Idle wait was interrupted manually", folder_name);
}
Ok(Event::Interrupt(i)) => {
info!(
context,
"{}: Idle wait was interrupted: {:?}", folder_name, &i
);
info!("{}: Idle wait was interrupted: {:?}", folder_name, &i);
info = i;
}
Err(err) => {
warn!(context, "{}: Idle wait errored: {:?}", folder_name, err);
warn!("{}: Idle wait errored: {:?}", folder_name, err);
}
}
@@ -124,14 +112,14 @@ impl Imap {
let watch_folder = if let Some(watch_folder) = watch_folder {
watch_folder
} else {
info!(context, "IMAP-fake-IDLE: no folder, waiting for interrupt");
info!("IMAP-fake-IDLE: no folder, waiting for interrupt");
return self
.idle_interrupt_receiver
.recv()
.await
.unwrap_or_default();
};
info!(context, "IMAP-fake-IDLEing folder={:?}", watch_folder);
info!("IMAP-fake-IDLEing folder={:?}", watch_folder);
// check every minute if there are new messages
// TODO: grow sleep durations / make them more flexible
@@ -159,7 +147,7 @@ impl Imap {
// (setup_handle_if_needed might not know about them if we
// never successfully connected)
if let Err(err) = self.prepare(context).await {
warn!(context, "fake_idle: could not connect: {}", err);
warn!("fake_idle: could not connect: {}", err);
continue;
}
if let Some(session) = &self.session {
@@ -168,7 +156,7 @@ impl Imap {
break InterruptInfo::new(false);
}
}
info!(context, "fake_idle is connected");
info!("fake_idle is connected");
// we are connected, let's see if fetching messages results
// in anything. If so, we behave as if IDLE had data but
// will have already fetched the messages so perform_*_fetch
@@ -178,27 +166,26 @@ impl Imap {
.await
{
Ok(res) => {
info!(context, "fetch_new_messages returned {:?}", res);
info!("fetch_new_messages returned {:?}", res);
if res {
break InterruptInfo::new(false);
}
}
Err(err) => {
error!(context, "could not fetch from folder: {:#}", err);
self.trigger_reconnect(context);
error!("could not fetch from folder: {:#}", err);
self.trigger_reconnect();
}
}
}
Event::Interrupt(info) => {
// Interrupt
info!(context, "Fake IDLE interrupted");
info!("Fake IDLE interrupted");
break info;
}
}
};
info!(
context,
"IMAP-fake-IDLE done after {:.4}s",
SystemTime::now()
.duration_since(fake_idle_start_time)

View File

@@ -2,6 +2,7 @@ use std::{collections::BTreeMap, time::Instant};
use anyhow::{Context as _, Result};
use futures::stream::StreamExt;
use tracing::info;
use super::{get_folder_meaning_by_attrs, get_folder_meaning_by_name};
use crate::config::Config;
@@ -24,7 +25,7 @@ impl Imap {
return Ok(false);
}
}
info!(context, "Starting full folder scan");
info!("Starting full folder scan");
self.prepare(context).await?;
let folders = self.list_folders(context).await?;
@@ -65,7 +66,7 @@ impl Imap {
{
let session = self.session.as_mut().context("no session")?;
// Drain leftover unsolicited EXISTS messages
session.server_sent_unsolicited_exists(context)?;
session.server_sent_unsolicited_exists()?;
loop {
self.fetch_move_delete(context, folder.name(), folder_meaning)
@@ -74,7 +75,7 @@ impl Imap {
let session = self.session.as_mut().context("no session")?;
// If the server sent an unsocicited EXISTS during the fetch, we need to fetch again
if !session.server_sent_unsolicited_exists(context)? {
if !session.server_sent_unsolicited_exists()? {
break;
}
}

View File

@@ -1,9 +1,9 @@
//! # IMAP folder selection module.
use anyhow::Context as _;
use tracing::info;
use super::session::Session as ImapSession;
use crate::context::Context;
type Result<T> = std::result::Result<T, Error>;
@@ -34,13 +34,13 @@ impl ImapSession {
///
/// CLOSE is considerably faster than an EXPUNGE, see
/// <https://tools.ietf.org/html/rfc3501#section-6.4.2>
pub(super) async fn maybe_close_folder(&mut self, context: &Context) -> anyhow::Result<()> {
pub(super) async fn maybe_close_folder(&mut self) -> anyhow::Result<()> {
if let Some(folder) = &self.selected_folder {
if self.selected_folder_needs_expunge {
info!(context, "Expunge messages in \"{}\".", folder);
info!("Expunge messages in \"{}\".", folder);
self.close().await.context("IMAP close/expunge failed")?;
info!(context, "close/expunge succeeded");
info!("close/expunge succeeded");
self.selected_folder = None;
self.selected_folder_needs_expunge = false;
}
@@ -51,11 +51,7 @@ impl ImapSession {
/// Selects a folder, possibly updating uid_validity and, if needed,
/// expunging the folder to remove delete-marked messages.
/// Returns whether a new folder was selected.
pub(super) async fn select_folder(
&mut self,
context: &Context,
folder: Option<&str>,
) -> Result<NewlySelected> {
pub(super) async fn select_folder(&mut self, folder: Option<&str>) -> Result<NewlySelected> {
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
// if there is _no_ new folder, we continue as we might want to expunge below.
if let Some(folder) = folder {
@@ -67,7 +63,7 @@ impl ImapSession {
}
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
self.maybe_close_folder(context).await?;
self.maybe_close_folder().await?;
// select new folder
if let Some(folder) = folder {
@@ -104,19 +100,18 @@ impl ImapSession {
/// Selects a folder. Tries to create it once and select again if the folder does not exist.
pub(super) async fn select_or_create_folder(
&mut self,
context: &Context,
folder: &str,
) -> anyhow::Result<NewlySelected> {
match self.select_folder(context, Some(folder)).await {
match self.select_folder( Some(folder)).await {
Ok(newly_selected) => Ok(newly_selected),
Err(err) => match err {
Error::NoFolder(..) => {
info!(context, "Failed to select folder {} because it does not exist, trying to create it.", folder);
info!( "Failed to select folder {} because it does not exist, trying to create it.", folder);
self.create(folder).await.with_context(|| {
format!("Couldn't select folder ('{err}'), then create() failed")
})?;
Ok(self.select_folder(context, Some(folder)).await.with_context(|| format!("failed to select newely created folder {folder}"))?)
Ok(self.select_folder(Some(folder)).await.with_context(|| format!("failed to select newely created folder {folder}"))?)
}
_ => Err(err).with_context(|| format!("failed to select folder {folder} with error other than NO, not trying to create it")),
},

View File

@@ -11,6 +11,7 @@ use futures_lite::FutureExt;
use rand::{thread_rng, Rng};
use tokio::fs::{self, File};
use tokio_tar::Archive;
use tracing::{error, info, warn};
use crate::blob::{BlobDirContents, BlobObject};
use crate::chat::{self, delete_and_reset_all_device_msgs, ChatId};
@@ -91,24 +92,22 @@ pub async fn imex(
let cancel = context.alloc_ongoing().await?;
let res = {
let mut guard = context.scheduler.pause(context.clone()).await;
let res = imex_inner(context, what, path, passphrase)
let _guard = context.scheduler.pause(context.clone()).await;
imex_inner(context, what, path, passphrase)
.race(async {
cancel.recv().await.ok();
Err(format_err!("canceled"))
})
.await;
guard.resume().await;
res
.await
};
context.free_ongoing().await;
if let Err(err) = res.as_ref() {
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
error!(context, "IMEX failed to complete: {:#}", err);
error!("IMEX failed to complete: {:#}", err);
context.emit_event(EventType::ImexProgress(0));
} else {
info!(context, "IMEX successfully completed");
info!("IMEX successfully completed");
context.emit_event(EventType::ImexProgress(1000));
}
@@ -116,7 +115,7 @@ pub async fn imex(
}
/// Returns the filename of the backup found (otherwise an error)
pub async fn has_backup(_context: &Context, dir_name: &Path) -> Result<String> {
pub async fn has_backup(dir_name: &Path) -> Result<String> {
let mut dir_iter = tokio::fs::read_dir(dir_name).await?;
let mut newest_backup_name = "".to_string();
let mut newest_backup_path: Option<PathBuf> = None;
@@ -146,7 +145,7 @@ pub async fn has_backup(_context: &Context, dir_name: &Path) -> Result<String> {
///
/// Returns setup code.
pub async fn initiate_key_transfer(context: &Context) -> Result<String> {
let setup_code = create_setup_code(context);
let setup_code = create_setup_code();
/* this may require a keypair to be created. this may take a second ... */
let setup_file_content = render_setup_file(context, &setup_code).await?;
/* encrypting may also take a while ... */
@@ -228,7 +227,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<St
}
/// Creates a new setup code for Autocrypt Setup Message.
pub fn create_setup_code(_context: &Context) -> String {
pub fn create_setup_code() -> String {
let mut random_val: u16;
let mut rng = thread_rng();
let mut ret = String::new();
@@ -345,7 +344,7 @@ async fn set_self_key(
)
.await?;
info!(context, "stored self key: {:?}", keypair.secret.key_id());
info!("stored self key: {:?}", keypair.secret.key_id());
Ok(())
}
@@ -378,7 +377,7 @@ async fn imex_inner(
path: &Path,
passphrase: Option<String>,
) -> Result<()> {
info!(context, "Import/export dir: {}", path.display());
info!("Import/export dir: {}", path.display());
ensure!(context.sql.is_open().await, "Database not opened.");
context.emit_event(EventType::ImexProgress(10));
@@ -428,7 +427,6 @@ async fn import_backup(
let backup_file = File::open(backup_to_import).await?;
let file_size = backup_file.metadata().await?.len();
info!(
context,
"Import \"{}\" ({} bytes) to \"{}\".",
backup_to_import.display(),
file_size,
@@ -470,7 +468,7 @@ async fn import_backup(
if let Some(name) = from_path.file_name() {
fs::rename(&from_path, context.get_blobdir().join(name)).await?;
} else {
warn!(context, "No file name");
warn!("No file name.");
}
}
}
@@ -529,7 +527,6 @@ async fn export_backup(context: &Context, dir: &Path, passphrase: String) -> Res
.context("could not export database")?;
info!(
context,
"Backup '{}' to '{}'.",
context.get_dbfile().display(),
dest_path.display(),
@@ -543,7 +540,7 @@ async fn export_backup(context: &Context, dir: &Path, passphrase: String) -> Res
context.emit_event(EventType::ImexFileWritten(dest_path));
}
Err(e) => {
error!(context, "backup failed: {}", e);
error!("backup failed: {}", e);
}
}
@@ -615,7 +612,7 @@ async fn import_self_keys(context: &Context, dir: &Path) -> Result<()> {
continue;
}
set_default = if name_f.contains("legacy") {
info!(context, "found legacy key '{}'", path_plus_name.display());
info!("found legacy key '{}'", path_plus_name.display());
false
} else {
true
@@ -625,17 +622,13 @@ async fn import_self_keys(context: &Context, dir: &Path) -> Result<()> {
continue;
}
}
info!(
context,
"considering key file: {}",
path_plus_name.display()
);
info!("considering key file: {}", path_plus_name.display());
match read_file(context, &path_plus_name).await {
Ok(buf) => {
let armored = std::string::String::from_utf8_lossy(&buf);
if let Err(err) = set_self_key(context, &armored, set_default, false).await {
info!(context, "set_self_key: {}", err);
info!("set_self_key: {}", err);
continue;
}
}
@@ -680,7 +673,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> {
let id = Some(id).filter(|_| is_default != 0);
if let Ok(key) = public_key {
if let Err(err) = export_key_to_asc_file(context, dir, id, &key).await {
error!(context, "Failed to export public key: {:#}.", err);
error!("Failed to export public key: {:#}.", err);
export_errors += 1;
}
} else {
@@ -688,7 +681,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> {
}
if let Ok(key) = private_key {
if let Err(err) = export_key_to_asc_file(context, dir, id, &key).await {
error!(context, "Failed to export private key: {:#}.", err);
error!("Failed to export private key: {:#}.", err);
export_errors += 1;
}
} else {
@@ -725,7 +718,6 @@ where
dir.join(format!("{}-key-{}.asc", kind, &id))
};
info!(
context,
"Exporting key {:?} to {}",
key.key_id(),
file_name.display()
@@ -766,7 +758,7 @@ async fn export_database(context: &Context, dest: &Path, passphrase: String) ->
.sql
.call_write(|conn| {
conn.execute("VACUUM;", params![])
.map_err(|err| warn!(context, "Vacuum failed, exporting anyway {err}"))
.map_err(|err| warn!("Vacuum failed, exporting anyway {err:#}."))
.ok();
conn.execute(
"ATTACH DATABASE ? AS backup KEY ?",
@@ -826,8 +818,7 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_create_setup_code() {
let t = TestContext::new().await;
let setupcode = create_setup_code(&t);
let setupcode = create_setup_code();
assert_eq!(setupcode.len(), 44);
assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
assert_eq!(setupcode.chars().nth(9).unwrap(), '-');
@@ -892,7 +883,7 @@ mod tests {
let context2 = TestContext::new().await;
assert!(!context2.is_configured().await?);
assert!(has_backup(&context2, backup_dir.path()).await.is_err());
assert!(has_backup(backup_dir.path()).await.is_err());
// export from context1
assert!(
@@ -906,7 +897,7 @@ mod tests {
.await;
// import to context2
let backup = has_backup(&context2, backup_dir.path()).await?;
let backup = has_backup(backup_dir.path()).await?;
// Import of unencrypted backup with incorrect "foobar" backup passphrase fails.
assert!(imex(
@@ -951,7 +942,7 @@ mod tests {
imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None).await?;
// import to context2
let backup = has_backup(&context2, backup_dir.path()).await?;
let backup = has_backup(backup_dir.path()).await?;
let context2_cloned = context2.clone();
let handle = task::spawn(async move {
imex(

View File

@@ -22,6 +22,7 @@
//! getter can not connect to an impersonated provider and the provider does not offer the
//! download to an impersonated getter.
use std::cmp::Ordering;
use std::future::Future;
use std::net::Ipv4Addr;
use std::ops::Deref;
@@ -43,6 +44,7 @@ use tokio::sync::broadcast::error::RecvError;
use tokio::sync::{broadcast, Mutex};
use tokio::task::{JoinHandle, JoinSet};
use tokio_stream::wrappers::ReadDirStream;
use tracing::{error, info, warn};
use crate::blob::BlobDirContents;
use crate::chat::delete_and_reset_all_device_msgs;
@@ -91,7 +93,7 @@ impl BackupProvider {
// Acquire global "ongoing" mutex.
let cancel_token = context.alloc_ongoing().await?;
let mut paused_guard = context.scheduler.pause(context.clone()).await;
let paused_guard = context.scheduler.pause(context.clone()).await;
let context_dir = context
.get_blobdir()
.parent()
@@ -99,7 +101,7 @@ impl BackupProvider {
let dbfile = context_dir.join(DBFILE_BACKUP_NAME);
if fs::metadata(&dbfile).await.is_ok() {
fs::remove_file(&dbfile).await?;
warn!(context, "Previous database export deleted");
warn!("Previous database export deleted.");
}
let dbfile = TempPathGuard::new(dbfile);
let res = tokio::select! {
@@ -108,7 +110,7 @@ impl BackupProvider {
match res {
Ok(slf) => Ok(slf),
Err(err) => {
error!(context, "Failed to set up second device setup: {:#}", err);
error!("Failed to set up second device setup: {err:#}.");
Err(err)
},
}
@@ -119,7 +121,6 @@ impl BackupProvider {
Ok((provider, ticket)) => (provider, ticket),
Err(err) => {
context.free_ongoing().await;
paused_guard.resume().await;
return Err(err);
}
};
@@ -128,7 +129,9 @@ impl BackupProvider {
tokio::spawn(async move {
let res = Self::watch_provider(&context, provider, cancel_token).await;
context.free_ongoing().await;
paused_guard.resume().await;
// Explicit drop to move the guards into this future
drop(paused_guard);
drop(dbfile);
res
})
@@ -168,7 +171,7 @@ impl BackupProvider {
.auth_token(token)
.spawn()?;
context.emit_event(SendProgress::ProviderListening.into());
info!(context, "Waiting for remote to connect");
info!("Waiting for remote to connect.");
let ticket = provider.ticket(hash);
Ok((provider, ticket))
}
@@ -255,7 +258,7 @@ impl BackupProvider {
match &res {
Ok(_) => context.emit_event(SendProgress::Completed.into()),
Err(err) => {
error!(context, "Backup transfer failure: {err:#}");
error!("Backup transfer failure: {err:#}.");
context.emit_event(SendProgress::Failed.into())
}
}
@@ -369,7 +372,7 @@ pub async fn get_backup(context: &Context, qr: Qr) -> Result<()> {
!context.is_configured().await?,
"Cannot import backups to accounts in use."
);
let mut guard = context.scheduler.pause(context.clone()).await;
let _guard = context.scheduler.pause(context.clone()).await;
// Acquire global "ongoing" mutex.
let cancel_token = context.alloc_ongoing().await?;
@@ -381,25 +384,38 @@ pub async fn get_backup(context: &Context, qr: Qr) -> Result<()> {
}
_ = cancel_token.recv() => Err(format_err!("cancelled")),
};
guard.resume().await;
res
}
async fn get_backup_inner(context: &Context, qr: Qr) -> Result<()> {
let ticket = match qr {
let mut ticket = match qr {
Qr::Backup { ticket } => ticket,
_ => bail!("QR code for backup must be of type DCBACKUP"),
};
if ticket.addrs.is_empty() {
bail!("ticket is missing addresses to dial");
}
// Crude sorting, most local wifi's are in the 192.168.0.0/24 range so this will try
// them first.
ticket.addrs.sort_by(|a, b| {
let a = a.to_string();
let b = b.to_string();
if a.starts_with("192.168.") && !b.starts_with("192.168.") {
Ordering::Less
} else if b.starts_with("192.168.") && !a.starts_with("192.168.") {
Ordering::Greater
} else {
Ordering::Equal
}
});
for addr in &ticket.addrs {
let opts = Options {
addr: *addr,
peer_id: Some(ticket.peer),
keylog: false,
};
info!(context, "attempting to contact {}", addr);
info!("Attempting to contact {addr}.");
match transfer_from_provider(context, &ticket, opts).await {
Ok(_) => {
delete_and_reset_all_device_msgs(context).await?;
@@ -407,7 +423,7 @@ async fn get_backup_inner(context: &Context, qr: Qr) -> Result<()> {
return Ok(());
}
Err(TransferError::ConnectionError(err)) => {
warn!(context, "Connection error: {err:#}.");
warn!("Connection error: {err:#}.");
continue;
}
Err(TransferError::Other(err)) => {
@@ -477,7 +493,6 @@ async fn transfer_from_provider(
match res {
Ok(stats) => {
info!(
context,
"Backup transfer finished, transfer rate is {} Mbps.",
stats.mbits()
);
@@ -512,7 +527,7 @@ async fn on_blob(
let dbfile = context_dir.join(DBFILE_BACKUP_NAME);
if fs::metadata(&dbfile).await.is_ok() {
fs::remove_file(&dbfile).await?;
warn!(context, "Previous database export deleted");
warn!("Previous database export deleted.");
}
dbfile
} else {
@@ -532,12 +547,11 @@ async fn on_blob(
let token = ticket.token.to_string();
jobs.lock().await.spawn(async move {
if let Err(err) = context.sql.import(&path, token).await {
error!(context, "cannot import database: {:#?}", err);
error!("Cannot import database: {err:#?}.");
}
if let Err(err) = fs::remove_file(&path).await {
error!(
context,
"failed to delete database import file '{}': {:#?}",
"Failed to delete database import file '{}': {:#?}.",
path.display(),
err,
);

View File

@@ -11,6 +11,7 @@ use std::sync::atomic::Ordering;
use anyhow::{Context as _, Result};
use deltachat_derive::{FromSql, ToSql};
use rand::{thread_rng, Rng};
use tracing::{error, info, warn};
use crate::context::Context;
use crate::imap::Imap;
@@ -110,7 +111,7 @@ impl Job {
///
/// The Job is consumed by this method.
pub(crate) async fn save(self, context: &Context) -> Result<()> {
info!(context, "saving job {:?}", self);
info!("saving job {:?}", self);
if self.job_id != 0 {
context
@@ -153,7 +154,7 @@ impl<'a> Connection<'a> {
}
pub(crate) async fn perform_job(context: &Context, mut connection: Connection<'_>, mut job: Job) {
info!(context, "job {} started...", &job);
info!("Job {} started...", &job);
let try_res = match perform_job_action(context, &mut job, &mut connection, 0).await {
Status::RetryNow => perform_job_action(context, &mut job, &mut connection, 1).await,
@@ -165,42 +166,33 @@ pub(crate) async fn perform_job(context: &Context, mut connection: Connection<'_
let tries = job.tries + 1;
if tries < JOB_RETRIES {
info!(context, "increase job {} tries to {}", job, tries);
info!("Increase job {job} tries to {tries}.");
job.tries = tries;
let time_offset = get_backoff_time_offset(tries);
job.desired_timestamp = time() + time_offset;
info!(
context,
"job #{} not succeeded on try #{}, retry in {} seconds.",
job.job_id,
tries,
time_offset
job.job_id, tries, time_offset
);
job.save(context).await.unwrap_or_else(|err| {
error!(context, "failed to save job: {:#}", err);
error!("Failed to save job: {err:#}.");
});
} else {
info!(
context,
"remove job {} as it exhausted {} retries", job, JOB_RETRIES
);
info!("Remove job {job} as it exhausted {JOB_RETRIES} retries.");
job.delete(context).await.unwrap_or_else(|err| {
error!(context, "failed to delete job: {:#}", err);
error!("Failed to delete job: {err:#}.");
});
}
}
Status::Finished(res) => {
if let Err(err) = res {
warn!(
context,
"remove job {} as it failed with error {:#}", job, err
);
warn!("Remove job {job} as it failed with error {err:#}.");
} else {
info!(context, "remove job {} as it succeeded", job);
info!("Remove job {job} as it succeeded.");
}
job.delete(context).await.unwrap_or_else(|err| {
error!(context, "failed to delete job: {:#}", err);
error!("failed to delete job: {:#}", err);
});
}
}
@@ -212,13 +204,13 @@ async fn perform_job_action(
connection: &mut Connection<'_>,
tries: u32,
) -> Status {
info!(context, "begin immediate try {} of job {}", tries, job);
info!("Begin immediate try {tries} of job {job}.");
let try_res = match job.action {
Action::DownloadMsg => job.download_msg(context, connection.inbox()).await,
};
info!(context, "Finished immediate try {} of job {}", tries, job);
info!("Finished immediate try {tries} of job {job}.");
try_res
}
@@ -250,7 +242,7 @@ pub(crate) async fn schedule_resync(context: &Context) -> Result<()> {
pub async fn add(context: &Context, job: Job) -> Result<()> {
job.save(context).await.context("failed to save job")?;
info!(context, "interrupt: imap");
info!("Interrupt: IMAP.");
context
.scheduler
.interrupt_inbox(InterruptInfo::new(false))
@@ -264,7 +256,7 @@ pub async fn add(context: &Context, job: Job) -> Result<()> {
/// jobs, this is tricky and probably wrong currently. Look at the
/// SQL queries for details.
pub(crate) async fn load_next(context: &Context, info: &InterruptInfo) -> Result<Option<Job>> {
info!(context, "loading job");
info!("Loading job.");
let query;
let params;
@@ -316,19 +308,19 @@ LIMIT 1;
Ok(job) => return Ok(job),
Err(err) => {
// Remove invalid job from the DB
info!(context, "cleaning up job, because of {:#}", err);
info!("Cleaning up job, because of {err:#}.");
// TODO: improve by only doing a single query
let id = context
.sql
.query_row(query, params.clone(), |row| row.get::<_, i32>(0))
.await
.context("Failed to retrieve invalid job ID from the database")?;
.context("failed to retrieve invalid job ID from the database")?;
context
.sql
.execute("DELETE FROM jobs WHERE id=?;", paramsv![id])
.await
.with_context(|| format!("Failed to delete invalid job {id}"))?;
.with_context(|| format!("failed to delete invalid job {id}"))?;
}
}
}

View File

@@ -14,6 +14,7 @@ pub use pgp::composed::{SignedPublicKey, SignedSecretKey};
use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait};
use tokio::runtime::Handle;
use tracing::info;
use crate::config::Config;
use crate::constants::KeyGenType;
@@ -211,14 +212,13 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
let start = std::time::SystemTime::now();
let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await?)
.unwrap_or_default();
info!(context, "Generating keypair with type {}", keytype);
info!("Generating keypair with type {}", keytype);
let keypair = Handle::current()
.spawn_blocking(move || crate::pgp::create_keypair(addr, keytype))
.await??;
store_self_keypair(context, &keypair, KeyPairUse::Default).await?;
info!(
context,
"Keypair generated in {:.3}s.",
start.elapsed().unwrap_or_default().as_secs()
);

View File

@@ -40,7 +40,6 @@ pub trait ToSql: rusqlite::ToSql + Send + Sync {}
impl<T: rusqlite::ToSql + Send + Sync> ToSql for T {}
#[macro_use]
pub mod log;
#[cfg(feature = "internals")]

View File

@@ -8,6 +8,7 @@ use async_channel::Receiver;
use bitflags::bitflags;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use tokio::time::timeout;
use tracing::{info, warn};
use crate::chat::{self, ChatId};
use crate::contact::ContactId;
@@ -337,9 +338,9 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
ContactId::SELF,
]
).await {
warn!(context, "failed to store location {:#}", err);
warn!( "failed to store location {:#}", err);
} else {
info!(context, "stored location for chat {}", chat_id);
info!("stored location for chat {}", chat_id);
continue_streaming = true;
}
}
@@ -639,7 +640,7 @@ pub(crate) async fn location_loop(context: &Context, interrupt_receiver: Receive
loop {
let next_event = match maybe_send_locations(context).await {
Err(err) => {
warn!(context, "maybe_send_locations failed: {:#}", err);
warn!("maybe_send_locations failed: {:#}", err);
Some(60) // Retry one minute later.
}
Ok(next_event) => next_event,
@@ -652,7 +653,6 @@ pub(crate) async fn location_loop(context: &Context, interrupt_receiver: Receive
};
info!(
context,
"Location loop is waiting for {} or interrupt",
duration_to_str(duration)
);
@@ -719,10 +719,7 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
// Send location-only message.
// Pending locations are attached automatically to every message,
// so also to this empty text message.
info!(
context,
"Chat {} has pending locations, sending them.", chat_id
);
info!("Chat {} has pending locations, sending them.", chat_id);
let mut msg = Message::new(Viewtype::Text);
msg.hidden = true;
msg.param.set_cmd(SystemMessage::LocationOnly);
@@ -730,8 +727,8 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
} else {
// Wait until pending locations can be sent.
info!(
context,
"Chat {} has pending locations, but they can't be sent yet.", chat_id
"Chat {} has pending locations, but they can't be sent yet.",
chat_id
);
next_event = next_event
.into_iter()
@@ -740,17 +737,14 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
}
} else {
info!(
context,
"Chat {} has location streaming enabled, but no pending locations.", chat_id
"Chat {} has location streaming enabled, but no pending locations.",
chat_id
);
}
} else {
// Location streaming was either explicitly disabled (locations_send_begin = 0) or
// locations_send_until is in the past.
info!(
context,
"Disabling location streaming for chat {}.", chat_id
);
info!("Disabling location streaming for chat {}.", chat_id);
context
.sql
.execute(

View File

@@ -4,36 +4,6 @@
use crate::context::Context;
#[macro_export]
macro_rules! info {
($ctx:expr, $msg:expr) => {
info!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let full = format!("{file}:{line}: {msg}",
file = file!(),
line = line!(),
msg = &formatted);
$ctx.emit_event($crate::EventType::Info(full));
}};
}
#[macro_export]
macro_rules! warn {
($ctx:expr, $msg:expr) => {
warn!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let full = format!("{file}:{line}: {msg}",
file = file!(),
line = line!(),
msg = &formatted);
$ctx.emit_event($crate::EventType::Warning(full));
}};
}
#[macro_export]
macro_rules! error {
($ctx:expr, $msg:expr) => {
@@ -41,7 +11,6 @@ macro_rules! error {
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
$ctx.set_last_error(&formatted);
$ctx.emit_event($crate::EventType::Error(formatted));
}};
}
@@ -50,13 +19,13 @@ impl Context {
/// Set last error string.
/// Implemented as blocking as used from macros in different, not always async blocks.
pub fn set_last_error(&self, error: &str) {
let mut last_error = self.last_error.write().unwrap();
let mut last_error = self.events.last_error.write().unwrap();
*last_error = error.to_string();
}
/// Get last error string.
pub fn get_last_error(&self) -> String {
let last_error = &*self.last_error.read().unwrap();
let last_error = &*self.events.last_error.read().unwrap();
last_error.clone()
}
}
@@ -168,12 +137,6 @@ mod tests {
error!(t, "foo-error");
assert_eq!(t.get_last_error(), "foo-error");
warn!(t, "foo-warning");
assert_eq!(t.get_last_error(), "foo-error");
info!(t, "foo-info");
assert_eq!(t.get_last_error(), "foo-error");
error!(t, "bar-error");
error!(t, "baz-error");
assert_eq!(t.get_last_error(), "baz-error");

View File

@@ -7,6 +7,7 @@ use anyhow::{ensure, format_err, Context as _, Result};
use deltachat_derive::{FromSql, ToSql};
use rusqlite::types::ValueRef;
use serde::{Deserialize, Serialize};
use tracing::{error, info, warn};
use crate::chat::{self, Chat, ChatId};
use crate::config::Config;
@@ -337,7 +338,6 @@ impl Message {
Ok(t) => t,
Err(_) => {
warn!(
context,
concat!(
"dc_msg_load_from_db: could not get ",
"text column as non-lossy utf8 id {}"
@@ -1511,7 +1511,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
&& (curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed)
{
update_msg_state(context, id, MessageState::InSeen).await?;
info!(context, "Seen message {}.", id);
info!("Seen message {}.", id);
markseen_on_imap_table(context, &curr_rfc724_mid).await?;
@@ -1595,11 +1595,11 @@ pub(crate) async fn set_msg_failed(context: &Context, msg_id: MsgId, error: &str
if let Ok(mut msg) = Message::load_from_db(context, msg_id).await {
if msg.state.can_fail() {
msg.state = MessageState::OutFailed;
warn!(context, "{} failed: {}", msg_id, error);
warn!("{} failed: {}", msg_id, error);
} else {
warn!(
context,
"{} seems to have failed ({}), but state is {}", msg_id, error, msg.state
"{} seems to have failed ({}), but state is {}",
msg_id, error, msg.state
)
}
@@ -1616,7 +1616,7 @@ pub(crate) async fn set_msg_failed(context: &Context, msg_id: MsgId, error: &str
msg_id,
}),
Err(e) => {
warn!(context, "{:?}", e);
warn!("{:?}", e);
}
}
}
@@ -1630,10 +1630,7 @@ pub async fn handle_mdn(
timestamp_sent: i64,
) -> Result<Option<(ChatId, MsgId)>> {
if from_id == ContactId::SELF {
warn!(
context,
"ignoring MDN sent to self, this is a bug on the sender device"
);
warn!("ignoring MDN sent to self, this is a bug on the sender device");
// This is not an error on our side,
// we successfully ignored an invalid MDN and return `Ok`.
@@ -1667,7 +1664,6 @@ pub async fn handle_mdn(
res
} else {
info!(
context,
"handle_mdn found no message with Message-ID {:?} sent by us in the database",
rfc724_mid
);
@@ -1785,7 +1781,7 @@ async fn ndn_maybe_add_info_msg(
Chattype::Mailinglist => {
// ndn_maybe_add_info_msg() is about the case when delivery to the group failed.
// If we get an NDN for the mailing list, just issue a warning.
warn!(context, "ignoring NDN for mailing list.");
warn!("ignoring NDN for mailing list.");
}
Chattype::Single | Chattype::Undefined => {}
}
@@ -1806,7 +1802,7 @@ pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
{
Ok(res) => res,
Err(err) => {
error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
error!("get_unblocked_msg_cnt() failed. {:#}", err);
0
}
}
@@ -1826,7 +1822,7 @@ pub async fn get_request_msg_cnt(context: &Context) -> usize {
{
Ok(res) => res,
Err(err) => {
error!(context, "get_request_msg_cnt() failed. {:#}", err);
error!("get_request_msg_cnt() failed. {:#}", err);
0
}
}
@@ -1897,7 +1893,7 @@ pub(crate) async fn rfc724_mid_exists(
) -> Result<Option<MsgId>> {
let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
if rfc724_mid.is_empty() {
warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
warn!("Empty rfc724_mid passed to rfc724_mid_exists");
return Ok(None);
}

View File

@@ -8,6 +8,7 @@ use chrono::TimeZone;
use format_flowed::{format_flowed, format_flowed_quote};
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
use tokio::fs;
use tracing::{info, warn};
use crate::blob::BlobObject;
use crate::chat::Chat;
@@ -651,8 +652,7 @@ impl<'a> MimeFactory<'a> {
};
let peerstates = self.peerstates_for_recipients(context).await?;
let should_encrypt =
encrypt_helper.should_encrypt(context, e2ee_guaranteed, &peerstates)?;
let should_encrypt = encrypt_helper.should_encrypt(e2ee_guaranteed, &peerstates)?;
let is_encrypted = should_encrypt && !force_plaintext;
let message = if parts.is_empty() {
@@ -730,7 +730,6 @@ impl<'a> MimeFactory<'a> {
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(
context,
"mimefactory: unencrypted message mime-body:\n{}",
message.clone().build().as_string(),
);
@@ -819,7 +818,6 @@ impl<'a> MimeFactory<'a> {
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(
context,
"mimefactory: outgoing message mime-body:\n{}",
outer_message.clone().build().as_string(),
);
@@ -940,7 +938,6 @@ impl<'a> MimeFactory<'a> {
& DC_FROM_HANDSHAKE
{
info!(
context,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
"vg-member-added",
);
@@ -1025,8 +1022,8 @@ impl<'a> MimeFactory<'a> {
let step = msg.param.get(Param::Arg).unwrap_or_default();
if !step.is_empty() {
info!(
context,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>", step,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
step,
);
headers
.protected
@@ -1074,7 +1071,7 @@ impl<'a> MimeFactory<'a> {
}
if let Some(grpimage) = grpimage {
info!(context, "setting group image '{}'", grpimage);
info!("setting group image '{}'", grpimage);
let mut meta = Message {
viewtype: Viewtype::Image,
..Default::default()
@@ -1234,7 +1231,7 @@ impl<'a> MimeFactory<'a> {
match self.get_location_kml_part(context).await {
Ok(part) => parts.push(part),
Err(err) => {
warn!(context, "mimefactory: could not send location: {}", err);
warn!("mimefactory: could not send location: {}", err);
}
}
}
@@ -1265,7 +1262,7 @@ impl<'a> MimeFactory<'a> {
"Chat-User-Avatar".into(),
format!("base64:{avatar}"),
)),
Err(err) => warn!(context, "mimefactory: cannot attach selfavatar: {}", err),
Err(err) => warn!("mimefactory: cannot attach selfavatar: {}", err),
},
None => headers
.protected

View File

@@ -12,6 +12,7 @@ use format_flowed::unformat_flowed;
use lettre_email::mime::{self, Mime};
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
use once_cell::sync::Lazy;
use tracing::{error, info, warn};
use crate::aheader::{Aheader, EncryptPreference};
use crate::blob::BlobObject;
@@ -213,7 +214,6 @@ impl MimeMessage {
// Parse IMF headers.
MimeMessage::merge_headers(
context,
&mut headers,
&mut recipients,
&mut from,
@@ -231,7 +231,6 @@ impl MimeMessage {
// messages are shown as unencrypted anyway.
MimeMessage::merge_headers(
context,
&mut headers,
&mut recipients,
&mut from,
@@ -281,14 +280,13 @@ impl MimeMessage {
let public_keyring = keyring_from_peerstate(decryption_info.peerstate.as_ref());
let (mail, mut signatures, encrypted) = match tokio::task::block_in_place(|| {
try_decrypt(context, &mail, &private_keyring, &public_keyring)
try_decrypt(&mail, &private_keyring, &public_keyring)
}) {
Ok(Some((raw, signatures))) => {
mail_raw = raw;
let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(
context,
"decrypted message mime-body:\n{}",
String::from_utf8_lossy(&mail_raw),
);
@@ -297,7 +295,7 @@ impl MimeMessage {
}
Ok(None) => (Ok(mail), HashSet::new(), false),
Err(err) => {
warn!(context, "decryption failed: {:#}", err);
warn!("decryption failed: {:#}", err);
(Err(err), HashSet::new(), false)
}
};
@@ -336,7 +334,6 @@ impl MimeMessage {
headers.remove("subject");
MimeMessage::merge_headers(
context,
&mut headers,
&mut recipients,
&mut signed_from,
@@ -355,10 +352,7 @@ impl MimeMessage {
// Or it's because someone is doing some replay attack
// - OTOH, I can't come up with an attack scenario
// where this would be useful.
warn!(
context,
"From header in signed part doesn't match the outer one",
);
warn!("From header in signed part doesn't match the outer one",);
}
}
}
@@ -468,7 +462,7 @@ impl MimeMessage {
}
/// Parses system messages.
fn parse_system_message_headers(&mut self, context: &Context) {
fn parse_system_message_headers(&mut self) {
if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() {
self.parts.retain(|part| {
part.mimetype.is_none()
@@ -478,7 +472,7 @@ impl MimeMessage {
if self.parts.len() == 1 {
self.is_system_message = SystemMessage::AutocryptSetupMessage;
} else {
warn!(context, "could not determine ASM mime-part");
warn!("could not determine ASM mime-part");
}
} else if let Some(value) = self.get_header(HeaderDef::ChatContent) {
if value == "location-streaming-enabled" {
@@ -602,7 +596,7 @@ impl MimeMessage {
}
async fn parse_headers(&mut self, context: &Context) -> Result<()> {
self.parse_system_message_headers(context);
self.parse_system_message_headers();
self.parse_avatar_headers(context).await;
self.parse_videochat_headers();
if self.delivery_report.is_none() {
@@ -659,8 +653,8 @@ impl MimeMessage {
}
} else {
warn!(
context,
"{} requested a read receipt to {}, ignoring", from, dn_to.addr
"{} requested a read receipt to {}, ignoring",
from, dn_to.addr
);
}
}
@@ -724,10 +718,7 @@ impl MimeMessage {
{
Ok(blob) => Some(AvatarAction::Change(blob.as_name().to_string())),
Err(err) => {
warn!(
context,
"Could not save decoded avatar to blob file: {:#}", err
);
warn!("Could not save decoded avatar to blob file: {:#}", err);
None
}
}
@@ -795,13 +786,12 @@ impl MimeMessage {
if mail.ctype.params.get("protected-headers").is_some() {
if mail.ctype.mimetype == "text/rfc822-headers" {
warn!(
context,
"Protected headers found in text/rfc822-headers attachment: Will be ignored.",
);
return Ok(false);
}
warn!(context, "Ignoring nested protected headers");
warn!( "Ignoring nested protected headers");
}
enum MimeS {
@@ -920,7 +910,7 @@ impl MimeMessage {
if mail.subparts.len() >= 2 {
match mail.ctype.params.get("report-type").map(|s| s as &str) {
Some("disposition-notification") => {
if let Some(report) = self.process_report(context, mail)? {
if let Some(report) = self.process_report(mail)? {
self.mdn_reports.push(report);
}
@@ -938,7 +928,7 @@ impl MimeMessage {
}
// Some providers, e.g. Tiscali, forget to set the report-type. So, if it's None, assume that it might be delivery-status
Some("delivery-status") | None => {
if let Some(report) = self.process_delivery_status(context, mail)? {
if let Some(report) = self.process_delivery_status(mail)? {
self.delivery_report = Some(report);
}
@@ -1002,7 +992,7 @@ impl MimeMessage {
let (mime_type, msg_type) = get_mime_type(mail)?;
let raw_mime = mail.ctype.mimetype.to_lowercase();
let filename = get_attachment_filename(context, mail)?;
let filename = get_attachment_filename(mail)?;
let old_part_count = self.parts.len();
@@ -1022,7 +1012,7 @@ impl MimeMessage {
None => {
match mime_type.type_() {
mime::IMAGE | mime::AUDIO | mime::VIDEO | mime::APPLICATION => {
warn!(context, "Missing attachment");
warn!("Missing attachment");
return Ok(false);
}
mime::TEXT
@@ -1033,7 +1023,7 @@ impl MimeMessage {
let decoded_data = match mail.get_body() {
Ok(decoded_data) => decoded_data,
Err(err) => {
warn!(context, "Invalid body parsed {:#}", err);
warn!("Invalid body parsed {:#}", err);
// Note that it's not always an error - might be no data
return Ok(false);
}
@@ -1053,7 +1043,7 @@ impl MimeMessage {
let decoded_data = match mail.get_body() {
Ok(decoded_data) => decoded_data,
Err(err) => {
warn!(context, "Invalid body parsed {:#}", err);
warn!("Invalid body parsed {:#}", err);
// Note that it's not always an error - might be no data
return Ok(false);
}
@@ -1185,7 +1175,7 @@ impl MimeMessage {
if filename.starts_with("location") || filename.starts_with("message") {
let parsed = location::Kml::parse(decoded_data)
.map_err(|err| {
warn!(context, "failed to parse kml part: {:#}", err);
warn!("failed to parse kml part: {:#}", err);
})
.ok();
if filename.starts_with("location") {
@@ -1203,7 +1193,7 @@ impl MimeMessage {
self.sync_items = context
.parse_sync_items(serialized)
.map_err(|err| {
warn!(context, "failed to parse sync data: {:#}", err);
warn!("failed to parse sync data: {:#}", err);
})
.ok();
return Ok(());
@@ -1224,13 +1214,13 @@ impl MimeMessage {
Ok(blob) => blob,
Err(err) => {
error!(
context,
"Could not add blob for mime part {}, error {:#}", filename, err
"Could not add blob for mime part {}, error {:#}",
filename, err
);
return Ok(());
}
};
info!(context, "added blobfile: {:?}", blob.as_name());
info!("added blobfile: {:?}", blob.as_name());
/* create and register Mime part referencing the new Blob object */
let mut part = Part::default();
@@ -1261,23 +1251,20 @@ impl MimeMessage {
) -> Result<bool> {
let key = match str::from_utf8(decoded_data) {
Err(err) => {
warn!(context, "PGP key attachment is not a UTF-8 file: {}", err);
warn!("PGP key attachment is not a UTF-8 file: {}", err);
return Ok(false);
}
Ok(key) => key,
};
let key = match SignedPublicKey::from_asc(key) {
Err(err) => {
warn!(
context,
"PGP key attachment is not an ASCII-armored file: {:#}", err
);
warn!("PGP key attachment is not an ASCII-armored file: {:#}", err);
return Ok(false);
}
Ok((key, _)) => key,
};
if let Err(err) = key.verify() {
warn!(context, "attached PGP key verification failed: {}", err);
warn!("attached PGP key verification failed: {}", err);
return Ok(false);
}
if !key.details.users.iter().any(|user| {
@@ -1293,19 +1280,17 @@ impl MimeMessage {
// user have an Autocrypt-capable MUA and also attaches a key, but if that's the
// case, let 'em first disable Autocrypt and then change the key by attaching it.
warn!(
context,
"not using attached PGP key for peer '{}' because another one is already set \
with prefer-encrypt={}",
peerstate.addr,
peerstate.prefer_encrypt,
peerstate.addr, peerstate.prefer_encrypt,
);
return Ok(false);
}
}
peerstate.public_key = Some(key);
info!(
context,
"using attached PGP key for peer '{}' with prefer-encrypt=mutual", peerstate.addr,
"using attached PGP key for peer '{}' with prefer-encrypt=mutual",
peerstate.addr,
);
peerstate.prefer_encrypt = EncryptPreference::Mutual;
peerstate.save_to_db(&context.sql).await?;
@@ -1357,7 +1342,6 @@ impl MimeMessage {
}
fn merge_headers(
context: &Context,
headers: &mut HashMap<String, String>,
recipients: &mut Vec<SingleInfo>,
from: &mut Option<SingleInfo>,
@@ -1376,7 +1360,7 @@ impl MimeMessage {
Ok(addrlist) => {
*chat_disposition_notification_to = addrlist.extract_single_info();
}
Err(e) => warn!(context, "Could not read {} address: {}", key, e),
Err(e) => warn!("Could not read {} address: {}", key, e),
}
} else {
let value = field.get_value();
@@ -1398,11 +1382,7 @@ impl MimeMessage {
}
}
fn process_report(
&self,
context: &Context,
report: &mailparse::ParsedMail<'_>,
) -> Result<Option<Report>> {
fn process_report(&self, report: &mailparse::ParsedMail<'_>) -> Result<Option<Report>> {
// parse as mailheaders
let report_body = if let Some(subpart) = report.subparts.get(1) {
subpart.get_body_raw()?
@@ -1433,7 +1413,6 @@ impl MimeMessage {
}));
}
warn!(
context,
"ignoring unknown disposition-notification, Message-Id: {:?}",
report_fields.get_header_value(HeaderDef::MessageId)
);
@@ -1443,7 +1422,6 @@ impl MimeMessage {
fn process_delivery_status(
&self,
context: &Context,
report: &mailparse::ParsedMail<'_>,
) -> Result<Option<DeliveryReport>> {
// Assume failure.
@@ -1455,7 +1433,7 @@ impl MimeMessage {
if status_part.ctype.mimetype != "message/delivery-status"
&& status_part.ctype.mimetype != "message/global-delivery-status"
{
warn!(context, "Second part of Delivery Status Notification is not message/delivery-status or message/global-delivery-status, ignoring");
warn!( "Second part of Delivery Status Notification is not message/delivery-status or message/global-delivery-status, ignoring");
return Ok(None);
}
@@ -1469,14 +1447,14 @@ impl MimeMessage {
let (status_fields, _) = mailparse::parse_headers(status_body)?;
if let Some(action) = status_fields.get_first_value("action") {
if action != "failed" {
info!(context, "DSN with {:?} action", action);
info!("DSN with {:?} action", action);
failure = false;
}
} else {
warn!(context, "DSN without action");
warn!("DSN without action");
}
} else {
warn!(context, "DSN without per-recipient fields");
warn!("DSN without per-recipient fields");
}
} else {
// No message/delivery-status part.
@@ -1513,7 +1491,6 @@ impl MimeMessage {
}
warn!(
context,
"ignoring unknown ndn-notification, Message-Id: {:?}",
report_fields.get_header_value(HeaderDef::MessageId)
);
@@ -1638,7 +1615,7 @@ impl MimeMessage {
}
Ok(None) => {}
Err(err) => {
warn!(context, "failed to handle_mdn: {:#}", err);
warn!("failed to handle_mdn: {:#}", err);
}
}
}
@@ -1651,7 +1628,7 @@ impl MimeMessage {
.find(|p| p.typ == Viewtype::Text)
.map(|p| p.msg.clone());
if let Err(e) = message::handle_ndn(context, delivery_report, error).await {
warn!(context, "Could not handle ndn: {}", e);
warn!("Could not handle ndn: {}", e);
}
}
}
@@ -1699,7 +1676,7 @@ async fn update_gossip_peerstates(
let header = match value.parse::<Aheader>() {
Ok(header) => header,
Err(err) => {
warn!(context, "Failed parsing Autocrypt-Gossip header: {}", err);
warn!("Failed parsing Autocrypt-Gossip header: {}", err);
continue;
}
};
@@ -1709,16 +1686,16 @@ async fn update_gossip_peerstates(
.any(|info| addr_cmp(&info.addr, &header.addr))
{
warn!(
context,
"Ignoring gossiped \"{}\" as the address is not in To/Cc list.", &header.addr,
"Ignoring gossiped \"{}\" as the address is not in To/Cc list.",
&header.addr,
);
continue;
}
if addr_cmp(from, &header.addr) {
// Non-standard, but anyway we can't update the cached peerstate here.
warn!(
context,
"Ignoring gossiped \"{}\" as it equals the From address", &header.addr,
"Ignoring gossiped \"{}\" as it equals the From address",
&header.addr,
);
continue;
}
@@ -1909,10 +1886,7 @@ fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
/// returned. If Content-Disposition is "attachment" but filename is
/// not specified, filename is guessed. If Content-Disposition cannot
/// be parsed, returns an error.
fn get_attachment_filename(
context: &Context,
mail: &mailparse::ParsedMail,
) -> Result<Option<String>> {
fn get_attachment_filename(mail: &mailparse::ParsedMail) -> Result<Option<String>> {
let ct = mail.get_content_disposition();
// try to get file name as "encoded-words" from
@@ -1924,7 +1898,7 @@ fn get_attachment_filename(
// be graceful and just use the original name.
// some MUA, including Delta Chat up to core1.50,
// use `filename*` mistakenly for simple encoded-words without following rfc2231
warn!(context, "apostrophed encoding invalid: {}", name);
warn!("apostrophed encoding invalid: {}", name);
desired_filename = Some(name);
}
}
@@ -1976,10 +1950,10 @@ pub(crate) fn get_list_post(headers: &[MailHeader]) -> Option<String> {
.map(|s| s.addr)
}
fn get_all_addresses_from_header<F>(headers: &[MailHeader], pred: F) -> Vec<SingleInfo>
where
F: Fn(String) -> bool,
{
fn get_all_addresses_from_header(
headers: &[MailHeader],
pred: fn(String) -> bool,
) -> Vec<SingleInfo> {
let mut result: Vec<SingleInfo> = Default::default();
headers
@@ -2154,147 +2128,123 @@ mod tests {
assert!(is_attachment_disposition(&mail.subparts[1]));
}
fn load_mail_with_attachment<'a>(t: &'a TestContext, raw: &'a [u8]) -> ParsedMail<'a> {
fn load_mail_with_attachment(raw: &[u8]) -> ParsedMail<'_> {
let mail = mailparse::parse_mail(raw).unwrap();
assert!(get_attachment_filename(t, &mail).unwrap().is_none());
assert!(get_attachment_filename(t, &mail.subparts[0])
assert!(get_attachment_filename(&mail).unwrap().is_none());
assert!(get_attachment_filename(&mail.subparts[0])
.unwrap()
.is_none());
mail
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_simple.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_simple.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("test.html".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_encoded_words() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_encoded_words.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_encoded_words() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_encoded_words.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("Maßnahmen Okt. 2020.html".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_encoded_words_binary() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_encoded_words_binary.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_encoded_words_binary() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_encoded_words_binary.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some(" § 165 Abs".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_encoded_words_windows1251() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_encoded_words_windows1251.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_encoded_words_windows1251() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_encoded_words_windows1251.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("file Что нового 2020.pdf".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_encoded_words_cont() {
#[test]
fn test_get_attachment_filename_encoded_words_cont() {
// test continued encoded-words and also test apostropes work that way
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_encoded_words_cont.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_encoded_words_cont.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("Maßn'ah'men Okt. 2020.html".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_encoded_words_bad_delimiter() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_encoded_words_bad_delimiter.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_encoded_words_bad_delimiter() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_encoded_words_bad_delimiter.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
// not decoded as a space is missing after encoded-words part
assert_eq!(filename, Some("=?utf-8?q?foo?=.bar".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_apostrophed() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_apostrophed.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_apostrophed() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_apostrophed.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("Maßnahmen Okt. 2021.html".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_apostrophed_cont() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_apostrophed_cont.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_apostrophed_cont() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_apostrophed_cont.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("Maßnahmen März 2022.html".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_apostrophed_windows1251() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_apostrophed_windows1251.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_apostrophed_windows1251() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_apostrophed_windows1251.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("программирование.HTM".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_apostrophed_cp1252() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_apostrophed_cp1252.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_apostrophed_cp1252() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_apostrophed_cp1252.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("Auftragsbestätigung.pdf".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_apostrophed_invalid() {
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_apostrophed_invalid.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
#[test]
fn test_get_attachment_filename_apostrophed_invalid() {
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_apostrophed_invalid.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("somedäüta.html.zip".to_string()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_attachment_filename_combined() {
#[test]
fn test_get_attachment_filename_combined() {
// test that if `filename` and `filename*0` are given, the filename is not doubled
let t = TestContext::new().await;
let mail = load_mail_with_attachment(
&t,
include_bytes!("../test-data/message/attach_filename_combined.eml"),
);
let filename = get_attachment_filename(&t, &mail.subparts[1]).unwrap();
let mail = load_mail_with_attachment(include_bytes!(
"../test-data/message/attach_filename_combined.eml"
));
let filename = get_attachment_filename(&mail.subparts[1]).unwrap();
assert_eq!(filename, Some("Maßnahmen Okt. 2020.html".to_string()))
}

View File

@@ -8,6 +8,7 @@ use anyhow::{Context as _, Error, Result};
use tokio::net::{lookup_host, TcpStream};
use tokio::time::timeout;
use tokio_io_timeout::TimeoutStream;
use tracing::{info, warn};
use crate::context::Context;
use crate::tools::time;
@@ -50,8 +51,8 @@ async fn lookup_host_with_cache(
Ok(res) => res,
Err(err) => {
warn!(
context,
"DNS resolution for {}:{} failed: {:#}.", hostname, port, err
"DNS resolution for {}:{} failed: {:#}.",
hostname, port, err
);
Vec::new()
}
@@ -64,7 +65,7 @@ async fn lookup_host_with_cache(
continue;
}
info!(context, "Resolved {}:{} into {}.", hostname, port, &addr);
info!("Resolved {}:{} into {}.", hostname, port, &addr);
// Update the cache.
context
@@ -110,8 +111,8 @@ async fn lookup_host_with_cache(
}
Err(err) => {
warn!(
context,
"Failed to parse cached address {:?}: {:#}.", cached_address, err
"Failed to parse cached address {:?}: {:#}.",
cached_address, err
);
}
}
@@ -163,10 +164,7 @@ pub(crate) async fn connect_tcp(
break;
}
Err(err) => {
warn!(
context,
"Failed to connect to {}: {:#}.", resolved_addr, err
);
warn!("Failed to connect to {}: {:#}.", resolved_addr, err);
last_error = Some(err);
}
}

View File

@@ -5,6 +5,7 @@ use std::collections::HashMap;
use anyhow::Result;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use serde::Deserialize;
use tracing::{info, warn};
use crate::config::Config;
use crate::context::Context;
@@ -62,7 +63,7 @@ pub async fn get_oauth2_url(
redirect_uri: &str,
) -> Result<Option<String>> {
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
context
.sql
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
@@ -83,7 +84,7 @@ pub(crate) async fn get_oauth2_access_token(
regenerate: bool,
) -> Result<Option<String>> {
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
let lock = context.oauth2_mutex.lock().await;
// read generated token
@@ -105,7 +106,7 @@ pub(crate) async fn get_oauth2_access_token(
let (redirect_uri, token_url, update_redirect_uri_on_success) =
if refresh_token.is_none() || refresh_token_for != code {
info!(context, "Generate OAuth2 refresh_token and access_token...",);
info!("Generate OAuth2 refresh_token and access_token...",);
(
context
.sql
@@ -116,10 +117,7 @@ pub(crate) async fn get_oauth2_access_token(
true,
)
} else {
info!(
context,
"Regenerate OAuth2 access_token by refresh_token...",
);
info!("Regenerate OAuth2 access_token by refresh_token...",);
(
context
.sql
@@ -167,14 +165,14 @@ pub(crate) async fn get_oauth2_access_token(
Ok(response) => response,
Err(err) => {
warn!(
context,
"Failed to parse OAuth2 JSON response from {}: error: {}", token_url, err
"Failed to parse OAuth2 JSON response from {}: error: {}",
token_url, err
);
return Ok(None);
}
},
Err(err) => {
warn!(context, "Error calling OAuth2 at {}: {:?}", token_url, err);
warn!("Error calling OAuth2 at {}: {:?}", token_url, err);
return Ok(None);
}
};
@@ -215,14 +213,14 @@ pub(crate) async fn get_oauth2_access_token(
.await?;
}
} else {
warn!(context, "Failed to find OAuth2 access token");
warn!("Failed to find OAuth2 access token");
}
drop(lock);
Ok(response.access_token)
} else {
warn!(context, "Internal OAuth2 error: 2");
warn!("Internal OAuth2 error: 2");
Ok(None)
}
@@ -234,7 +232,7 @@ pub(crate) async fn get_oauth2_addr(
code: &str,
) -> Result<Option<String>> {
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
let oauth2 = match Oauth2::from_address(context, addr, socks5_enabled).await {
let oauth2 = match Oauth2::from_address(addr, socks5_enabled).await {
Some(o) => o,
None => return Ok(None),
};
@@ -260,13 +258,13 @@ pub(crate) async fn get_oauth2_addr(
}
impl Oauth2 {
async fn from_address(context: &Context, addr: &str, skip_mx: bool) -> Option<Self> {
async fn from_address(addr: &str, skip_mx: bool) -> Option<Self> {
let addr_normalized = normalize_addr(addr);
if let Some(domain) = addr_normalized
.find('@')
.map(|index| addr_normalized.split_at(index + 1).1)
{
if let Some(oauth2_authorizer) = provider::get_provider_info(context, domain, skip_mx)
if let Some(oauth2_authorizer) = provider::get_provider_info(domain, skip_mx)
.await
.and_then(|provider| provider.oauth2_authorizer.as_ref())
{
@@ -294,14 +292,14 @@ impl Oauth2 {
let client = match crate::http::get_client(socks5_config) {
Ok(cl) => cl,
Err(err) => {
warn!(context, "failed to get HTTP client: {}", err);
warn!("failed to get HTTP client: {}", err);
return None;
}
};
let response = match client.get(userinfo_url).send().await {
Ok(response) => response,
Err(err) => {
warn!(context, "failed to get userinfo: {}", err);
warn!("failed to get userinfo: {}", err);
return None;
}
};
@@ -309,7 +307,7 @@ impl Oauth2 {
let parsed = match response {
Ok(parsed) => parsed,
Err(err) => {
warn!(context, "Error getting userinfo: {}", err);
warn!("Error getting userinfo: {}", err);
return None;
}
};
@@ -319,11 +317,11 @@ impl Oauth2 {
if let Some(s) = addr.as_str() {
Some(s.to_string())
} else {
warn!(context, "E-mail in userinfo is not a string: {}", addr);
warn!("E-mail in userinfo is not a string: {}", addr);
None
}
} else {
warn!(context, "E-mail missing in userinfo.");
warn!("E-mail missing in userinfo.");
None
}
}
@@ -377,39 +375,34 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_oauth_from_address() {
let t = TestContext::new().await;
assert_eq!(
Oauth2::from_address(&t, "hello@gmail.com", false).await,
Oauth2::from_address("hello@gmail.com", false).await,
Some(OAUTH2_GMAIL)
);
assert_eq!(
Oauth2::from_address(&t, "hello@googlemail.com", false).await,
Oauth2::from_address("hello@googlemail.com", false).await,
Some(OAUTH2_GMAIL)
);
assert_eq!(
Oauth2::from_address(&t, "hello@yandex.com", false).await,
Oauth2::from_address("hello@yandex.com", false).await,
Some(OAUTH2_YANDEX)
);
assert_eq!(
Oauth2::from_address(&t, "hello@yandex.ru", false).await,
Oauth2::from_address("hello@yandex.ru", false).await,
Some(OAUTH2_YANDEX)
);
assert_eq!(Oauth2::from_address(&t, "hello@web.de", false).await, None);
assert_eq!(Oauth2::from_address("hello@web.de", false).await, None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_oauth_from_mx() {
// youtube staff seems to use "google workspace with oauth2", figures this out by MX lookup
let t = TestContext::new().await;
assert_eq!(
Oauth2::from_address(&t, "hello@youtube.com", false).await,
Oauth2::from_address("hello@youtube.com", false).await,
Some(OAUTH2_GMAIL)
);
// without MX lookup, we would not know as youtube.com is not in our provider-db
assert_eq!(
Oauth2::from_address(&t, "hello@youtube.com", true).await,
None
);
assert_eq!(Oauth2::from_address("hello@youtube.com", true).await, None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

View File

@@ -4,6 +4,7 @@ use std::collections::HashSet;
use anyhow::{Context as _, Error, Result};
use num_traits::FromPrimitive;
use tracing::warn;
use crate::aheader::{Aheader, EncryptPreference};
use crate::chat::{self, Chat};
@@ -594,10 +595,8 @@ impl Peerstate {
}
Err(err) => {
warn!(
context,
"New address {:?} is not valid, not doing AEAP: {:#}.",
new_addr,
err
new_addr, err
)
}
}

View File

@@ -4,10 +4,10 @@ mod data;
use anyhow::Result;
use chrono::{NaiveDateTime, NaiveTime};
use tracing::warn;
use trust_dns_resolver::{config, AsyncResolver, TokioAsyncResolver};
use crate::config::Config;
use crate::context::Context;
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS, PROVIDER_UPDATED};
/// Provider status according to manual testing.
@@ -184,11 +184,7 @@ fn get_resolver() -> Result<TokioAsyncResolver> {
///
/// For compatibility, email address can be passed to this function
/// instead of the domain.
pub async fn get_provider_info(
context: &Context,
domain: &str,
skip_mx: bool,
) -> Option<&'static Provider> {
pub async fn get_provider_info(domain: &str, skip_mx: bool) -> Option<&'static Provider> {
let domain = domain.rsplit('@').next()?;
if let Some(provider) = get_provider_by_domain(domain) {
@@ -196,7 +192,7 @@ pub async fn get_provider_info(
}
if !skip_mx {
if let Some(provider) = get_provider_by_mx(context, domain).await {
if let Some(provider) = get_provider_by_mx(domain).await {
return Some(provider);
}
}
@@ -216,7 +212,7 @@ pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
/// Finds a provider based on MX record for the given domain.
///
/// For security reasons, only Gmail can be configured this way.
pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'static Provider> {
pub async fn get_provider_by_mx(domain: &str) -> Option<&'static Provider> {
if let Ok(resolver) = get_resolver() {
let mut fqdn: String = domain.to_string();
if !fqdn.ends_with('.') {
@@ -243,7 +239,7 @@ pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'sta
}
}
} else {
warn!(context, "cannot get a resolver to check MX records.");
warn!("cannot get a resolver to check MX records.");
}
None
@@ -272,7 +268,6 @@ mod tests {
use chrono::NaiveDate;
use super::*;
use crate::test_utils::TestContext;
use crate::tools::time;
#[test]
@@ -322,13 +317,12 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_provider_info() {
let t = TestContext::new().await;
assert!(get_provider_info(&t, "", false).await.is_none());
assert!(get_provider_info(&t, "google.com", false).await.unwrap().id == "gmail");
assert!(get_provider_info("", false).await.is_none());
assert!(get_provider_info("google.com", false).await.unwrap().id == "gmail");
// get_provider_info() accepts email addresses for backwards compatibility
assert!(
get_provider_info(&t, "example@google.com", false)
get_provider_info("example@google.com", false)
.await
.unwrap()
.id

View File

@@ -8,6 +8,7 @@ pub use dclogin_scheme::LoginOptions;
use once_cell::sync::Lazy;
use percent_encoding::percent_decode_str;
use serde::Deserialize;
use tracing::info;
use self::dclogin_scheme::configure_from_login_qr;
use crate::chat::{self, get_chat_id_by_grpid, ChatIdBlocked};
@@ -247,7 +248,7 @@ fn starts_with_ignore_case(string: &str, pattern: &str) -> bool {
/// The function should be called after a QR code is scanned.
/// The function takes the raw text scanned and checks what can be done with it.
pub async fn check_qr(context: &Context, qr: &str) -> Result<Qr> {
info!(context, "Scanned QR code: {}", qr);
info!("Scanned QR code: {}", qr);
let qrcode = if starts_with_ignore_case(qr, OPENPGP4FPR_SCHEME) {
decode_openpgp(context, qr)

View File

@@ -5,6 +5,7 @@ use std::sync::atomic::Ordering;
use anyhow::{anyhow, Context as _, Result};
use async_imap::types::{Quota, QuotaResource};
use tracing::warn;
use crate::chat::add_device_msg_with_importance;
use crate::config::Config;
@@ -134,7 +135,7 @@ impl Context {
/// Called in response to `Action::UpdateRecentQuota`.
pub(crate) async fn update_recent_quota(&self, imap: &mut Imap) -> Result<()> {
if let Err(err) = imap.prepare(self).await {
warn!(self, "could not connect: {:#}", err);
warn!("could not connect: {err:#}");
return Ok(());
}
@@ -162,7 +163,7 @@ impl Context {
self.set_config(Config::QuotaExceeding, None).await?;
}
}
Err(err) => warn!(self, "cannot get highest quota usage: {:#}", err),
Err(err) => warn!("cannot get highest quota usage: {:#}", err),
}
}

View File

@@ -18,6 +18,7 @@ use std::collections::BTreeMap;
use std::fmt;
use anyhow::Result;
use tracing::info;
use crate::chat::{send_msg, ChatId};
use crate::contact::ContactId;
@@ -232,8 +233,8 @@ pub(crate) async fn set_msg_reaction(
set_msg_id_reaction(context, msg_id, chat_id, contact_id, reaction).await
} else {
info!(
context,
"Can't assign reaction to unknown message with Message-ID {}", in_reply_to
"Can't assign reaction to unknown message with Message-ID {}",
in_reply_to
);
Ok(())
}

View File

@@ -9,6 +9,7 @@ use mailparse::{parse_mail, SingleInfo};
use num_traits::FromPrimitive;
use once_cell::sync::Lazy;
use regex::Regex;
use tracing::{info, warn};
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
use crate::config::Config;
@@ -96,11 +97,10 @@ pub(crate) async fn receive_imf_inner(
is_partial_download: Option<u32>,
fetching_existing_messages: bool,
) -> Result<Option<ReceivedMsg>> {
info!(context, "Receiving message, seen={}...", seen);
info!("Receiving message, seen={seen}...");
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(
context,
"receive_imf: incoming message mime-body:\n{}",
String::from_utf8_lossy(imf_raw),
);
@@ -109,7 +109,7 @@ pub(crate) async fn receive_imf_inner(
let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw, is_partial_download).await
{
Err(err) => {
warn!(context, "receive_imf: can't parse MIME: {:#}", err);
warn!("receive_imf: can't parse MIME: {err:#}.");
let msg_ids;
if !rfc724_mid.starts_with(GENERATED_PREFIX) {
let row_id = context
@@ -138,11 +138,11 @@ pub(crate) async fn receive_imf_inner(
// we can not add even an empty record if we have no info whatsoever
if !mime_parser.has_headers() {
warn!(context, "receive_imf: no headers found");
warn!("receive_imf: no headers found.");
return Ok(None);
}
info!(context, "received message has Message-Id: {}", rfc724_mid);
info!("Received message has Message-Id: {rfc724_mid}");
// check, if the mail is already in our database.
// make sure, this check is done eg. before securejoin-processing.
@@ -151,14 +151,11 @@ pub(crate) async fn receive_imf_inner(
let msg = Message::load_from_db(context, old_msg_id).await?;
if msg.download_state() != DownloadState::Done && is_partial_download.is_none() {
// the message was partially downloaded before and is fully downloaded now.
info!(
context,
"Message already partly in DB, replacing by full message."
);
info!("Message already partly in DB, replacing by full message.");
Some(old_msg_id)
} else {
// the message was probably moved around.
info!(context, "Message already in DB, doing nothing.");
info!("Message already in DB, doing nothing.");
return Ok(None);
}
} else {
@@ -180,10 +177,7 @@ pub(crate) async fn receive_imf_inner(
match from_field_to_contact_id(context, &mime_parser.from, prevent_rename).await? {
Some(contact_id_res) => contact_id_res,
None => {
warn!(
context,
"receive_imf: From field does not contain an acceptable address"
);
warn!("receive_imf: From field does not contain an acceptable address.");
return Ok(None);
}
};
@@ -246,10 +240,7 @@ pub(crate) async fn receive_imf_inner(
.iter()
.all(|recipient| mime_parser.gossiped_addr.contains(&recipient.addr))
{
info!(
context,
"Received message contains Autocrypt-Gossip for all members, updating timestamp."
);
info!("Received message contains Autocrypt-Gossip for all members, updating timestamp.");
if chat_id.get_gossiped_timestamp(context).await? < sent_timestamp {
chat_id
.set_gossiped_timestamp(context, sent_timestamp)
@@ -269,13 +260,13 @@ pub(crate) async fn receive_imf_inner(
if from_id == ContactId::SELF {
if mime_parser.was_encrypted() {
if let Err(err) = context.execute_sync_items(sync_items).await {
warn!(context, "receive_imf cannot execute sync items: {:#}", err);
warn!("receive_imf cannot execute sync items: {err:#}.");
}
} else {
warn!(context, "sync items are not encrypted.");
warn!("Sync items are not encrypted.");
}
} else {
warn!(context, "sync items not sent by self.");
warn!("Sync items not sent by self.");
}
}
@@ -284,7 +275,7 @@ pub(crate) async fn receive_imf_inner(
.receive_status_update(from_id, insert_msg_id, status_update)
.await
{
warn!(context, "receive_imf cannot update status: {:#}", err);
warn!("receive_imf cannot update status: {err:#}.");
}
}
@@ -302,10 +293,7 @@ pub(crate) async fn receive_imf_inner(
)
.await
{
warn!(
context,
"receive_imf cannot update profile image: {:#}", err
);
warn!("receive_imf cannot update profile image: {err:#}.");
};
}
}
@@ -331,7 +319,7 @@ pub(crate) async fn receive_imf_inner(
)
.await
{
warn!(context, "cannot update contact status: {:#}", err);
warn!("Cannot update contact status: {err:#}.");
}
}
@@ -392,10 +380,7 @@ pub async fn from_field_to_contact_id(
let from_addr = match ContactAddress::new(&from.addr) {
Ok(from_addr) => from_addr,
Err(err) => {
warn!(
context,
"Cannot create a contact for the given From field: {:#}.", err
);
warn!("Cannot create a contact for the given From field: {err:#}.");
return Ok(None);
}
};
@@ -476,7 +461,7 @@ async fn add_parts(
// this message is a classic email not a chat-message nor a reply to one
match show_emails {
ShowEmails::Off => {
info!(context, "Classical email not shown (TRASH)");
info!("Classical email not shown (TRASH).");
chat_id = Some(DC_CHAT_ID_TRASH);
allow_creation = false;
}
@@ -519,7 +504,7 @@ async fn add_parts(
securejoin_seen = false;
}
Err(err) => {
warn!(context, "Error in Secure-Join message handling: {:#}", err);
warn!("Error in Secure-Join message handling: {err:#}.");
chat_id = Some(DC_CHAT_ID_TRASH);
securejoin_seen = true;
}
@@ -536,7 +521,7 @@ async fn add_parts(
if chat_id.is_none() && mime_parser.delivery_report.is_some() {
chat_id = Some(DC_CHAT_ID_TRASH);
info!(context, "Message is a DSN (TRASH)",);
info!("Message is a DSN (TRASH).",);
}
if chat_id.is_none() {
@@ -710,10 +695,7 @@ async fn add_parts(
// the contact requests will pop up and this should be just fine.
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo)
.await?;
info!(
context,
"Message is a reply to a known message, mark sender as known.",
);
info!("Message is a reply to a known message, mark sender as known.",);
}
}
}
@@ -754,7 +736,7 @@ async fn add_parts(
chat_id = None;
}
Err(err) => {
warn!(context, "Error in Secure-Join watching: {:#}", err);
warn!("Error in Secure-Join watching: {err:#}.");
chat_id = Some(DC_CHAT_ID_TRASH);
}
}
@@ -771,7 +753,7 @@ async fn add_parts(
if is_draft {
// Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
info!(context, "Email is probably just a draft (TRASH)");
info!("Email is probably just a draft (TRASH).");
chat_id = Some(DC_CHAT_ID_TRASH);
}
@@ -863,14 +845,14 @@ async fn add_parts(
if fetching_existing_messages && mime_parser.decrypting_failed {
chat_id = Some(DC_CHAT_ID_TRASH);
// We are only gathering old messages on first start. We do not want to add loads of non-decryptable messages to the chats.
info!(context, "Existing non-decipherable message. (TRASH)");
info!("Existing non-decipherable message (TRASH).");
}
if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
if let Some(part) = mime_parser.parts.first() {
if part.typ == Viewtype::Text && part.msg.is_empty() {
chat_id = Some(DC_CHAT_ID_TRASH);
info!(context, "Message is a status update only (TRASH)");
info!("Message is a status update only (TRASH).");
}
}
}
@@ -880,7 +862,7 @@ async fn add_parts(
DC_CHAT_ID_TRASH
} else {
chat_id.unwrap_or_else(|| {
info!(context, "No chat id for message (TRASH)");
info!("No chat id for message (TRASH).");
DC_CHAT_ID_TRASH
})
};
@@ -892,10 +874,7 @@ async fn add_parts(
match value.parse::<EphemeralTimer>() {
Ok(timer) => timer,
Err(err) => {
warn!(
context,
"can't parse ephemeral timer \"{}\": {:#}", value, err
);
warn!("Can't parse ephemeral timer \"{value}\": {err:#}.");
EphemeralTimer::Disabled
}
}
@@ -915,12 +894,7 @@ async fn add_parts(
&& !mime_parser.parts.is_empty()
&& chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
{
info!(
context,
"received new ephemeral timer value {:?} for chat {}, checking if it should be applied",
ephemeral_timer,
chat_id
);
info!("Received new ephemeral timer value {ephemeral_timer:?} for chat {chat_id}, checking if it should be applied.");
if is_dc_message == MessengerMessage::Yes
&& get_previous_message(context, mime_parser)
.await?
@@ -935,10 +909,7 @@ async fn add_parts(
// value is different, it means the sender has not received some timer update that we
// have seen or sent ourselves, so we ignore incoming timer to prevent a rollback.
warn!(
context,
"ignoring ephemeral timer change to {:?} for chat {} to avoid rollback",
ephemeral_timer,
chat_id
"Ignoring ephemeral timer change to {ephemeral_timer:?} for chat {chat_id} to avoid rollback.",
);
} else if chat_id
.update_timestamp(context, Param::EphemeralSettingsTimestamp, sent_timestamp)
@@ -948,15 +919,9 @@ async fn add_parts(
.inner_set_ephemeral_timer(context, ephemeral_timer)
.await
{
warn!(
context,
"failed to modify timer for chat {}: {:#}", chat_id, err
);
warn!("Failed to modify timer for chat {chat_id}: {err:#}.");
} else {
info!(
context,
"updated ephemeral timer to {:?} for chat {}", ephemeral_timer, chat_id
);
info!("Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}.");
if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
chat::add_info_msg(
context,
@@ -968,10 +933,7 @@ async fn add_parts(
}
}
} else {
warn!(
context,
"ignoring ephemeral timer change to {:?} because it's outdated", ephemeral_timer
);
warn!("Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated.");
}
}
@@ -999,7 +961,7 @@ async fn add_parts(
if chat.is_protected() || new_status.is_some() {
if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await
{
warn!(context, "verification problem: {:#}", err);
warn!("Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
} else {
@@ -1256,10 +1218,7 @@ SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id,
chat_id.unarchive_if_not_muted(context, state).await?;
info!(
context,
"Message has {icnt} parts and is assigned to chat #{chat_id}."
);
info!("Message has {icnt} parts and is assigned to chat #{chat_id}.");
// new outgoing message from another device marks the chat as noticed.
if !incoming && !chat_id.is_special() {
@@ -1340,7 +1299,6 @@ async fn save_locations(
}
} else {
warn!(
context,
"Address in location.kml {:?} is not the same as the sender address {:?}.",
addr,
contact.get_addr()
@@ -1428,8 +1386,8 @@ async fn lookup_chat_by_reply(
}
info!(
context,
"Assigning message to {} as it's a reply to {}", parent_chat.id, parent.rfc724_mid
"Assigning message to {} as it's a reply to {}.",
parent_chat.id, parent.rfc724_mid
);
return Ok(Some((parent_chat.id, parent_chat.blocked)));
}
@@ -1499,7 +1457,7 @@ async fn create_or_lookup_group(
.map(|chat_id| (chat_id, create_blocked));
return Ok(res);
} else {
info!(context, "creating ad-hoc group prevented from caller");
info!("Creating ad-hoc group prevented from caller.");
return Ok(None);
};
@@ -1525,7 +1483,7 @@ async fn create_or_lookup_group(
let create_protected = if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await {
warn!(context, "verification problem: {:#}", err);
warn!("Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
}
@@ -1557,7 +1515,7 @@ async fn create_or_lookup_group(
{
// Group does not exist but should be created.
if !allow_creation {
info!(context, "creating group forbidden by caller");
info!("Creating group forbidden by caller.");
return Ok(None);
}
@@ -1602,6 +1560,7 @@ async fn create_or_lookup_group(
// .await?;
//}
info!("Chat {} is created.", new_chat_id);
context.emit_event(EventType::ChatModified(new_chat_id));
}
@@ -1618,7 +1577,7 @@ async fn create_or_lookup_group(
} else {
// The message was decrypted successfully, but contains a late "quit" or otherwise
// unwanted message.
info!(context, "message belongs to unwanted group (TRASH)");
info!("Message belongs to unwanted group (TRASH).");
Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
}
}
@@ -1658,7 +1617,7 @@ async fn apply_group_changes(
Some(stock_str::msg_del_member(context, &removed_addr, from_id).await)
};
}
None => warn!(context, "removed {:?} has no contact_id", removed_addr),
None => warn!("Removed {removed_addr:?} has no contact_id."),
}
} else {
removed_id = None;
@@ -1683,7 +1642,7 @@ async fn apply_group_changes(
.update_timestamp(context, Param::GroupNameTimestamp, sent_timestamp)
.await?
{
info!(context, "updating grpname for chat {}", chat_id);
info!("Updating grpname for chat {chat_id}.");
context
.sql
.execute(
@@ -1723,7 +1682,7 @@ async fn apply_group_changes(
if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await {
warn!(context, "verification problem: {:#}", err);
warn!("Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
}
@@ -1742,10 +1701,7 @@ async fn apply_group_changes(
&& !chat::is_contact_in_chat(context, chat_id, from_id).await?
{
warn!(
context,
"Contact {} attempts to modify group chat {} member list without being a member.",
from_id,
chat_id
"Contact {from_id} attempts to modify group chat {chat_id} member list without being a member."
);
} else if chat_id
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
@@ -1778,10 +1734,7 @@ async fn apply_group_changes(
}
members_to_add.dedup();
info!(
context,
"adding {:?} to chat id={}", members_to_add, chat_id
);
info!("Adding {members_to_add:?} to chat id={chat_id}.");
chat::add_to_chat_contacts_table(context, chat_id, &members_to_add).await?;
send_event_chat_modified = true;
}
@@ -1789,19 +1742,13 @@ async fn apply_group_changes(
if let Some(avatar_action) = &mime_parser.group_avatar {
if !chat::is_contact_in_chat(context, chat_id, ContactId::SELF).await? {
warn!(
context,
"Received group avatar update for group chat {} we are not a member of.", chat_id
);
warn!("Received group avatar update for group chat {chat_id} we are not a member of.");
} else if !chat::is_contact_in_chat(context, chat_id, from_id).await? {
warn!(
context,
"Contact {} attempts to modify group chat {} avatar without being a member.",
from_id,
chat_id
"Contact {from_id} attempts to modify group chat {chat_id} avatar without being a member.",
);
} else {
info!(context, "group-avatar change for {}", chat_id);
info!("Group-avatar change for {chat_id}.");
if chat
.param
.update_timestamp(Param::AvatarTimestamp, sent_timestamp)?
@@ -1935,7 +1882,7 @@ async fn create_or_lookup_mailinglist(
.await
.with_context(|| {
format!(
"Failed to create mailinglist '{}' for grpid={}",
"failed to create mailinglist '{}' for grpid={}",
&name, &listid
)
})?;
@@ -1943,7 +1890,7 @@ async fn create_or_lookup_mailinglist(
chat::add_to_chat_contacts_table(context, chat_id, &[ContactId::SELF]).await?;
Ok(Some((chat_id, blocked)))
} else {
info!(context, "creating list forbidden by caller");
info!("Creating list forbidden by caller.");
Ok(None)
}
}
@@ -1966,7 +1913,7 @@ async fn apply_mailinglist_changes(
let list_post = match ContactAddress::new(list_post) {
Ok(list_post) => list_post,
Err(err) => {
warn!(context, "Invalid List-Post: {:#}.", err);
warn!("Invalid List-Post: {:#}.", err);
return Ok(());
}
};
@@ -2033,10 +1980,7 @@ async fn create_adhoc_group(
member_ids: &[ContactId],
) -> Result<Option<ChatId>> {
if mime_parser.is_mailinglist_message() {
info!(
context,
"not creating ad-hoc group for mailing list message"
);
info!("Not creating ad-hoc group for mailing list message.");
return Ok(None);
}
@@ -2050,15 +1994,12 @@ async fn create_adhoc_group(
// Chat-Group-ID and incompatible Message-ID format.
//
// Instead, assign the message to 1:1 chat with the sender.
warn!(
context,
"not creating ad-hoc group for message that cannot be decrypted"
);
warn!("Not creating ad-hoc group for message that cannot be decrypted.");
return Ok(None);
}
if member_ids.len() < 3 {
info!(context, "not creating ad-hoc group: too few contacts");
info!("Not creating ad-hoc group: too few contacts.");
return Ok(None);
}
@@ -2098,11 +2039,7 @@ async fn check_verified_properties(
// we do not fail here currently, this would exclude (a) non-deltas
// and (b) deltas with different protection views across multiple devices.
// for group creation or protection enabled/disabled, however, Chat-Verified is respected.
warn!(
context,
"{} did not mark message as protected.",
contact.get_addr()
);
warn!("{} did not mark message as protected.", contact.get_addr());
}
// ensure, the contact is verified
@@ -2164,8 +2101,7 @@ async fn check_verified_properties(
for (to_addr, mut is_verified) in rows {
info!(
context,
"check_verified_properties: {:?} self={:?}",
"check_verified_properties: {:?} self={:?}.",
to_addr,
context.is_self_addr(&to_addr).await
);
@@ -2183,7 +2119,7 @@ async fn check_verified_properties(
|| peerstate.verified_key_fingerprint != peerstate.public_key_fingerprint
&& peerstate.verified_key_fingerprint != peerstate.gossip_key_fingerprint
{
info!(context, "{} has verified {}.", contact.get_addr(), to_addr);
info!("{} has verified {}.", contact.get_addr(), to_addr);
let fp = peerstate.gossip_key_fingerprint.clone();
if let Some(fp) = fp {
peerstate.set_verified(
@@ -2315,7 +2251,7 @@ async fn add_or_lookup_contacts_by_address_list(
add_or_lookup_contact_by_addr(context, display_name, addr, origin).await?;
contact_ids.insert(contact_id);
} else {
warn!(context, "Contact with address {:?} cannot exist.", addr);
warn!("Contact with address {:?} cannot exist.", addr);
}
}

View File

@@ -1,4 +1,6 @@
use tokio::fs;
use tracing_futures::WithSubscriber;
use tracing_subscriber::{prelude::*, registry::Registry};
use super::*;
use crate::aheader::EncryptPreference;
@@ -1340,8 +1342,12 @@ async fn test_apply_mailinglist_changes_assigned_by_reply() {
let chat = Chat::load_from_db(&t, chat_id).await.unwrap();
assert!(chat.can_send(&t).await.unwrap());
let subscriber = Registry::default().with(t.to_layer());
let imf_raw = format!("In-Reply-To: 3333@example.org\n{GH_MAILINGLIST2}");
receive_imf(&t, imf_raw.as_bytes(), false).await.unwrap();
receive_imf(&t, imf_raw.as_bytes(), false)
.with_subscriber(subscriber)
.await
.unwrap();
assert_eq!(
t.get_last_msg().await.in_reply_to.unwrap(),

View File

@@ -5,14 +5,19 @@ use anyhow::{bail, Context as _, Result};
use async_channel::{self as channel, Receiver, Sender};
use futures::future::try_join_all;
use futures_lite::FutureExt;
use tokio::sync::{RwLock, RwLockWriteGuard};
use tokio::sync::{oneshot, RwLock, RwLockWriteGuard};
use tokio::task;
use tracing::instrument::Instrument;
use tracing::{error, info, warn};
use tracing_futures::WithSubscriber;
use self::connectivity::ConnectivityStore;
use crate::config::Config;
use crate::contact::{ContactId, RecentlySeenLoop};
use crate::context::future::ContextIdFutureExt;
use crate::context::Context;
use crate::ephemeral::{self, delete_expired_imap_messages};
use crate::events::EventType;
use crate::imap::{FolderMeaning, Imap};
use crate::job;
use crate::location;
@@ -56,11 +61,10 @@ impl SchedulerState {
/// Starts the scheduler if it is not yet started.
async fn do_start(mut inner: RwLockWriteGuard<'_, InnerSchedulerState>, context: Context) {
info!(context, "starting IO");
let ctx = context.clone();
info!("Starting IO.");
match Scheduler::start(context).await {
Ok(scheduler) => inner.scheduler = Some(scheduler),
Err(err) => error!(&ctx, "Failed to start IO: {:#}", err),
Err(err) => error!("Failed to start IO: {err:#}."),
}
}
@@ -78,7 +82,7 @@ impl SchedulerState {
// For this, the caller needs to instruct the event poller
// to terminate on receiving the next event and then call stop_io()
// which will emit the below event(s)
info!(context, "stopping IO");
context.emit_event(EventType::Info("Stopping IO.".to_string()));
if let Some(debug_logging) = context.debug_logging.read().await.as_ref() {
debug_logging.loop_handle.abort();
}
@@ -89,25 +93,33 @@ impl SchedulerState {
/// Pauses the IO scheduler.
///
/// If it is currently running the scheduler will be stopped. When
/// [`IoPausedGuard::resume`] is called the scheduler is started again.
/// If it is currently running the scheduler will be stopped. When the
/// [`IoPausedGuard`] is dropped the scheduler is started again.
///
/// If in the meantime [`SchedulerState::start`] or [`SchedulerState::stop`] is called
/// resume will do the right thing and restore the scheduler to the state requested by
/// the last call.
pub(crate) async fn pause<'a>(&'_ self, context: Context) -> IoPausedGuard {
let mut inner = self.inner.write().await;
inner.paused = true;
Self::do_stop(inner, &context).await;
IoPausedGuard {
context,
done: false,
{
let mut inner = self.inner.write().await;
inner.paused = true;
Self::do_stop(inner, &context).await;
}
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
rx.await.ok();
let mut inner = context.scheduler.inner.write().await;
inner.paused = false;
if inner.started && inner.scheduler.is_none() {
SchedulerState::do_start(inner, context.clone()).await;
}
});
IoPausedGuard { sender: Some(tx) }
}
/// Restarts the scheduler, only if it is running.
pub(crate) async fn restart(&self, context: &Context) {
info!(context, "restarting IO");
info!("Restarting IO.");
if self.is_running().await {
self.stop(context).await;
self.start(context.clone()).await;
@@ -194,31 +206,21 @@ struct InnerSchedulerState {
paused: bool,
}
/// Guard to make sure the IO Scheduler is resumed.
///
/// Returned by [`SchedulerState::pause`]. To resume the IO scheduler simply drop this
/// guard.
#[derive(Debug)]
pub(crate) struct IoPausedGuard {
context: Context,
done: bool,
}
impl IoPausedGuard {
pub(crate) async fn resume(&mut self) {
self.done = true;
let mut inner = self.context.scheduler.inner.write().await;
inner.paused = false;
if inner.started && inner.scheduler.is_none() {
SchedulerState::do_start(inner, self.context.clone()).await;
}
}
sender: Option<oneshot::Sender<()>>,
}
impl Drop for IoPausedGuard {
fn drop(&mut self) {
if self.done {
return;
if let Some(sender) = self.sender.take() {
// Can only fail if receiver is dropped, but then we're already resumed.
sender.send(()).ok();
}
// Async .resume() should be called manually due to lack of async drop.
error!(self.context, "Pause guard dropped without resuming.");
}
}
@@ -250,7 +252,7 @@ pub(crate) struct Scheduler {
async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConnectionHandlers) {
use futures::future::FutureExt;
info!(ctx, "starting inbox loop");
info!("starting inbox loop");
let ImapConnectionHandlers {
mut connection,
stop_receiver,
@@ -260,7 +262,7 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
let fut = async move {
let ctx = ctx1;
if let Err(err) = started.send(()).await {
warn!(ctx, "inbox loop, missing started receiver: {}", err);
warn!("Inbox loop, missing started receiver: {err:#}.");
return;
};
@@ -268,7 +270,7 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
loop {
let job = match job::load_next(&ctx, &info).await {
Err(err) => {
error!(ctx, "Failed loading job from the database: {:#}.", err);
error!("Failed loading job from the database: {err:#}.");
None
}
Ok(job) => job,
@@ -283,14 +285,14 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
let quota_requested = ctx.quota_update_request.swap(false, Ordering::Relaxed);
if quota_requested {
if let Err(err) = ctx.update_recent_quota(&mut connection).await {
warn!(ctx, "Failed to update quota: {:#}.", err);
warn!("Failed to update quota: {err:#}.");
}
}
let resync_requested = ctx.resync_request.swap(false, Ordering::Relaxed);
if resync_requested {
if let Err(err) = connection.resync_folders(&ctx).await {
warn!(ctx, "Failed to resync folders: {:#}.", err);
warn!("Failed to resync folders: {err:#}.");
ctx.resync_request.store(true, Ordering::Relaxed);
}
}
@@ -306,7 +308,7 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
}
}
Err(err) => {
warn!(ctx, "Failed to get last housekeeping time: {}", err);
warn!("Failed to get last housekeeping time: {err:#}.");
}
};
@@ -320,17 +322,17 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
if let Err(err) =
ctx.set_config_bool(Config::FetchedExistingMsgs, true).await
{
warn!(ctx, "Can't set Config::FetchedExistingMsgs: {:#}", err);
warn!("Can't set Config::FetchedExistingMsgs: {:#}", err);
}
if let Err(err) = connection.fetch_existing_msgs(&ctx).await {
warn!(ctx, "Failed to fetch existing messages: {:#}", err);
connection.trigger_reconnect(&ctx);
warn!("Failed to fetch existing messages: {:#}", err);
connection.trigger_reconnect();
}
}
}
Err(err) => {
warn!(ctx, "Can't get Config::FetchedExistingMsgs: {:#}", err);
warn!("Can't get Config::FetchedExistingMsgs: {:#}", err);
}
}
@@ -343,7 +345,7 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
stop_receiver
.recv()
.map(|_| {
info!(ctx, "shutting down inbox loop");
info!("shutting down inbox loop");
})
.race(fut)
.await;
@@ -363,7 +365,7 @@ async fn fetch_idle(
let folder_config = match folder_meaning.to_config() {
Some(c) => c,
None => {
error!(ctx, "Bad folder meaning: {}", folder_meaning);
error!("Bad folder meaning: {}", folder_meaning);
return connection
.fake_idle(ctx, None, FolderMeaning::Unknown)
.await;
@@ -373,8 +375,8 @@ async fn fetch_idle(
Ok(folder) => folder,
Err(err) => {
warn!(
ctx,
"Can not watch {} folder, failed to retrieve config: {:#}", folder_config, err
"Can not watch {} folder, failed to retrieve config: {:#}",
folder_config, err
);
return connection
.fake_idle(ctx, None, FolderMeaning::Unknown)
@@ -386,7 +388,7 @@ async fn fetch_idle(
watch_folder
} else {
connection.connectivity.set_not_configured(ctx).await;
info!(ctx, "Can not watch {} folder, not set", folder_config);
info!("Can not watch {folder_config} folder, not set");
return connection
.fake_idle(ctx, None, FolderMeaning::Unknown)
.await;
@@ -398,8 +400,8 @@ async fn fetch_idle(
.await
.context("prepare IMAP connection")
{
warn!(ctx, "{:#}", err);
connection.trigger_reconnect(ctx);
warn!("{err:#}");
connection.trigger_reconnect();
return connection
.fake_idle(ctx, Some(watch_folder), folder_meaning)
.await;
@@ -413,7 +415,7 @@ async fn fetch_idle(
.context("store_seen_flags_on_imap")
.ok_or_log(ctx);
} else {
warn!(ctx, "No session even though we just prepared it");
warn!("No session even though we just prepared it");
}
}
@@ -423,8 +425,8 @@ async fn fetch_idle(
.await
.context("fetch_move_delete")
{
connection.trigger_reconnect(ctx);
warn!(ctx, "{:#}", err);
connection.trigger_reconnect();
warn!("{:#}", err);
return InterruptInfo::new(false);
}
@@ -447,7 +449,7 @@ async fn fetch_idle(
Err(err) => {
// Don't reconnect, if there is a problem with the connection we will realize this when IDLEing
// but maybe just one folder can't be selected or something
warn!(ctx, "{:#}", err);
warn!("{:#}", err);
}
Ok(true) => {
// Fetch the watched folder again in case scanning other folder moved messages
@@ -461,8 +463,8 @@ async fn fetch_idle(
.await
.context("fetch_move_delete after scan_folders")
{
connection.trigger_reconnect(ctx);
warn!(ctx, "{:#}", err);
connection.trigger_reconnect();
warn!("{:#}", err);
return InterruptInfo::new(false);
}
}
@@ -479,21 +481,18 @@ async fn fetch_idle(
connection.connectivity.set_connected(ctx).await;
ctx.emit_event(EventType::ImapInboxIdle);
if let Some(session) = connection.session.take() {
if !session.can_idle() {
info!(
ctx,
"IMAP session does not support IDLE, going to fake idle."
);
info!("IMAP session does not support IDLE, going to fake idle.");
return connection
.fake_idle(ctx, Some(watch_folder), folder_meaning)
.await;
}
info!(ctx, "IMAP session supports IDLE, using it.");
info!("IMAP session supports IDLE, using it.");
match session
.idle(
ctx,
connection.idle_interrupt_receiver.clone(),
Some(watch_folder),
)
@@ -505,13 +504,13 @@ async fn fetch_idle(
info
}
Err(err) => {
connection.trigger_reconnect(ctx);
warn!(ctx, "{:#}", err);
connection.trigger_reconnect();
warn!("{:#}", err);
InterruptInfo::new(false)
}
}
} else {
warn!(ctx, "No IMAP session, going to fake idle.");
warn!("No IMAP session, going to fake idle.");
connection
.fake_idle(ctx, Some(watch_folder), folder_meaning)
.await
@@ -526,7 +525,7 @@ async fn simple_imap_loop(
) {
use futures::future::FutureExt;
info!(ctx, "starting simple loop for {}", folder_meaning);
info!("starting simple loop for {folder_meaning}");
let ImapConnectionHandlers {
mut connection,
stop_receiver,
@@ -537,7 +536,7 @@ async fn simple_imap_loop(
let fut = async move {
let ctx = ctx1;
if let Err(err) = started.send(()).await {
warn!(&ctx, "simple imap loop, missing started receiver: {}", err);
warn!("simple imap loop, missing started receiver: {}", err);
return;
}
@@ -549,7 +548,7 @@ async fn simple_imap_loop(
stop_receiver
.recv()
.map(|_| {
info!(ctx, "shutting down simple loop");
info!("shutting down simple loop");
})
.race(fut)
.await;
@@ -558,7 +557,7 @@ async fn simple_imap_loop(
async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnectionHandlers) {
use futures::future::FutureExt;
info!(ctx, "starting smtp loop");
info!("starting smtp loop");
let SmtpConnectionHandlers {
mut connection,
stop_receiver,
@@ -569,20 +568,19 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
let fut = async move {
let ctx = ctx1;
if let Err(err) = started.send(()).await {
warn!(&ctx, "smtp loop, missing started receiver: {}", err);
warn!("smtp loop, missing started receiver: {}", err);
return;
}
let mut timeout = None;
loop {
if let Err(err) = send_smtp_messages(&ctx, &mut connection).await {
warn!(ctx, "send_smtp_messages failed: {:#}", err);
warn!("send_smtp_messages failed: {:#}", err);
timeout = Some(timeout.map_or(30, |timeout: u64| timeout.saturating_mul(3)))
} else {
let duration_until_can_send = ctx.ratelimit.read().await.until_can_send();
if !duration_until_can_send.is_zero() {
info!(
ctx,
"smtp got rate limited, waiting for {} until can send again",
duration_to_str(duration_until_can_send)
);
@@ -597,7 +595,7 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
}
// Fake Idle
info!(ctx, "smtp fake idle - started");
info!("smtp fake idle - started");
match &connection.last_send_error {
None => connection.connectivity.set_connected(&ctx).await,
Some(err) => connection.connectivity.set_err(&ctx, err).await,
@@ -609,8 +607,8 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
// unnecessary retries.
if let Some(timeout) = timeout {
info!(
ctx,
"smtp has messages to retry, planning to retry {} seconds later", timeout
"smtp has messages to retry, planning to retry {} seconds later",
timeout
);
let duration = std::time::Duration::from_secs(timeout);
tokio::time::timeout(duration, async {
@@ -619,18 +617,18 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
.await
.unwrap_or_default();
} else {
info!(ctx, "smtp has no messages to retry, waiting for interrupt");
info!("smtp has no messages to retry, waiting for interrupt");
idle_interrupt_receiver.recv().await.unwrap_or_default();
};
info!(ctx, "smtp fake idle - interrupted")
info!("smtp fake idle - interrupted")
}
};
stop_receiver
.recv()
.map(|_| {
info!(ctx, "shutting down smtp loop");
info!("shutting down smtp loop");
})
.race(fut)
.await;
@@ -652,7 +650,12 @@ impl Scheduler {
let (inbox_start_send, inbox_start_recv) = channel::bounded(1);
let handle = {
let ctx = ctx.clone();
task::spawn(inbox_loop(ctx, inbox_start_send, inbox_handlers))
task::spawn(
inbox_loop(ctx, inbox_start_send, inbox_handlers)
.with_current_subscriber()
.bind_current_context_id()
.in_current_span(),
)
};
let inbox = SchedBox {
meaning: FolderMeaning::Inbox,
@@ -672,7 +675,12 @@ impl Scheduler {
let (conn_state, handlers) = ImapConnectionState::new(&ctx).await?;
let (start_send, start_recv) = channel::bounded(1);
let ctx = ctx.clone();
let handle = task::spawn(simple_imap_loop(ctx, start_send, handlers, meaning));
let handle = task::spawn(
simple_imap_loop(ctx, start_send, handlers, meaning)
.with_current_subscriber()
.bind_current_context_id()
.in_current_span(),
);
oboxes.push(SchedBox {
meaning,
conn_state,
@@ -684,22 +692,37 @@ impl Scheduler {
let smtp_handle = {
let ctx = ctx.clone();
task::spawn(smtp_loop(ctx, smtp_start_send, smtp_handlers))
task::spawn(
smtp_loop(ctx, smtp_start_send, smtp_handlers)
.with_current_subscriber()
.bind_current_context_id()
.in_current_span(),
)
};
start_recvs.push(smtp_start_recv);
let ephemeral_handle = {
let ctx = ctx.clone();
task::spawn(async move {
ephemeral::ephemeral_loop(&ctx, ephemeral_interrupt_recv).await;
})
task::spawn(
async move {
ephemeral::ephemeral_loop(&ctx, ephemeral_interrupt_recv).await;
}
.with_current_subscriber()
.bind_current_context_id()
.in_current_span(),
)
};
let location_handle = {
let ctx = ctx.clone();
task::spawn(async move {
location::location_loop(&ctx, location_interrupt_recv).await;
})
task::spawn(
async move {
location::location_loop(&ctx, location_interrupt_recv).await;
}
.with_current_subscriber()
.bind_current_context_id()
.in_current_span(),
)
};
let recently_seen_loop = RecentlySeenLoop::new(ctx.clone());
@@ -721,7 +744,7 @@ impl Scheduler {
bail!("failed to start scheduler: {}", err);
}
info!(ctx, "scheduler is running");
info!("scheduler is running");
Ok(res)
}

View File

@@ -4,6 +4,7 @@ use std::{iter::once, ops::Deref, sync::Arc};
use anyhow::{anyhow, Result};
use humansize::{format_size, BINARY};
use tokio::sync::Mutex;
use tracing::info;
use crate::events::EventType;
use crate::imap::{scan_folders::get_watched_folder_configs, FolderMeaning};
@@ -408,7 +409,7 @@ impl Context {
ret +=
&format!("<b>{}:</b> ", &*escaper::encode_minimal(root_name));
} else {
info!(self, "connectivity: root name hidden: \"{}\"", root_name);
info!("connectivity: root name hidden: \"{}\"", root_name);
}
let messages = stock_str::messages(self).await;

View File

@@ -4,6 +4,7 @@ use std::convert::TryFrom;
use anyhow::{bail, Context as _, Error, Result};
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use tracing::{error, info, warn};
use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat, ChatId, ChatIdBlocked};
@@ -124,7 +125,7 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
)
};
info!(context, "Generated QR code: {}", qr);
info!("Generated QR code: {}", qr);
Ok(qr)
}
@@ -133,7 +134,7 @@ async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
match SignedPublicKey::load_self(context).await {
Ok(key) => Some(key.fingerprint()),
Err(_) => {
warn!(context, "get_self_fingerprint(): failed to load key");
warn!("get_self_fingerprint(): failed to load key");
None
}
}
@@ -147,9 +148,9 @@ async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
/// The function returns immediately and the handshake will run in background.
pub async fn join_securejoin(context: &Context, qr: &str) -> Result<ChatId> {
securejoin(context, qr).await.map_err(|err| {
warn!(context, "Fatal joiner error: {:#}", err);
warn!("Fatal joiner error: {:#}", err);
// The user just scanned this QR code so has context on what failed.
error!(context, "QR process failed");
error!("QR process failed");
err
})
}
@@ -160,7 +161,7 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId> {
==== Step 2 in "Setup verified contact" protocol =====
========================================================*/
info!(context, "Requesting secure-join ...",);
info!("Requesting secure-join ...",);
let qr_scan = check_qr(context, qr).await?;
let invite = QrInvite::try_from(qr_scan)?;
@@ -215,7 +216,6 @@ async fn fingerprint_equals_sender(
Ok(peerstate) => peerstate,
Err(err) => {
warn!(
context,
"Failed to sender peerstate for {}: {}",
contact.get_addr(),
err
@@ -288,8 +288,8 @@ pub(crate) async fn handle_securejoin_handshake(
.context("Not a Secure-Join message")?;
info!(
context,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received",
step,
);
let join_vg = step.starts_with("vg-");
@@ -308,15 +308,15 @@ pub(crate) async fn handle_securejoin_handshake(
let invitenumber = match mime_message.get_header(HeaderDef::SecureJoinInvitenumber) {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing)");
warn!("Secure-join denied (invitenumber missing)");
return Ok(HandshakeMessage::Ignore);
}
};
if !token::exists(context, token::Namespace::InviteNumber, invitenumber).await {
warn!(context, "Secure-join denied (bad invitenumber).");
warn!("Secure-join denied (bad invitenumber).");
return Ok(HandshakeMessage::Ignore);
}
info!(context, "Secure-join requested.",);
info!("Secure-join requested.",);
inviter_progress!(context, contact_id, 300);
@@ -366,7 +366,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore);
}
};
if !encrypted_and_signed(context, mime_message, Some(&fingerprint)) {
if !encrypted_and_signed(mime_message, Some(&fingerprint)) {
could_not_establish_secure_connection(
context,
contact_id,
@@ -386,7 +386,7 @@ pub(crate) async fn handle_securejoin_handshake(
.await?;
return Ok(HandshakeMessage::Ignore);
}
info!(context, "Fingerprint verified.",);
info!("Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0 = match mime_message.get_header(HeaderDef::SecureJoinAuth) {
Some(auth) => auth,
@@ -429,7 +429,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore);
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await?;
info!(context, "Auth verified.",);
info!("Auth verified.",);
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
inviter_progress!(context, contact_id, 600);
if join_vg {
@@ -439,7 +439,7 @@ pub(crate) async fn handle_securejoin_handshake(
let field_grpid = match mime_message.get_header(HeaderDef::SecureJoinGroup) {
Some(s) => s.as_str(),
None => {
warn!(context, "Missing Secure-Join-Group header");
warn!("Missing Secure-Join-Group header");
return Ok(HandshakeMessage::Ignore);
}
};
@@ -450,7 +450,7 @@ pub(crate) async fn handle_securejoin_handshake(
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
.await
{
error!(context, "failed to add contact: {}", err);
error!("failed to add contact: {}", err);
}
}
None => bail!("Chat {} not found", &field_grpid),
@@ -501,7 +501,7 @@ pub(crate) async fn handle_securejoin_handshake(
if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
if contact.is_verified(context).await? == VerifiedStatus::Unverified {
warn!(context, "{} invalid.", step);
warn!("{} invalid.", step);
return Ok(HandshakeMessage::Ignore);
}
if join_vg {
@@ -510,7 +510,7 @@ pub(crate) async fn handle_securejoin_handshake(
.map(|s| s.as_str())
.unwrap_or_else(|| "");
if let Err(err) = chat::get_chat_id_by_grpid(context, field_grpid).await {
warn!(context, "Failed to lookup chat_id from grpid: {}", err);
warn!("Failed to lookup chat_id from grpid: {}", err);
return Err(
err.context(format!("Chat for group {} not found", &field_grpid))
);
@@ -518,12 +518,12 @@ pub(crate) async fn handle_securejoin_handshake(
}
Ok(HandshakeMessage::Ignore) // "Done" deletes the message and breaks multi-device
} else {
warn!(context, "{} invalid.", step);
warn!("{} invalid.", step);
Ok(HandshakeMessage::Ignore)
}
}
_ => {
warn!(context, "invalid step: {}", step);
warn!("invalid step: {}", step);
Ok(HandshakeMessage::Ignore)
}
}
@@ -557,7 +557,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
let step = mime_message
.get_header(HeaderDef::SecureJoin)
.context("Not a Secure-Join message")?;
info!(context, "observing secure-join message \'{}\'", step);
info!("observing secure-join message \'{}\'", step);
match step.as_str() {
"vg-request-with-auth"
@@ -566,11 +566,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
| "vc-contact-confirm"
| "vg-member-added-received"
| "vc-contact-confirm-received" => {
if !encrypted_and_signed(
context,
mime_message,
get_self_fingerprint(context).await.as_ref(),
) {
if !encrypted_and_signed(mime_message, get_self_fingerprint(context).await.as_ref()) {
could_not_establish_secure_connection(
context,
contact_id,
@@ -715,8 +711,8 @@ async fn could_not_establish_secure_connection(
let msg = stock_str::contact_not_verified(context, &contact).await;
chat::add_info_msg(context, chat_id, &msg, time()).await?;
warn!(
context,
"StockMessage::ContactNotVerified posted to 1:1 chat ({})", details
"StockMessage::ContactNotVerified posted to 1:1 chat ({})",
details
);
Ok(())
}
@@ -733,7 +729,7 @@ async fn mark_peer_as_verified(
PeerstateVerifiedStatus::BidirectVerified,
verifier,
) {
error!(context, "Could not mark peer as verified: {}", err);
error!("Could not mark peer as verified: {}", err);
return Err(err);
}
peerstate.prefer_encrypt = EncryptPreference::Mutual;
@@ -749,25 +745,24 @@ async fn mark_peer_as_verified(
******************************************************************************/
fn encrypted_and_signed(
context: &Context,
mimeparser: &MimeMessage,
expected_fingerprint: Option<&Fingerprint>,
) -> bool {
if !mimeparser.was_encrypted() {
warn!(context, "Message not encrypted.",);
warn!("Message not encrypted.",);
false
} else if let Some(expected_fingerprint) = expected_fingerprint {
if !mimeparser.signatures.contains(expected_fingerprint) {
warn!(
context,
"Message does not match expected fingerprint {}.", expected_fingerprint,
"Message does not match expected fingerprint {}.",
expected_fingerprint,
);
false
} else {
true
}
} else {
warn!(context, "Fingerprint for comparison missing.");
warn!("Fingerprint for comparison missing.");
false
}
}

View File

@@ -4,6 +4,7 @@
//! the required user interactions.
use anyhow::{Context as _, Result};
use tracing::{error, warn};
use super::bobstate::{BobHandshakeStage, BobState};
use super::qrinvite::QrInvite;
@@ -45,7 +46,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
let (state, stage, aborted_states) =
BobState::start_protocol(context, invite.clone(), chat_id).await?;
for state in aborted_states {
error!(context, "Aborting previously unfinished QR Join process.");
error!("Aborting previously unfinished QR Join process.");
state.notify_aborted(context, "new QR scanned").await?;
state.emit_progress(context, JoinerProgress::Error);
}
@@ -133,10 +134,7 @@ pub(super) async fn handle_contact_confirm(
Ok(retval)
}
Some(_) => {
warn!(
context,
"Impossible state returned from handling handshake message"
);
warn!("Impossible state returned from handling handshake message");
Ok(retval)
}
None => Ok(retval),
@@ -208,8 +206,8 @@ impl BobState {
let chat_id = self.joining_chat_id(context).await?;
chat::add_info_msg(context, chat_id, &msg, time()).await?;
warn!(
context,
"StockMessage::ContactNotVerified posted to joining chat ({})", why
"StockMessage::ContactNotVerified posted to joining chat ({})",
why
);
Ok(())
}

View File

@@ -9,6 +9,7 @@
use anyhow::{Error, Result};
use rusqlite::Connection;
use tracing::{info, warn};
use super::qrinvite::QrInvite;
use super::{encrypted_and_signed, fingerprint_equals_sender, mark_peer_as_verified};
@@ -96,7 +97,7 @@ impl BobState {
if fingerprint_equals_sender(context, invite.fingerprint(), invite.contact_id()).await?
{
// The scanned fingerprint matches Alice's key, we can proceed to step 4b.
info!(context, "Taking securejoin protocol shortcut");
info!("Taking securejoin protocol shortcut");
send_handshake_message(context, &invite, chat_id, BobHandshakeMsg::RequestWithAuth)
.await?;
(
@@ -245,15 +246,14 @@ impl BobState {
Some(step) => step,
None => {
warn!(
context,
"Message has no Secure-Join header: {}",
mime_message.get_rfc724_mid().unwrap_or_default()
);
return Ok(None);
}
};
if !self.is_msg_expected(context, step.as_str()) {
info!(context, "{} message out of sync for BobState", step);
if !self.is_msg_expected(step.as_str()) {
info!("{} message out of sync for BobState", step);
return Ok(None);
}
match step.as_str() {
@@ -264,19 +264,19 @@ impl BobState {
self.step_contact_confirm(context, mime_message).await
}
_ => {
warn!(context, "Invalid step for BobState: {}", step);
warn!("Invalid step for BobState: {}", step);
Ok(None)
}
}
}
/// Returns `true` if the message is expected according to the protocol.
fn is_msg_expected(&self, context: &Context, step: &str) -> bool {
fn is_msg_expected(&self, step: &str) -> bool {
let variant_matches = match self.invite {
QrInvite::Contact { .. } => step.starts_with("vc-"),
QrInvite::Group { .. } => step.starts_with("vg-"),
};
let step_matches = self.next.matches(context, step);
let step_matches = self.next.matches(step);
variant_matches && step_matches
}
@@ -289,11 +289,8 @@ impl BobState {
context: &Context,
mime_message: &MimeMessage,
) -> Result<Option<BobHandshakeStage>> {
info!(
context,
"Bob Step 4 - handling vc-auth-require/vg-auth-required message"
);
if !encrypted_and_signed(context, mime_message, Some(self.invite.fingerprint())) {
info!("Bob Step 4 - handling vc-auth-require/vg-auth-required message");
if !encrypted_and_signed(mime_message, Some(self.invite.fingerprint())) {
let reason = if mime_message.was_encrypted() {
"Valid signature missing"
} else {
@@ -310,7 +307,7 @@ impl BobState {
.await?;
return Ok(Some(BobHandshakeStage::Terminated("Fingerprint mismatch")));
}
info!(context, "Fingerprint verified.",);
info!("Fingerprint verified.",);
self.update_next(&context.sql, SecureJoinStep::ContactConfirm)
.await?;
self.send_handshake_message(context, BobHandshakeMsg::RequestWithAuth)
@@ -331,10 +328,7 @@ impl BobState {
context: &Context,
mime_message: &MimeMessage,
) -> Result<Option<BobHandshakeStage>> {
info!(
context,
"Bob Step 7 - handling vc-contact-confirm/vg-member-added message"
);
info!("Bob Step 7 - handling vc-contact-confirm/vg-member-added message");
let vg_expect_encrypted = match self.invite {
QrInvite::Contact { .. } => {
// setup-contact is always encrypted
@@ -357,7 +351,7 @@ impl BobState {
}
};
if vg_expect_encrypted
&& !encrypted_and_signed(context, mime_message, Some(self.invite.fingerprint()))
&& !encrypted_and_signed(mime_message, Some(self.invite.fingerprint()))
{
self.update_next(&context.sql, SecureJoinStep::Terminated)
.await?;
@@ -381,7 +375,7 @@ impl BobState {
.map(|s| s.as_str())
.ok_or_else(|| Error::msg("Missing Chat-Group-Member-Added header"))?;
if !context.is_self_addr(member_added).await? {
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
info!( "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return Ok(None);
}
}
@@ -389,10 +383,7 @@ impl BobState {
self.send_handshake_message(context, BobHandshakeMsg::ContactConfirmReceived)
.await
.map_err(|_| {
warn!(
context,
"Failed to send vc-contact-confirm-received/vg-member-added-received"
);
warn!("Failed to send vc-contact-confirm-received/vg-member-added-received");
})
// This is not an error affecting the protocol outcome.
.ok();
@@ -530,16 +521,16 @@ pub enum SecureJoinStep {
impl SecureJoinStep {
/// Compares the legacy string representation of a step to a [`SecureJoinStep`] variant.
fn matches(&self, context: &Context, step: &str) -> bool {
fn matches(&self, step: &str) -> bool {
match self {
Self::AuthRequired => step == "vc-auth-required" || step == "vg-auth-required",
Self::ContactConfirm => step == "vc-contact-confirm" || step == "vg-member-added",
SecureJoinStep::Terminated => {
warn!(context, "Terminated state for next securejoin step");
warn!("Terminated state for next securejoin step");
false
}
SecureJoinStep::Completed => {
warn!(context, "Completed state for next securejoin step");
warn!("Completed state for next securejoin step");
false
}
}

View File

@@ -9,6 +9,7 @@ use async_smtp::response::{Category, Code, Detail};
use async_smtp::{self as smtp, EmailAddress, SmtpTransport};
use tokio::io::BufStream;
use tokio::task;
use tracing::{error, info, warn};
use crate::config::Config;
use crate::contact::{Contact, ContactId};
@@ -87,7 +88,7 @@ impl Smtp {
/// Connect using configured parameters.
pub async fn connect_configured(&mut self, context: &Context) -> Result<()> {
if self.has_maybe_stale_connection() {
info!(context, "Closing stale connection");
info!("Closing stale connection");
self.disconnect().await;
}
@@ -234,7 +235,7 @@ impl Smtp {
provider_strict_tls: bool,
) -> Result<()> {
if self.is_connected() {
warn!(context, "SMTP already connected.");
warn!("SMTP already connected.");
return Ok(());
}
@@ -364,7 +365,7 @@ pub(crate) async fn smtp_send(
msg_id: MsgId,
) -> SendResult {
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!(context, "smtp-sending out mime message:");
info!("smtp-sending out mime message:");
println!("{message}");
}
@@ -385,7 +386,7 @@ pub(crate) async fn smtp_send(
let status = match send_result {
Err(crate::smtp::send::Error::SmtpSend(err)) => {
// Remote error, retry later.
info!(context, "SMTP failed to send: {:?}", &err);
info!("SMTP failed to send: {:?}", &err);
let res = match err {
async_smtp::error::Error::Permanent(ref response) => {
@@ -412,10 +413,10 @@ pub(crate) async fn smtp_send(
};
if maybe_transient {
info!(context, "Permanent error that is likely to actually be transient, postponing retry for later");
info!( "Permanent error that is likely to actually be transient, postponing retry for later");
SendResult::Retry
} else {
info!(context, "Permanent error, message sending failed");
info!("Permanent error, message sending failed");
// If we do not retry, add an info message to the chat.
// Yandex error "554 5.7.1 [2] Message rejected under suspicion of SPAM; https://ya.cc/..."
// should definitely go here, because user has to open the link to
@@ -436,27 +437,22 @@ pub(crate) async fn smtp_send(
// Any extended smtp status codes like x.1.1, x.1.2 or x.1.3 that we
// receive as a transient error are misconfigurations of the smtp server.
// See <https://tools.ietf.org/html/rfc3463#section-3.2>
info!(context, "Received extended status code {} for a transient error. This looks like a misconfigured SMTP server, let's fail immediately", first_word);
info!("Received extended status code {} for a transient error. This looks like a misconfigured SMTP server, let's fail immediately", first_word);
SendResult::Failure(format_err!("Permanent SMTP error: {}", err))
} else {
info!(
context,
"Transient error with status code {}, postponing retry for later",
first_word
);
SendResult::Retry
}
} else {
info!(
context,
"Transient error without status code, postponing retry for later"
);
info!("Transient error without status code, postponing retry for later");
SendResult::Retry
}
}
_ => {
info!(
context,
"Message sending failed without error returned by the server, retry later"
);
SendResult::Retry
@@ -464,7 +460,7 @@ pub(crate) async fn smtp_send(
};
// this clears last_success info
info!(context, "Failed to send message over SMTP, disconnecting");
info!("Failed to send message over SMTP, disconnecting");
smtp.disconnect().await;
res
@@ -472,19 +468,19 @@ pub(crate) async fn smtp_send(
Err(crate::smtp::send::Error::Envelope(err)) => {
// Local error, job is invalid, do not retry.
smtp.disconnect().await;
warn!(context, "SMTP job is invalid: {}", err);
warn!("SMTP job is invalid: {}", err);
SendResult::Failure(err)
}
Err(crate::smtp::send::Error::NoTransport) => {
// Should never happen.
// It does not even make sense to disconnect here.
error!(context, "SMTP job failed because SMTP has no transport");
error!("SMTP job failed because SMTP has no transport");
SendResult::Failure(format_err!("SMTP has not transport"))
}
Err(crate::smtp::send::Error::Other(err)) => {
// Local error, job is invalid, do not retry.
smtp.disconnect().await;
warn!(context, "unable to load job: {}", err);
warn!("unable to load job: {}", err);
SendResult::Failure(err)
}
Ok(()) => SendResult::Success,
@@ -551,8 +547,8 @@ pub(crate) async fn send_msg_to_smtp(
bail!("Number of retries exceeded the limit");
}
info!(
context,
"Try number {} to send message {} over SMTP", retries, msg_id
"Try number {} to send message {} over SMTP",
retries, msg_id
);
let recipients_list = recipients
@@ -561,7 +557,7 @@ pub(crate) async fn send_msg_to_smtp(
|addr| match async_smtp::EmailAddress::new(addr.to_string()) {
Ok(addr) => Some(addr),
Err(err) => {
warn!(context, "invalid recipient: {} {:?}", addr, err);
warn!("invalid recipient: {} {:?}", addr, err);
None
}
},
@@ -574,10 +570,7 @@ pub(crate) async fn send_msg_to_smtp(
.await
.with_context(|| format!("failed to check message {msg_id} existence"))?
{
info!(
context,
"Sending of message {} was cancelled by the user.", msg_id
);
info!("Sending of message {} was cancelled by the user.", msg_id);
return Ok(());
}
@@ -607,7 +600,7 @@ pub(crate) async fn send_msg_to_smtp(
async fn send_mdns(context: &Context, connection: &mut Smtp) -> Result<()> {
loop {
if !context.ratelimit.read().await.can_send() {
info!(context, "Ratelimiter does not allow sending MDNs now");
info!("Ratelimiter does not allow sending MDNs now");
return Ok(());
}
@@ -715,7 +708,7 @@ async fn send_mdn_msg_id(
match smtp_send(context, &recipients, &body, smtp, msg_id).await {
SendResult::Success => {
info!(context, "Successfully sent MDN for {}", msg_id);
info!("Successfully sent MDN for {}", msg_id);
context
.sql
.execute("DELETE FROM smtp_mdns WHERE msg_id = ?", paramsv![msg_id])
@@ -733,10 +726,7 @@ async fn send_mdn_msg_id(
Ok(())
}
SendResult::Retry => {
info!(
context,
"Temporary SMTP failure while sending an MDN for {}", msg_id
);
info!("Temporary SMTP failure while sending an MDN for {}", msg_id);
Ok(())
}
SendResult::Failure(err) => Err(err),
@@ -751,7 +741,7 @@ async fn send_mdn(context: &Context, smtp: &mut Smtp) -> Result<bool> {
context.sql.execute("DELETE FROM smtp_mdns", []).await?;
return Ok(false);
}
info!(context, "Sending MDNs");
info!("Sending MDNs");
context
.sql

View File

@@ -1,6 +1,7 @@
//! # SMTP message sending
use async_smtp::{EmailAddress, Envelope, SendableEmail};
use tracing::warn;
use super::Smtp;
use crate::config::Config;
@@ -70,8 +71,8 @@ impl Smtp {
self.last_success = Some(std::time::SystemTime::now());
} else {
warn!(
context,
"uh? SMTP has no transport, failed to send to {}", recipients_display
"uh? SMTP has no transport, failed to send to {}",
recipients_display
);
return Err(Error::NoTransport);
}

View File

@@ -22,6 +22,7 @@ use crate::param::{Param, Params};
use crate::peerstate::{deduplicate_peerstates, Peerstate};
use crate::stock_str;
use crate::tools::{delete_file, time};
use tracing::{error, info, warn};
#[allow(missing_docs)]
#[macro_export]
@@ -216,7 +217,7 @@ impl Sql {
// the structure is complete now and all objects are usable
if recalc_fingerprints {
info!(context, "[migration] recalc fingerprints");
info!("[migration] recalc fingerprints");
let addrs = self
.query_map(
"SELECT addr FROM acpeerstates;",
@@ -265,7 +266,7 @@ impl Sql {
.await?
}
Err(e) => {
warn!(context, "Migrations can't recode avatar, removing. {:#}", e);
warn!("Migrations can't recode avatar, removing. {:#}", e);
context.set_config(Config::Selfavatar, None).await?
}
}
@@ -280,8 +281,8 @@ impl Sql {
pub async fn open(&self, context: &Context, passphrase: String) -> Result<()> {
if self.is_open().await {
error!(
context,
"Cannot open, database \"{:?}\" already opened.", self.dbfile,
"Cannot open, database \"{:?}\" already opened.",
self.dbfile,
);
bail!("SQL database is already opened.");
}
@@ -291,7 +292,7 @@ impl Sql {
self.close().await;
Err(err)
} else {
info!(context, "Opened database {:?}.", self.dbfile);
info!("Opened database {:?}.", self.dbfile);
*self.is_encrypted.write().await = Some(passphrase_nonempty);
// setup debug logging if there is an entry containing its id
@@ -697,28 +698,19 @@ fn new_connection(path: &Path, passphrase: &str) -> Result<Connection> {
/// Cleanup the account to restore some storage and optimize the database.
pub async fn housekeeping(context: &Context) -> Result<()> {
if let Err(err) = remove_unused_files(context).await {
warn!(
context,
"Housekeeping: cannot remove unused files: {:#}", err
);
warn!("Housekeeping: cannot remove unused files: {err:#}.");
}
if let Err(err) = start_ephemeral_timers(context).await {
warn!(
context,
"Housekeeping: cannot start ephemeral timers: {:#}", err
);
warn!("Housekeeping: cannot start ephemeral timers: {err:#}.");
}
if let Err(err) = prune_tombstones(&context.sql).await {
warn!(
context,
"Housekeeping: Cannot prune message tombstones: {:#}", err
);
warn!("Housekeeping: Cannot prune message tombstones: {err:#}.");
}
if let Err(err) = deduplicate_peerstates(&context.sql).await {
warn!(context, "Failed to deduplicate peerstates: {:#}", err)
warn!("Failed to deduplicate peerstates: {err:#}.")
}
context.schedule_quota_update().await?;
@@ -731,11 +723,11 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
.await
{
Err(err) => {
warn!(context, "Failed to run incremental vacuum: {err:#}");
warn!("Failed to run incremental vacuum: {err:#}.");
}
Ok(Some(())) => {
// Incremental vacuum returns a zero-column result if it did anything.
info!(context, "Successfully ran incremental vacuum.");
info!("Successfully ran incremental vacuum.");
}
Ok(None) => {
// Incremental vacuum returned `SQLITE_DONE` immediately,
@@ -747,7 +739,7 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
.set_config(Config::LastHousekeeping, Some(&time().to_string()))
.await
{
warn!(context, "Can't set config: {}", e);
warn!("Can't set config: {e:#}.");
}
context
@@ -759,7 +751,7 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
.await
.ok_or_log_msg(context, "failed to remove old MDNs");
info!(context, "Housekeeping done.");
info!("Housekeeping done.");
Ok(())
}
@@ -768,7 +760,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
let mut files_in_use = HashSet::new();
let mut unreferenced_count = 0;
info!(context, "Start housekeeping...");
info!("Start housekeeping...");
maybe_add_from_param(
&context.sql,
&mut files_in_use,
@@ -814,7 +806,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
.await
.context("housekeeping: failed to SELECT value FROM config")?;
info!(context, "{} files in use.", files_in_use.len(),);
info!("{} files in use.", files_in_use.len());
/* go through directories and delete unused files */
let blobdir = context.get_blobdir();
for p in [&blobdir.join(BLOBS_BACKUP_NAME), blobdir] {
@@ -845,8 +837,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
// The dir could be created not by a user, but by a desktop
// environment f.e. So, no warning.
info!(
context,
"Housekeeping: Cannot rmdir {}: {:#}",
"Housekeeping: Cannot rmdir {}: {:#}.",
entry.path().display(),
e
);
@@ -867,8 +858,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
&& (recently_created || recently_modified || recently_accessed)
{
info!(
context,
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
"Housekeeping: Keeping new unreferenced file #{}: {:?}.",
unreferenced_count,
entry.file_name(),
);
@@ -878,15 +868,13 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
unreferenced_count += 1;
}
info!(
context,
"Housekeeping: Deleting unreferenced file #{}: {:?}",
"Housekeeping: Deleting unreferenced file #{}: {:?}.",
unreferenced_count,
entry.file_name()
);
let path = entry.path();
if let Err(err) = delete_file(context, &path).await {
error!(
context,
"Failed to delete unused file {}: {:#}.",
path.display(),
err
@@ -895,12 +883,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
}
}
Err(err) => {
warn!(
context,
"Housekeeping: Cannot read dir {}: {:#}",
p.display(),
err
);
warn!("Housekeeping: Cannot read dir {}: {:#}.", p.display(), err);
}
}
}
@@ -1162,7 +1145,6 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
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.
@@ -1174,7 +1156,9 @@ mod tests {
assert!(!disable_server_delete);
assert!(!recode_avatar);
info!(&t, "test_migration_flags: XXX END MARKER");
t.emit_event(crate::EventType::Info(
"test_migration_flags: XXX END MARKER".to_string(),
));
loop {
let evt = t

View File

@@ -1,6 +1,7 @@
//! Migrations module.
use anyhow::{Context as _, Result};
use tracing::{info, warn};
use crate::config::Config;
use crate::constants::ShowEmails;
@@ -372,7 +373,7 @@ UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
)
.await?;
} else {
warn!(context, "Can't parse configured address: {:?}", addr);
warn!("Can't parse configured address: {:?}", addr);
}
}
@@ -715,10 +716,7 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
} else {
"Created new database; "
};
info!(
context,
"{}[migration] v{}-v{}", created_db, dbversion, new_version
);
info!("{}[migration] v{}-v{}", created_db, dbversion, new_version);
}
Ok((

View File

@@ -4,6 +4,7 @@ use anyhow::Result;
use lettre_email::mime::{self};
use lettre_email::PartBuilder;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
use crate::chat::{Chat, ChatId};
use crate::config::Config;
@@ -90,8 +91,8 @@ impl Context {
let chat = Chat::load_from_db(self, chat_id).await?;
if !chat.is_promoted() {
info!(
self,
"group '{}' not yet promoted, do not sync tokens yet.", chat.grpid
"group '{}' not yet promoted, do not sync tokens yet.",
chat.grpid
);
return Ok(());
}
@@ -226,7 +227,7 @@ impl Context {
/// If eg. just an item cannot be deleted,
/// that should not hold off the other items to be executed.
pub(crate) async fn execute_sync_items(&self, items: &SyncItems) -> Result<()> {
info!(self, "executing {} sync item(s)", items.items.len());
info!("executing {} sync item(s)", items.items.len());
for item in &items.items {
match &item.data {
AddQrToken(token) => {
@@ -236,10 +237,7 @@ impl Context {
{
Some(chat_id)
} else {
warn!(
self,
"Ignoring token for nonexistent/deleted group '{}'.", grpid
);
warn!("Ignoring token for nonexistent/deleted group '{}'.", grpid);
continue;
}
} else {

View File

@@ -17,6 +17,7 @@ use tempfile::{tempdir, TempDir};
use tokio::runtime::Handle;
use tokio::sync::RwLock;
use tokio::task;
use tracing::warn;
use crate::chat::{self, Chat, ChatId, MessageListOptions};
use crate::chatlist::Chatlist;
@@ -536,8 +537,8 @@ impl TestContext {
.expect("add_or_lookup");
match modified {
Modifier::None => (),
Modifier::Modified => warn!(&self.ctx, "Contact {} modified by TestContext", &addr),
Modifier::Created => warn!(&self.ctx, "Contact {} created by TestContext", &addr),
Modifier::Modified => warn!("Contact {} modified by TestContext", &addr),
Modifier::Created => warn!("Contact {} created by TestContext", &addr),
}
Contact::load_from_db(&self.ctx, contact_id).await.unwrap()
}

View File

@@ -19,6 +19,7 @@ use mailparse::headers::Headers;
use mailparse::MailHeaderMap;
use rand::{thread_rng, Rng};
use tokio::{fs, io};
use tracing::warn;
use crate::chat::{add_device_msg, add_device_msg_with_importance};
use crate::constants::{DC_ELLIPSIS, DC_OUTDATED_WARNING_DAYS};
@@ -200,7 +201,7 @@ async fn maybe_warn_on_bad_time(context: &Context, now: i64, known_past_timestam
.await
.ok();
} else {
warn!(context, "Can't convert current timestamp");
warn!("Can't convert current timestamp");
}
return true;
}
@@ -336,7 +337,7 @@ pub(crate) async fn delete_file(context: &Context, path: impl AsRef<Path>) -> Re
bail!("path {} does not exist", path_abs.display());
}
if !path_abs.is_file() {
warn!(context, "refusing to delete non-file {}.", path.display());
warn!("refusing to delete non-file \"{}\".", path.display());
bail!("not a file: \"{}\"", path.display());
}
@@ -358,7 +359,7 @@ pub async fn delete_files_in_dir(context: &Context, path: impl AsRef<Path>) -> R
Ok(file) => {
delete_file(context, file.file_name()).await?;
}
Err(e) => warn!(context, "Could not read file to delete: {}", e),
Err(e) => warn!("Could not read file to delete: {}", e),
}
}
Ok(())
@@ -374,7 +375,6 @@ pub(crate) async fn create_folder(
Ok(_) => Ok(()),
Err(err) => {
warn!(
context,
"Cannot create directory \"{}\": {}",
path.as_ref().display(),
err
@@ -396,7 +396,6 @@ pub(crate) async fn write_file(
let path_abs = get_abs_path(context, &path);
fs::write(&path_abs, buf).await.map_err(|err| {
warn!(
context,
"Cannot write {} bytes to \"{}\": {}",
buf.len(),
path.as_ref().display(),
@@ -414,7 +413,6 @@ pub async fn read_file(context: &Context, path: impl AsRef<Path>) -> Result<Vec<
Ok(bytes) => Ok(bytes),
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err
@@ -431,7 +429,6 @@ pub async fn open_file(context: &Context, path: impl AsRef<Path>) -> Result<fs::
Ok(bytes) => Ok(bytes),
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err
@@ -452,7 +449,6 @@ pub fn open_file_std<P: AsRef<std::path::Path>>(
Ok(bytes) => Ok(bytes),
Err(err) => {
warn!(
context,
"Cannot read \"{}\" or file is empty: {}",
path.as_ref().display(),
err

View File

@@ -10,6 +10,7 @@ use lettre_email::PartBuilder;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::io::AsyncReadExt;
use tracing::{info, warn};
use crate::chat::Chat;
use crate::contact::ContactId;
@@ -165,6 +166,19 @@ pub(crate) struct StatusUpdateItemAndSerial {
max_serial: StatusUpdateSerial,
}
/// Returns an entry index and a reference.
fn find_zip_entry<'a>(
file: &'a async_zip::ZipFile,
name: &str,
) -> Option<(usize, &'a async_zip::StoredZipEntry)> {
for (i, ent) in file.entries().iter().enumerate() {
if ent.entry().filename() == name {
return Some((i, ent));
}
}
None
}
impl Context {
/// check if a file is an acceptable webxdc for sending or receiving.
pub(crate) async fn is_webxdc_file(&self, filename: &str, file: &[u8]) -> Result<bool> {
@@ -174,22 +188,22 @@ impl Context {
if file.len() as u64 > WEBXDC_RECEIVING_LIMIT {
info!(
self,
"{} exceeds receiving limit of {} bytes", &filename, WEBXDC_RECEIVING_LIMIT
"{} exceeds receiving limit of {} bytes",
&filename, WEBXDC_RECEIVING_LIMIT
);
return Ok(false);
}
let archive = match async_zip::read::mem::ZipFileReader::new(file).await {
let archive = match async_zip::read::mem::ZipFileReader::new(file.to_vec()).await {
Ok(archive) => archive,
Err(_) => {
info!(self, "{} cannot be opened as zip-file", &filename);
info!("{} cannot be opened as zip-file", &filename);
return Ok(false);
}
};
if archive.entry("index.html").is_none() {
info!(self, "{} misses index.html", &filename);
if find_zip_entry(archive.file(), "index.html").is_none() {
info!("{} misses index.html", &filename);
return Ok(false);
}
@@ -215,15 +229,15 @@ impl Context {
let valid = match async_zip::read::fs::ZipFileReader::new(path).await {
Ok(archive) => {
if archive.entry("index.html").is_none() {
info!(self, "{} misses index.html", filename);
if find_zip_entry(archive.file(), "index.html").is_none() {
info!("{} misses index.html", filename);
false
} else {
true
}
}
Err(_) => {
info!(self, "{} cannot be opened as zip-file", filename);
info!("{} cannot be opened as zip-file", filename);
false
}
};
@@ -649,10 +663,9 @@ fn parse_webxdc_manifest(bytes: &[u8]) -> Result<WebxdcManifest> {
}
async fn get_blob(archive: &mut async_zip::read::fs::ZipFileReader, name: &str) -> Result<Vec<u8>> {
let (i, _) = archive
.entry(name)
let (i, _) = find_zip_entry(archive.file(), name)
.ok_or_else(|| anyhow!("no entry found for {}", name))?;
let mut reader = archive.entry_reader(i).await?;
let mut reader = archive.entry(i).await?;
let mut buf = Vec::new();
reader.read_to_end(&mut buf).await?;
Ok(buf)
@@ -739,7 +752,7 @@ impl Message {
if let Some(ref name) = manifest.name {
let name = name.trim();
if name.is_empty() {
warn!(context, "empty name given in manifest");
warn!("empty name given in manifest");
manifest.name = None;
}
}
@@ -754,9 +767,9 @@ impl Message {
} else {
self.get_filename().unwrap_or_default()
},
icon: if archive.entry("icon.png").is_some() {
icon: if find_zip_entry(archive.file(), "icon.png").is_some() {
"icon.png".to_string()
} else if archive.entry("icon.jpg").is_some() {
} else if find_zip_entry(archive.file(), "icon.jpg").is_some() {
"icon.jpg".to_string()
} else {
WEBXDC_DEFAULT_ICON.to_string()