mirror of
https://github.com/chatmail/core.git
synced 2026-04-03 22:12:11 +03:00
Compare commits
47 Commits
link2xt/py
...
v1.112.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf0c02bc8 | ||
|
|
1d42907114 | ||
|
|
070d832580 | ||
|
|
0dfec83b0f | ||
|
|
c84155cbd4 | ||
|
|
eb5ddf270f | ||
|
|
fb093253c6 | ||
|
|
1864be5c55 | ||
|
|
7138d44083 | ||
|
|
04daff0608 | ||
|
|
801250a9e0 | ||
|
|
20319b5426 | ||
|
|
9cca34bba5 | ||
|
|
530981119e | ||
|
|
6d0327d057 | ||
|
|
580ce5a9e9 | ||
|
|
6f327c950d | ||
|
|
92ad843ff2 | ||
|
|
a8059c6bff | ||
|
|
4b468a25fe | ||
|
|
1e135b649c | ||
|
|
40d32f2d0c | ||
|
|
c9ec087cd8 | ||
|
|
84d79e1479 | ||
|
|
83af248068 | ||
|
|
4f25edb1a1 | ||
|
|
ded1634b7d | ||
|
|
635c73ffc6 | ||
|
|
fcc1fe73be | ||
|
|
fa278d50f7 | ||
|
|
2f02be4c64 | ||
|
|
7add1c116c | ||
|
|
124a0e90e1 | ||
|
|
1716cdf51c | ||
|
|
3fdcffb314 | ||
|
|
f033aae25c | ||
|
|
c42d942460 | ||
|
|
0ba8201797 | ||
|
|
87252ab053 | ||
|
|
53eec521dc | ||
|
|
238570a7b9 | ||
|
|
043ae48806 | ||
|
|
fb88f2e6ab | ||
|
|
5db867cd1b | ||
|
|
ec00c160c6 | ||
|
|
616eabc613 | ||
|
|
89b32e02c5 |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -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
|
||||
#
|
||||
|
||||
36
.github/workflows/deltachat-rpc-server.yml
vendored
36
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -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/*
|
||||
|
||||
37
CHANGELOG.md
37
CHANGELOG.md
@@ -1,18 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
## [1.112.1] - 2023-03-27
|
||||
|
||||
### Changes
|
||||
- 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`
|
||||
- `receive_imf`: Mark special messages as seen. Exactly: delivery reports, webxdc status updates. #4230
|
||||
|
||||
|
||||
## [1.112.0] - 2023-03-23
|
||||
|
||||
### 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
|
||||
- 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 +34,11 @@
|
||||
- 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
|
||||
|
||||
|
||||
## [1.111.0] - 2023-03-05
|
||||
|
||||
@@ -2314,5 +2330,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
|
||||
[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
|
||||
[1.112.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.0...v1.112.1
|
||||
|
||||
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -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.1"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1218,7 +1209,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
@@ -1241,7 +1232,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1256,9 +1247,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
"deltachat-jsonrpc",
|
||||
"env_logger 0.10.0",
|
||||
"futures-lite",
|
||||
@@ -1279,7 +1271,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1288,7 +1280,7 @@ dependencies = [
|
||||
"libc",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rand 0.7.3",
|
||||
"rand 0.8.5",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -2427,7 +2419,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 +2457,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber",
|
||||
"walkdir",
|
||||
"webpki",
|
||||
"x509-parser",
|
||||
"zeroize",
|
||||
@@ -3026,9 +3019,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 +3060,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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
@@ -23,7 +23,7 @@ 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"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -72,7 +72,6 @@ typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
*
|
||||
* The example above uses "pthreads",
|
||||
* however, you can also use anything else for thread handling.
|
||||
* All deltachat-core functions, unless stated otherwise, are thread-safe.
|
||||
*
|
||||
* Now you can **configure the context:**
|
||||
*
|
||||
@@ -142,6 +141,67 @@ typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
* ~~~
|
||||
*
|
||||
*
|
||||
* ## Thread safety
|
||||
*
|
||||
* All deltachat-core functions, unless stated otherwise, are thread-safe.
|
||||
* In particular, it is safe to pass the same dc_context_t pointer
|
||||
* to multiple functions running concurrently in different threads.
|
||||
*
|
||||
* All the functions are guaranteed not to use the reference passed to them
|
||||
* after returning. If the function spawns a long-running process,
|
||||
* such as dc_configure() or dc_imex(), it will ensure that the objects
|
||||
* passed to them are not deallocated as long as they are needed.
|
||||
* For example, it is safe to call dc_imex(context, ...) and
|
||||
* call dc_context_unref(context) immediately after return from dc_imex().
|
||||
* It is however **not safe** to call dc_context_unref(context) concurrently
|
||||
* until dc_imex() returns, because dc_imex() may have not increased
|
||||
* the reference count of dc_context_t yet.
|
||||
*
|
||||
* This means that the context may be still in use after
|
||||
* dc_context_unref() call.
|
||||
* For example, it is possible to start the import/export process,
|
||||
* call dc_context_unref(context) immediately after
|
||||
* and observe #DC_EVENT_IMEX_PROGRESS events via the event emitter.
|
||||
* Once dc_get_next_event() returns NULL,
|
||||
* it is safe to terminate the application.
|
||||
*
|
||||
* It is recommended to create dc_context_t in the main thread
|
||||
* and only call dc_context_unref() once other threads that may use it,
|
||||
* such as the event loop thread, are terminated.
|
||||
* Common mistake is to use dc_context_unref() as a way
|
||||
* to cause dc_get_next_event() return NULL and terminate event loop this way.
|
||||
* If event loop thread is inside a function taking dc_context_t
|
||||
* as an argument at the moment dc_context_unref() is called on the main thread,
|
||||
* the behavior is undefined.
|
||||
*
|
||||
* Recommended way to safely terminate event loop
|
||||
* and shutdown the application is
|
||||
* to use a boolean variable
|
||||
* indicating that the event loop should stop
|
||||
* and check it in the event loop thread
|
||||
* every time before calling dc_get_next_event().
|
||||
* To terminate the event loop, main thread should:
|
||||
* 1. Notify event loop that it should terminate by atomically setting the
|
||||
* boolean flag in the memory shared between the main thread and event loop.
|
||||
* 2. Call dc_stop_io() or dc_accounts_stop_io(), depending
|
||||
* on whether a single account or account manager is used.
|
||||
* Stopping I/O is guaranteed to emit at least one event
|
||||
* and interrupt the event loop even if it was blocked on dc_get_next_event().
|
||||
* 3. Wait until the event loop thread notices the flag,
|
||||
* exits the event loop and terminates.
|
||||
* 4. Call dc_context_unref() or dc_accounts_unref().
|
||||
* 5. Keep calling dc_get_next_event() in a loop until it returns NULL,
|
||||
* indicating that the contexts are deallocated.
|
||||
* 6. Terminate the application.
|
||||
*
|
||||
* When using C API via FFI in runtimes that use automatic memory management,
|
||||
* such as CPython, JVM or Node.js, take care to ensure the correct
|
||||
* shutdown order and avoid calling dc_context_unref() or dc_accounts_unref()
|
||||
* on the objects still in use in other threads,
|
||||
* e.g. by keeping a reference to the wrapper object.
|
||||
* The details depend on the runtime being used.
|
||||
*
|
||||
*
|
||||
* ## Class reference
|
||||
*
|
||||
* For a class reference, see the "Classes" link atop.
|
||||
@@ -5705,6 +5765,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 +7058,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
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -500,6 +500,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
||||
EventType::SmtpMessageSent(_) => 103,
|
||||
EventType::ImapMessageDeleted(_) => 104,
|
||||
EventType::ImapMessageMoved(_) => 105,
|
||||
EventType::ImapInboxIdle => 106,
|
||||
EventType::NewBlobFile(_) => 150,
|
||||
EventType::DeletedBlobFile(_) => 151,
|
||||
EventType::Warning(_) => 300,
|
||||
@@ -544,6 +545,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::SmtpMessageSent(_)
|
||||
| EventType::ImapMessageDeleted(_)
|
||||
| EventType::ImapMessageMoved(_)
|
||||
| EventType::ImapInboxIdle
|
||||
| EventType::NewBlobFile(_)
|
||||
| EventType::DeletedBlobFile(_)
|
||||
| EventType::Warning(_)
|
||||
@@ -594,6 +596,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::SmtpMessageSent(_)
|
||||
| EventType::ImapMessageDeleted(_)
|
||||
| EventType::ImapMessageMoved(_)
|
||||
| EventType::ImapInboxIdle
|
||||
| EventType::NewBlobFile(_)
|
||||
| EventType::DeletedBlobFile(_)
|
||||
| EventType::Warning(_)
|
||||
@@ -653,6 +656,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
||||
EventType::MsgsChanged { .. }
|
||||
| EventType::ReactionsChanged { .. }
|
||||
| EventType::IncomingMsg { .. }
|
||||
| EventType::ImapInboxIdle
|
||||
| EventType::MsgsNoticed(_)
|
||||
| EventType::MsgDelivered { .. }
|
||||
| EventType::MsgFailed { .. }
|
||||
@@ -4226,6 +4230,10 @@ pub unsafe extern "C" fn dc_backup_provider_wait(provider: *mut dc_backup_provid
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_backup_provider_unref(provider: *mut dc_backup_provider_t) {
|
||||
if provider.is_null() {
|
||||
eprintln!("ignoring careless call to dc_backup_provider_unref()");
|
||||
return;
|
||||
}
|
||||
drop(Box::from_raw(provider));
|
||||
}
|
||||
|
||||
@@ -4240,17 +4248,28 @@ pub unsafe extern "C" fn dc_receive_backup(
|
||||
}
|
||||
let ctx = &*context;
|
||||
let qr_text = to_string_lossy(qr);
|
||||
let qr = match block_on(qr::check_qr(ctx, &qr_text)).log_err(ctx, "Invalid QR code") {
|
||||
receive_backup(ctx.clone(), qr_text)
|
||||
}
|
||||
|
||||
// Because this is a long-running operation make sure we own the Context. This stops a FFI
|
||||
// user from deallocating it by calling unref on the object while we are using it.
|
||||
fn receive_backup(ctx: Context, qr_text: String) -> libc::c_int {
|
||||
let qr = match block_on(qr::check_qr(&ctx, &qr_text))
|
||||
.log_err(&ctx, "Invalid QR code")
|
||||
.context("Invalid QR code")
|
||||
.set_last_error(&ctx)
|
||||
{
|
||||
Ok(qr) => qr,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
spawn(async move {
|
||||
imex::get_backup(ctx, qr)
|
||||
.await
|
||||
.log_err(ctx, "Get backup failed")
|
||||
.ok();
|
||||
});
|
||||
1
|
||||
match block_on(imex::get_backup(&ctx, qr))
|
||||
.log_err(&ctx, "Get backup failed")
|
||||
.context("Get backup failed")
|
||||
.set_last_error(&ctx)
|
||||
{
|
||||
Ok(_) => 1,
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
trait ResultExt<T, E> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.111.0"
|
||||
"version": "1.112.1"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ deltachat_rpc_client = [
|
||||
[project.entry-points.pytest11]
|
||||
"deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
root = ".."
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.111.0"
|
||||
version = "1.112.1"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
@@ -9,11 +9,9 @@ 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" }
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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;
|
||||
@@ -13,6 +15,23 @@ use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
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));
|
||||
}
|
||||
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
|
||||
1285
fuzz/Cargo.lock
generated
1285
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.1"
|
||||
}
|
||||
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -314,6 +319,9 @@ def test_webxdc_message(acfactory, data, lp):
|
||||
assert msg2.text == "message1"
|
||||
assert msg2.is_webxdc()
|
||||
assert msg2.filename
|
||||
ac2._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
|
||||
ac2.direct_imap.select_folder("Inbox")
|
||||
assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True)))) == 1
|
||||
|
||||
|
||||
def test_mvbox_sentbox_threads(acfactory, lp):
|
||||
@@ -513,22 +521,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 +621,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 +692,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,8 @@ and an own build machine.
|
||||
|
||||
- `clippy.sh` runs `cargo clippy` for all Rust code in the project.
|
||||
|
||||
- `deny.sh` runs `cargo deny` for all Rust code in the project.
|
||||
|
||||
- `../.github/workflows` contains jobs run by GitHub Actions.
|
||||
|
||||
- `remote_tests_python.sh` rsyncs to a build machine and runs
|
||||
|
||||
2
scripts/deny.sh
Executable file
2
scripts/deny.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
cargo deny --workspace --all-features check -D warnings
|
||||
@@ -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 -----------------------
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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");
|
||||
info!(self, "Stopping IO for all accounts.");
|
||||
for account in self.accounts.values() {
|
||||
account.stop_io().await;
|
||||
}
|
||||
|
||||
10
src/blob.rs
10
src/blob.rs
@@ -417,7 +417,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
|
||||
);
|
||||
@@ -458,7 +458,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!(context, "Exif orientation value ignored: {other:?}."),
|
||||
}
|
||||
}
|
||||
Ok(0)
|
||||
@@ -490,7 +490,7 @@ impl<'a> BlobDirContents<'a> {
|
||||
match entry {
|
||||
Ok(entry) => Some(entry),
|
||||
Err(err) => {
|
||||
error!(context, "Failed to read blob file: {err}");
|
||||
error!(context, "Failed to read blob file: {err}.");
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -501,7 +501,7 @@ impl<'a> BlobDirContents<'a> {
|
||||
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
|
||||
@@ -569,7 +569,7 @@ fn encoded_img_exceeds_bytes(
|
||||
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(),
|
||||
|
||||
40
src/chat.rs
40
src/chat.rs
@@ -247,7 +247,7 @@ impl ChatId {
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot create chat, contact {} does not exist.", contact_id,
|
||||
"Cannot create chat, contact {contact_id} does not exist."
|
||||
);
|
||||
bail!("Can not create chat for non-existing contact");
|
||||
}
|
||||
@@ -285,7 +285,7 @@ impl ChatId {
|
||||
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
||||
info!(
|
||||
context,
|
||||
"Created group/mailinglist '{}' grpid={} as {}, blocked={}",
|
||||
"Created group/mailinglist '{}' grpid={} as {}, blocked={}.",
|
||||
grpname,
|
||||
grpid,
|
||||
chat_id,
|
||||
@@ -338,14 +338,14 @@ impl ChatId {
|
||||
if contact_id != ContactId::SELF {
|
||||
info!(
|
||||
context,
|
||||
"Blocking the contact {} to block 1:1 chat", contact_id
|
||||
"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!(context, "Can't block groups yet, deleting the chat.");
|
||||
self.delete(context).await?;
|
||||
}
|
||||
Chattype::Mailinglist => {
|
||||
@@ -500,7 +500,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!(context, "Cannot set protection: {e:#}."); // make error user-visible
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
@@ -1070,7 +1070,7 @@ impl ChatId {
|
||||
);
|
||||
info!(
|
||||
context,
|
||||
"set gossiped_timestamp for chat {} to {}.", self, timestamp,
|
||||
"Set gossiped_timestamp for chat {} to {}.", self, timestamp,
|
||||
);
|
||||
|
||||
context
|
||||
@@ -1211,7 +1211,7 @@ impl Chat {
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
"failed to load contacts for {}: {:#}", chat.id, err
|
||||
"Failed to load contacts for {}: {:#}.", chat.id, err
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2032,8 +2032,11 @@ 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!(
|
||||
context,
|
||||
"Cannot recode image, using original data: {err:#}."
|
||||
);
|
||||
}
|
||||
}
|
||||
msg.param.set(Param::File, blob.as_name());
|
||||
@@ -2256,7 +2259,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!(context, "SMTP job cannot get selfavatar-state: {err:#}.");
|
||||
false
|
||||
}
|
||||
};
|
||||
@@ -2283,7 +2286,7 @@ async fn create_send_msg_job(context: &Context, msg_id: MsgId) -> Result<Option<
|
||||
// may happen eg. for groups with only SELF and bcc_self disabled
|
||||
info!(
|
||||
context,
|
||||
"message {} has no recipient, skipping smtp-send", msg_id
|
||||
"Message {msg_id} has no recipient, skipping smtp-send."
|
||||
);
|
||||
msg_id.set_delivered(context).await?;
|
||||
return Ok(None);
|
||||
@@ -2318,27 +2321,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!(context, "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!(context, "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!(context, "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!(context, "Failed to set selfavatar timestamp: {err:#}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2693,8 +2696,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:?}."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3015,7 +3017,7 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
// 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"
|
||||
"Invalid attempt to add self e-mail address to group."
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -3562,7 +3564,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!(context, "Device-message {label} already added.");
|
||||
return Ok(msg_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,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!(context, "Cannot update special chat names: {err:#}.")
|
||||
}
|
||||
|
||||
let str_like_cmd = format!("%{query}%");
|
||||
|
||||
@@ -133,6 +133,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),
|
||||
|
||||
|
||||
@@ -91,15 +91,13 @@ 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -91,7 +92,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()
|
||||
@@ -119,7 +120,6 @@ impl BackupProvider {
|
||||
Ok((provider, ticket)) => (provider, ticket),
|
||||
Err(err) => {
|
||||
context.free_ongoing().await;
|
||||
paused_guard.resume().await;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
@@ -128,7 +128,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
|
||||
})
|
||||
@@ -369,7 +371,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,18 +383,31 @@ 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,
|
||||
|
||||
31
src/job.rs
31
src/job.rs
@@ -153,7 +153,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!(context, "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,7 +165,7 @@ 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!(context, "Increase job {job} tries to {tries}.");
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
@@ -177,26 +177,23 @@ pub(crate) async fn perform_job(context: &Context, mut connection: Connection<'_
|
||||
time_offset
|
||||
);
|
||||
job.save(context).await.unwrap_or_else(|err| {
|
||||
error!(context, "failed to save job: {:#}", err);
|
||||
error!(context, "Failed to save job: {err:#}.");
|
||||
});
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"remove job {} as it exhausted {} retries", job, JOB_RETRIES
|
||||
"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!(context, "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!(context, "Remove job {job} as it failed with error {err:#}.");
|
||||
} else {
|
||||
info!(context, "remove job {} as it succeeded", job);
|
||||
info!(context, "Remove job {job} as it succeeded.");
|
||||
}
|
||||
|
||||
job.delete(context).await.unwrap_or_else(|err| {
|
||||
@@ -212,13 +209,13 @@ async fn perform_job_action(
|
||||
connection: &mut Connection<'_>,
|
||||
tries: u32,
|
||||
) -> Status {
|
||||
info!(context, "begin immediate try {} of job {}", tries, job);
|
||||
info!(context, "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!(context, "Finished immediate try {tries} of job {job}.");
|
||||
|
||||
try_res
|
||||
}
|
||||
@@ -250,7 +247,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!(context, "Interrupt: IMAP.");
|
||||
context
|
||||
.scheduler
|
||||
.interrupt_inbox(InterruptInfo::new(false))
|
||||
@@ -264,7 +261,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!(context, "Loading job.");
|
||||
|
||||
let query;
|
||||
let params;
|
||||
@@ -316,19 +313,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!(context, "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}"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1976,10 +1976,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
|
||||
|
||||
@@ -96,7 +96,7 @@ 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!(context, "Receiving message, seen={seen}...");
|
||||
|
||||
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
|
||||
info!(
|
||||
@@ -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!(context, "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!(context, "receive_imf: no headers found.");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
info!(context, "received message has Message-Id: {}", rfc724_mid);
|
||||
info!(context, "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.
|
||||
@@ -182,7 +182,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
None => {
|
||||
warn!(
|
||||
context,
|
||||
"receive_imf: From field does not contain an acceptable address"
|
||||
"receive_imf: From field does not contain an acceptable address."
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -269,13 +269,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!(context, "receive_imf cannot execute sync items: {err:#}.");
|
||||
}
|
||||
} else {
|
||||
warn!(context, "sync items are not encrypted.");
|
||||
warn!(context, "Sync items are not encrypted.");
|
||||
}
|
||||
} else {
|
||||
warn!(context, "sync items not sent by self.");
|
||||
warn!(context, "Sync items not sent by self.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,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!(context, "receive_imf cannot update status: {err:#}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,10 +302,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"receive_imf cannot update profile image: {:#}", err
|
||||
);
|
||||
warn!(context, "receive_imf cannot update profile image: {err:#}.");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -331,7 +328,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(context, "cannot update contact status: {:#}", err);
|
||||
warn!(context, "Cannot update contact status: {err:#}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +391,7 @@ pub async fn from_field_to_contact_id(
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot create a contact for the given From field: {:#}.", err
|
||||
"Cannot create a contact for the given From field: {err:#}."
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -476,7 +473,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!(context, "Classical email not shown (TRASH).");
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
allow_creation = false;
|
||||
}
|
||||
@@ -519,7 +516,7 @@ async fn add_parts(
|
||||
securejoin_seen = false;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Error in Secure-Join message handling: {:#}", err);
|
||||
warn!(context, "Error in Secure-Join message handling: {err:#}.");
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
securejoin_seen = true;
|
||||
}
|
||||
@@ -536,7 +533,8 @@ 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!(context, "Message is a DSN (TRASH).",);
|
||||
markseen_on_imap_table(context, rfc724_mid).await.ok();
|
||||
}
|
||||
|
||||
if chat_id.is_none() {
|
||||
@@ -754,7 +752,7 @@ async fn add_parts(
|
||||
chat_id = None;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "Error in Secure-Join watching: {:#}", err);
|
||||
warn!(context, "Error in Secure-Join watching: {err:#}.");
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
}
|
||||
@@ -771,7 +769,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!(context, "Email is probably just a draft (TRASH).");
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
|
||||
@@ -863,14 +861,15 @@ 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!(context, "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!(context, "Message is a status update only (TRASH).");
|
||||
markseen_on_imap_table(context, rfc724_mid).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -880,7 +879,7 @@ async fn add_parts(
|
||||
DC_CHAT_ID_TRASH
|
||||
} else {
|
||||
chat_id.unwrap_or_else(|| {
|
||||
info!(context, "No chat id for message (TRASH)");
|
||||
info!(context, "No chat id for message (TRASH).");
|
||||
DC_CHAT_ID_TRASH
|
||||
})
|
||||
};
|
||||
@@ -892,10 +891,7 @@ async fn add_parts(
|
||||
match value.parse::<EphemeralTimer>() {
|
||||
Ok(timer) => timer,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"can't parse ephemeral timer \"{}\": {:#}", value, err
|
||||
);
|
||||
warn!(context, "Can't parse ephemeral timer \"{value}\": {err:#}.");
|
||||
EphemeralTimer::Disabled
|
||||
}
|
||||
}
|
||||
@@ -915,12 +911,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!(context, "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?
|
||||
@@ -936,9 +927,7 @@ async fn add_parts(
|
||||
// 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)
|
||||
@@ -950,12 +939,12 @@ async fn add_parts(
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"failed to modify timer for chat {}: {:#}", chat_id, err
|
||||
"Failed to modify timer for chat {chat_id}: {err:#}."
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"updated ephemeral timer to {:?} for chat {}", ephemeral_timer, chat_id
|
||||
"Updated ephemeral timer to {ephemeral_timer:?} for chat {chat_id}."
|
||||
);
|
||||
if mime_parser.is_system_message != SystemMessage::EphemeralTimerChanged {
|
||||
chat::add_info_msg(
|
||||
@@ -970,7 +959,7 @@ async fn add_parts(
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"ignoring ephemeral timer change to {:?} because it's outdated", ephemeral_timer
|
||||
"Ignoring ephemeral timer change to {ephemeral_timer:?} because it is outdated."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -999,7 +988,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!(context, "Verification problem: {err:#}.");
|
||||
let s = format!("{err}. See 'Info' for more details");
|
||||
mime_parser.repl_msg_by_error(&s);
|
||||
} else {
|
||||
@@ -1429,7 +1418,7 @@ 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 +1488,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!(context, "Creating ad-hoc group prevented from caller.");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
@@ -1525,7 +1514,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!(context, "Verification problem: {err:#}.");
|
||||
let s = format!("{err}. See 'Info' for more details");
|
||||
mime_parser.repl_msg_by_error(&s);
|
||||
}
|
||||
@@ -1557,7 +1546,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!(context, "Creating group forbidden by caller.");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -1618,7 +1607,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!(context, "Message belongs to unwanted group (TRASH).");
|
||||
Ok(Some((DC_CHAT_ID_TRASH, Blocked::Not)))
|
||||
}
|
||||
}
|
||||
@@ -1658,7 +1647,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!(context, "Removed {removed_addr:?} has no contact_id."),
|
||||
}
|
||||
} else {
|
||||
removed_id = None;
|
||||
@@ -1683,7 +1672,7 @@ async fn apply_group_changes(
|
||||
.update_timestamp(context, Param::GroupNameTimestamp, sent_timestamp)
|
||||
.await?
|
||||
{
|
||||
info!(context, "updating grpname for chat {}", chat_id);
|
||||
info!(context, "Updating grpname for chat {chat_id}.");
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
@@ -1723,7 +1712,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!(context, "Verification problem: {err:#}.");
|
||||
let s = format!("{err}. See 'Info' for more details");
|
||||
mime_parser.repl_msg_by_error(&s);
|
||||
}
|
||||
@@ -1743,9 +1732,7 @@ async fn apply_group_changes(
|
||||
{
|
||||
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 +1765,7 @@ async fn apply_group_changes(
|
||||
}
|
||||
members_to_add.dedup();
|
||||
|
||||
info!(
|
||||
context,
|
||||
"adding {:?} to chat id={}", members_to_add, chat_id
|
||||
);
|
||||
info!(context, "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;
|
||||
}
|
||||
@@ -1791,17 +1775,15 @@ async fn apply_group_changes(
|
||||
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
|
||||
"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!(context, "Group-avatar change for {chat_id}.");
|
||||
if chat
|
||||
.param
|
||||
.update_timestamp(Param::AvatarTimestamp, sent_timestamp)?
|
||||
@@ -1935,7 +1917,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 +1925,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!(context, "Creating list forbidden by caller.");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -2035,7 +2017,7 @@ async fn create_adhoc_group(
|
||||
if mime_parser.is_mailinglist_message() {
|
||||
info!(
|
||||
context,
|
||||
"not creating ad-hoc group for mailing list message"
|
||||
"Not creating ad-hoc group for mailing list message."
|
||||
);
|
||||
|
||||
return Ok(None);
|
||||
@@ -2052,13 +2034,13 @@ async fn create_adhoc_group(
|
||||
// Instead, assign the message to 1:1 chat with the sender.
|
||||
warn!(
|
||||
context,
|
||||
"not creating ad-hoc group for message that cannot be decrypted"
|
||||
"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!(context, "Not creating ad-hoc group: too few contacts.");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -2165,7 +2147,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
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 self::connectivity::ConnectivityStore;
|
||||
@@ -13,6 +13,7 @@ use crate::config::Config;
|
||||
use crate::contact::{ContactId, RecentlySeenLoop};
|
||||
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;
|
||||
@@ -89,20 +90,28 @@ 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.
|
||||
@@ -194,31 +203,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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +478,7 @@ 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!(
|
||||
|
||||
22
src/sql.rs
22
src/sql.rs
@@ -699,26 +699,26 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
if let Err(err) = remove_unused_files(context).await {
|
||||
warn!(
|
||||
context,
|
||||
"Housekeeping: cannot remove unused files: {:#}", err
|
||||
"Housekeeping: cannot remove unused files: {:#}.", err
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(err) = start_ephemeral_timers(context).await {
|
||||
warn!(
|
||||
context,
|
||||
"Housekeeping: cannot start ephemeral timers: {:#}", err
|
||||
"Housekeeping: cannot start ephemeral timers: {:#}.", err
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(err) = prune_tombstones(&context.sql).await {
|
||||
warn!(
|
||||
context,
|
||||
"Housekeeping: Cannot prune message tombstones: {:#}", err
|
||||
"Housekeeping: Cannot prune message tombstones: {:#}.", err
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(err) = deduplicate_peerstates(&context.sql).await {
|
||||
warn!(context, "Failed to deduplicate peerstates: {:#}", err)
|
||||
warn!(context, "Failed to deduplicate peerstates: {:#}.", err)
|
||||
}
|
||||
|
||||
context.schedule_quota_update().await?;
|
||||
@@ -731,7 +731,7 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
warn!(context, "Failed to run incremental vacuum: {err:#}");
|
||||
warn!(context, "Failed to run incremental vacuum: {err:#}.");
|
||||
}
|
||||
Ok(Some(())) => {
|
||||
// Incremental vacuum returns a zero-column result if it did anything.
|
||||
@@ -747,7 +747,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!(context, "Can't set config: {e:#}.");
|
||||
}
|
||||
|
||||
context
|
||||
@@ -814,7 +814,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!(context, "{} 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] {
|
||||
@@ -846,7 +846,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
|
||||
// environment f.e. So, no warning.
|
||||
info!(
|
||||
context,
|
||||
"Housekeeping: Cannot rmdir {}: {:#}",
|
||||
"Housekeeping: Cannot rmdir {}: {:#}.",
|
||||
entry.path().display(),
|
||||
e
|
||||
);
|
||||
@@ -868,7 +868,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
|
||||
{
|
||||
info!(
|
||||
context,
|
||||
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
|
||||
"Housekeeping: Keeping new unreferenced file #{}: {:?}.",
|
||||
unreferenced_count,
|
||||
entry.file_name(),
|
||||
);
|
||||
@@ -879,7 +879,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
|
||||
}
|
||||
info!(
|
||||
context,
|
||||
"Housekeeping: Deleting unreferenced file #{}: {:?}",
|
||||
"Housekeeping: Deleting unreferenced file #{}: {:?}.",
|
||||
unreferenced_count,
|
||||
entry.file_name()
|
||||
);
|
||||
@@ -897,7 +897,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Housekeeping: Cannot read dir {}: {:#}",
|
||||
"Housekeeping: Cannot read dir {}: {:#}.",
|
||||
p.display(),
|
||||
err
|
||||
);
|
||||
|
||||
@@ -165,6 +165,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> {
|
||||
@@ -180,7 +193,7 @@ impl Context {
|
||||
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);
|
||||
@@ -188,7 +201,7 @@ impl Context {
|
||||
}
|
||||
};
|
||||
|
||||
if archive.entry("index.html").is_none() {
|
||||
if find_zip_entry(archive.file(), "index.html").is_none() {
|
||||
info!(self, "{} misses index.html", &filename);
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -215,7 +228,7 @@ impl Context {
|
||||
|
||||
let valid = match async_zip::read::fs::ZipFileReader::new(path).await {
|
||||
Ok(archive) => {
|
||||
if archive.entry("index.html").is_none() {
|
||||
if find_zip_entry(archive.file(), "index.html").is_none() {
|
||||
info!(self, "{} misses index.html", filename);
|
||||
false
|
||||
} else {
|
||||
@@ -649,10 +662,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)
|
||||
@@ -754,9 +766,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()
|
||||
|
||||
Reference in New Issue
Block a user