mirror of
https://github.com/chatmail/core.git
synced 2026-04-06 23:52:11 +03:00
Compare commits
7 Commits
link2xt/de
...
flub/ongoi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9fd5296bb | ||
|
|
4aa248da74 | ||
|
|
b7edd4e57a | ||
|
|
61b00f9991 | ||
|
|
32629b9f9e | ||
|
|
201d05d4fa | ||
|
|
0c5d1832ae |
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@@ -14,7 +14,8 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- stable
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
@@ -82,9 +83,9 @@ jobs:
|
||||
- os: macos-latest
|
||||
rust: 1.73.0
|
||||
|
||||
# Minimum Supported Rust Version = 1.70.0
|
||||
# Minimum Supported Rust Version = 1.67.0
|
||||
- os: ubuntu-latest
|
||||
rust: 1.70.0
|
||||
rust: 1.67.0
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -130,7 +131,7 @@ jobs:
|
||||
name: Build deltachat-rpc-server
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -145,7 +146,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||
path: ${{ matrix.os == 'windows-latest' && 'target/debug/deltachat-rpc-server.exe' || 'target/debug/deltachat-rpc-server' }}
|
||||
path: target/debug/deltachat-rpc-server
|
||||
retention-days: 1
|
||||
|
||||
python_lint:
|
||||
@@ -166,8 +167,8 @@ jobs:
|
||||
working-directory: deltachat-rpc-client
|
||||
run: tox -e lint
|
||||
|
||||
cffi_python_tests:
|
||||
name: CFFI Python tests
|
||||
python_tests:
|
||||
name: Python tests
|
||||
needs: ["c_library", "python_lint"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -217,8 +218,8 @@ jobs:
|
||||
working-directory: python
|
||||
run: tox -e mypy,doc,py
|
||||
|
||||
rpc_python_tests:
|
||||
name: JSON-RPC Python tests
|
||||
aysnc_python_tests:
|
||||
name: Async Python tests
|
||||
needs: ["python_lint", "rpc_server"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -228,8 +229,6 @@ jobs:
|
||||
python: 3.12
|
||||
- os: macos-latest
|
||||
python: 3.12
|
||||
- os: windows-latest
|
||||
python: 3.12
|
||||
|
||||
# PyPy tests
|
||||
- os: ubuntu-latest
|
||||
@@ -260,18 +259,11 @@ jobs:
|
||||
path: target/debug
|
||||
|
||||
- name: Make deltachat-rpc-server executable
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: chmod +x target/debug/deltachat-rpc-server
|
||||
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
||||
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
"${{ github.workspace }}/target/debug" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Run deltachat-rpc-client tests
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
|
||||
4
.github/workflows/jsonrpc.yml
vendored
4
.github/workflows/jsonrpc.yml
vendored
@@ -2,9 +2,9 @@ name: JSON-RPC API Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
2
.github/workflows/node-docs.yml
vendored
2
.github/workflows/node-docs.yml
vendored
@@ -8,7 +8,7 @@ name: Generate & upload node.js documentation
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
|
||||
2
.github/workflows/node-tests.yml
vendored
2
.github/workflows/node-tests.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
|
||||
3
.github/workflows/upload-docs.yml
vendored
3
.github/workflows/upload-docs.yml
vendored
@@ -3,7 +3,8 @@ name: Build & Deploy Documentation on rs.delta.chat
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- docs-gh-action
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
3
.github/workflows/upload-ffi-docs.yml
vendored
3
.github/workflows/upload-ffi-docs.yml
vendored
@@ -7,7 +7,8 @@ name: Build & Deploy Documentation on cffi.delta.chat
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- docs-gh-action
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
67
CHANGELOG.md
67
CHANGELOG.md
@@ -1,69 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.127.1] - 2023-10-27
|
||||
|
||||
### API-Changes
|
||||
|
||||
- jsonrpc: add `.is_protection_broken` to `FullChat` and `BasicChat`.
|
||||
- jsonrpc: Add `id` to `ProviderInfo`.
|
||||
|
||||
## [1.127.0] - 2023-10-26
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] `dc_accounts_new` API is changed. Unused `os_name` argument is removed and `writable` argument is added.
|
||||
- jsonrpc: Add `resend_messages`.
|
||||
- [**breaking**] Remove unused function `is_verified_ex()` ([#4551](https://github.com/deltachat/deltachat-core-rust/pull/4551))
|
||||
- [**breaking**] Make `MsgId.delete_from_db()` private.
|
||||
- [**breaking**] deltachat-jsonrpc: use `kind` as a tag for all union types
|
||||
- json-rpc: Force stickers to be sent as stickers ([#4819](https://github.com/deltachat/deltachat-core-rust/pull/4819)).
|
||||
- Add mailto parse api ([#4829](https://github.com/deltachat/deltachat-core-rust/pull/4829)).
|
||||
- [**breaking**] Remove unused `DC_STR_PROTECTION_(EN)ABLED` strings
|
||||
- [**breaking**] Remove unused `dc_set_chat_protection()`
|
||||
- Hide `DcSecretKey` trait from the API.
|
||||
- Verified 1:1 chats ([#4315](https://github.com/deltachat/deltachat-core-rust/pull/4315)). Disabled by default, enable with `verified_one_on_one_chats` config.
|
||||
|
||||
### CI
|
||||
|
||||
- Run Rust tests with `RUST_BACKTRACE` set.
|
||||
- Replace `master` branch with `main`. Run CI only on `main` branch pushes.
|
||||
- Test `deltachat-rpc-client` on Windows.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document how logs and error messages should be formatted in `CONTRIBUTING.md`.
|
||||
- Clarify transitive behaviour of `dc_contact_is_verfified()`.
|
||||
- Document `configured_addr`.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Add lockfile to account manager ([#4314](https://github.com/deltachat/deltachat-core-rust/pull/4314)).
|
||||
- Don't show a contact as verified if their key changed since the verification ([#4574](https://github.com/deltachat/deltachat-core-rust/pull/4574)).
|
||||
- deltachat-rpc-server: Add `--openrpc` option to print OpenRPC specification for JSON-RPC API. This specification can be used to generate JSON-RPC API clients.
|
||||
- Track whether contact is a bot or not ([#4821](https://github.com/deltachat/deltachat-core-rust/pull/4821)).
|
||||
- Replace `Config::SendSyncMsgs` with `SyncMsgs` ([#4817](https://github.com/deltachat/deltachat-core-rust/pull/4817)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Don't create 1:1 chat as protected for contact who doesn't prefer to encrypt ([#4538](https://github.com/deltachat/deltachat-core-rust/pull/4538)).
|
||||
- Allow to save a draft if the verification is broken ([#4542](https://github.com/deltachat/deltachat-core-rust/pull/4542)).
|
||||
- Fix info-message orderings of verified 1:1 chats ([#4545](https://github.com/deltachat/deltachat-core-rust/pull/4545)).
|
||||
- Fix example; this was changed some time ago, see https://docs.webxdc.org/spec.html#sendupdate
|
||||
- `receive_imf`: Update peerstate from db after handling Securejoin handshake ([#4600](https://github.com/deltachat/deltachat-core-rust/pull/4600)).
|
||||
- Sort old incoming messages below all outgoing ones ([#4621](https://github.com/deltachat/deltachat-core-rust/pull/4621)).
|
||||
- Do not mark non-verified group chats as verified when using securejoin.
|
||||
- `receive_imf`: Set protection only for Chattype::Single ([#4597](https://github.com/deltachat/deltachat-core-rust/pull/4597)).
|
||||
- Return from `dc_get_chatlist(DC_GCL_FOR_FORWARDING)` only chats where we can send ([#4616](https://github.com/deltachat/deltachat-core-rust/pull/4616)).
|
||||
- Clear VerifiedOneOnOneChats config on backup ([#4615](https://github.com/deltachat/deltachat-core-rust/pull/4615)).
|
||||
- Try removal of accounts multiple times with timeouts in case the database file is blocked (restore `try_many_times` workaround).
|
||||
|
||||
### Build system
|
||||
|
||||
- Remove examples/simple.rs.
|
||||
- Increase MSRV to 1.70.0.
|
||||
- Update dependencies.
|
||||
- Switch to iroh 0.4.x fork with updated dependencies.
|
||||
|
||||
## [1.126.1] - 2023-10-24
|
||||
|
||||
### Fixes
|
||||
@@ -3031,6 +2967,3 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.124.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.124.0...v1.124.1
|
||||
[1.125.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.124.1...v1.125.0
|
||||
[1.126.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.125.0...v1.126.0
|
||||
[1.126.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.126.0...v1.126.1
|
||||
[1.127.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.126.1...v1.127.0
|
||||
[1.127.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.127.0...v1.127.1
|
||||
|
||||
980
Cargo.lock
generated
980
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.127.1"
|
||||
version = "1.126.1"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.67"
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
@@ -26,13 +26,17 @@ opt-level = "z"
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
|
||||
[patch.crates-io]
|
||||
quinn-udp = { git = "https://github.com/quinn-rs/quinn", branch="main" }
|
||||
quinn-proto = { git = "https://github.com/quinn-rs/quinn", branch="main" }
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
format-flowed = { path = "./format-flowed" }
|
||||
ratelimit = { path = "./deltachat-ratelimit" }
|
||||
|
||||
anyhow = "1"
|
||||
async-channel = "2.0.0"
|
||||
async-channel = "1.8.0"
|
||||
async-imap = { version = "0.9.1", 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"] }
|
||||
@@ -47,12 +51,12 @@ escaper = "0.1"
|
||||
fast-socks5 = "0.8"
|
||||
fd-lock = "3.0.11"
|
||||
futures = "0.3"
|
||||
futures-lite = "2.0.0"
|
||||
futures-lite = "1.13.0"
|
||||
hex = "0.4.0"
|
||||
hickory-resolver = "0.24"
|
||||
humansize = "2"
|
||||
image = { version = "0.24.7", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh = { git = "https://github.com/deltachat/iroh", branch = "0.4-update-quic", default-features = false }
|
||||
iroh = { version = "0.4.1", default-features = false }
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2"
|
||||
@@ -65,10 +69,9 @@ once_cell = "1.18.0"
|
||||
percent-encoding = "2.3"
|
||||
parking_lot = "0.12"
|
||||
pgp = { version = "0.10", default-features = false }
|
||||
pin-project = "1"
|
||||
pretty_env_logger = { version = "0.5", optional = true }
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = "0.31"
|
||||
quick-xml = "0.30"
|
||||
rand = "0.8"
|
||||
regex = "1.9"
|
||||
reqwest = { version = "0.11.20", features = ["json"] }
|
||||
@@ -97,7 +100,7 @@ uuid = { version = "1", features = ["serde", "v4"] }
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||
futures-lite = "2.0.0"
|
||||
futures-lite = "1.13"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.5"
|
||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml">
|
||||
<img alt="Rust CI" src="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml/badge.svg">
|
||||
<a href="https://github.com/yoav-lavi/melody/actions/workflows/rust.yml">
|
||||
<img alt="Rust CI" src="https://github.com/yoav-lavi/melody/actions/workflows/rust.yml/badge.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.127.1"
|
||||
version = "1.126.1"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.127.1"
|
||||
version = "1.126.1"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
@@ -19,12 +19,12 @@ schemars = "0.8.13"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.8.0"
|
||||
log = "0.4"
|
||||
async-channel = { version = "2.0.0" }
|
||||
async-channel = { version = "1.8.0" }
|
||||
futures = { version = "0.3.28" }
|
||||
serde_json = "1.0.105"
|
||||
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
|
||||
typescript-type-def = { version = "0.5.8", features = ["json_value"] }
|
||||
tokio = { version = "1.33.0" }
|
||||
tokio = { version = "1.32.0" }
|
||||
sanitize-filename = "0.5"
|
||||
walkdir = "2.3.3"
|
||||
base64 = "0.21"
|
||||
@@ -34,7 +34,7 @@ axum = { version = "0.6.20", optional = true, features = ["ws"] }
|
||||
env_logger = { version = "0.10.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.33.0", features = ["full", "rt-multi-thread"] }
|
||||
tokio = { version = "1.32.0", features = ["full", "rt-multi-thread"] }
|
||||
|
||||
|
||||
[features]
|
||||
|
||||
@@ -31,7 +31,6 @@ pub struct FullChat {
|
||||
fresh_message_counter: usize,
|
||||
// is_group - please check over chat.type in frontend instead
|
||||
is_contact_request: bool,
|
||||
is_protection_broken: bool,
|
||||
is_device_chat: bool,
|
||||
self_in_group: bool,
|
||||
is_muted: bool,
|
||||
@@ -101,7 +100,6 @@ impl FullChat {
|
||||
color,
|
||||
fresh_message_counter,
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_protection_broken: chat.is_protection_broken(),
|
||||
is_device_chat: chat.is_device_talk(),
|
||||
self_in_group: contact_ids.contains(&ContactId::SELF),
|
||||
is_muted: chat.is_muted(),
|
||||
@@ -136,7 +134,6 @@ pub struct BasicChat {
|
||||
is_self_talk: bool,
|
||||
color: String,
|
||||
is_contact_request: bool,
|
||||
is_protection_broken: bool,
|
||||
is_device_chat: bool,
|
||||
is_muted: bool,
|
||||
}
|
||||
@@ -163,7 +160,6 @@ impl BasicChat {
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
color,
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_protection_broken: chat.is_protection_broken(),
|
||||
is_device_chat: chat.is_device_talk(),
|
||||
is_muted: chat.is_muted(),
|
||||
})
|
||||
|
||||
@@ -6,8 +6,6 @@ use typescript_type_def::TypeDef;
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProviderInfo {
|
||||
/// Unique ID, corresponding to provider database filename.
|
||||
pub id: String,
|
||||
pub before_login_hint: String,
|
||||
pub overview_page: String,
|
||||
pub status: u32, // in reality this is an enum, but for simplicity and because it gets converted into a number anyway, we use an u32 here.
|
||||
@@ -16,7 +14,6 @@ pub struct ProviderInfo {
|
||||
impl ProviderInfo {
|
||||
pub fn from_dc_type(provider: Option<&Provider>) -> Option<Self> {
|
||||
provider.map(|p| ProviderInfo {
|
||||
id: p.id.to_owned(),
|
||||
before_login_hint: p.before_login_hint.to_owned(),
|
||||
overview_page: p.overview_page.to_owned(),
|
||||
status: p.status.to_u32().unwrap(),
|
||||
|
||||
@@ -17,7 +17,7 @@ mod tests {
|
||||
let accounts = Accounts::new(tmp_dir, writable).await?;
|
||||
let api = CommandApi::new(accounts);
|
||||
|
||||
let (sender, receiver) = unbounded::<String>();
|
||||
let (sender, mut receiver) = unbounded::<String>();
|
||||
|
||||
let (client, mut rx) = RpcClient::new();
|
||||
let session = RpcSession::new(client, api);
|
||||
@@ -36,17 +36,17 @@ mod tests {
|
||||
let request = r#"{"jsonrpc":"2.0","method":"add_account","params":[],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
let result = receiver.next().await;
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, response.to_owned());
|
||||
assert_eq!(result, Some(response.to_owned()));
|
||||
}
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"get_all_account_ids","params":[],"id":2}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":[1]}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
let result = receiver.next().await;
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, response.to_owned());
|
||||
assert_eq!(result, Some(response.to_owned()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -59,7 +59,7 @@ mod tests {
|
||||
let accounts = Accounts::new(tmp_dir, writable).await?;
|
||||
let api = CommandApi::new(accounts);
|
||||
|
||||
let (sender, receiver) = unbounded::<String>();
|
||||
let (sender, mut receiver) = unbounded::<String>();
|
||||
|
||||
let (client, mut rx) = RpcClient::new();
|
||||
let session = RpcSession::new(client, api);
|
||||
@@ -78,15 +78,15 @@ mod tests {
|
||||
let request = r#"{"jsonrpc":"2.0","method":"add_account","params":[],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
assert_eq!(result, response.to_owned());
|
||||
let result = receiver.next().await;
|
||||
assert_eq!(result, Some(response.to_owned()));
|
||||
}
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"batch_set_config","id":2,"params":[1,{"addr":"","mail_user":"","mail_pw":"","mail_server":"","mail_port":"","mail_security":"","imap_certificate_checks":"","send_user":"","send_pw":"","send_server":"","send_port":"","send_security":"","smtp_certificate_checks":"","socks5_enabled":"0","socks5_host":"","socks5_port":"","socks5_user":"","socks5_password":""}]}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":null}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
assert_eq!(result, response.to_owned());
|
||||
let result = receiver.next().await;
|
||||
assert_eq!(result, Some(response.to_owned()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.127.1"
|
||||
"version": "1.126.1"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.127.1"
|
||||
version = "1.126.1"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -71,3 +71,6 @@ line-length = 120
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
log_cli = true
|
||||
|
||||
@@ -358,34 +358,3 @@ def test_openrpc_command_line() -> None:
|
||||
openrpc = json.loads(out)
|
||||
assert "openrpc" in openrpc
|
||||
assert "methods" in openrpc
|
||||
|
||||
|
||||
def test_provider_info(rpc) -> None:
|
||||
account_id = rpc.add_account()
|
||||
|
||||
provider_info = rpc.get_provider_info(account_id, "example.org")
|
||||
assert provider_info["id"] == "example.com"
|
||||
|
||||
provider_info = rpc.get_provider_info(account_id, "uep7oiw4ahtaizuloith.org")
|
||||
assert provider_info is None
|
||||
|
||||
# Test MX record resolution.
|
||||
provider_info = rpc.get_provider_info(account_id, "github.com")
|
||||
assert provider_info["id"] == "gmail"
|
||||
|
||||
# Disable MX record resolution.
|
||||
rpc.set_config(account_id, "socks5_enabled", "1")
|
||||
provider_info = rpc.get_provider_info(account_id, "github.com")
|
||||
assert provider_info is None
|
||||
|
||||
|
||||
def test_qr_setup_contact(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
qr_code, _svg = alice.get_qr_code()
|
||||
bob.secure_join(qr_code)
|
||||
|
||||
while True:
|
||||
event = alice.wait_for_event()
|
||||
if event["kind"] == "SecurejoinInviterProgress" and event["progress"] == 1000:
|
||||
return
|
||||
|
||||
@@ -29,5 +29,3 @@ commands =
|
||||
|
||||
[pytest]
|
||||
timeout = 60
|
||||
log_cli = true
|
||||
log_level = info
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.127.1"
|
||||
version = "1.126.1"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
@@ -15,12 +15,11 @@ deltachat = { path = "..", default-features = false }
|
||||
|
||||
anyhow = "1"
|
||||
env_logger = { version = "0.10.0" }
|
||||
futures-lite = "2.0.0"
|
||||
futures-lite = "1.13.0"
|
||||
log = "0.4"
|
||||
num_cpus = "1"
|
||||
serde_json = "1.0.105"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.33.0", features = ["io-std"] }
|
||||
tokio = { version = "1.32.0", features = ["io-std"] }
|
||||
tokio-util = "0.7.9"
|
||||
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
|
||||
|
||||
|
||||
@@ -20,18 +20,9 @@ use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
fn main() {
|
||||
// Build multithreaded runtime with at least two threads.
|
||||
// This ensures that on systems with one CPU
|
||||
// such as CI runners there are at least two threads
|
||||
// and it is more difficult to deadlock.
|
||||
let r = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(std::cmp::max(2, num_cpus::get()))
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(main_impl());
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() {
|
||||
let r = main_impl().await;
|
||||
// From tokio documentation:
|
||||
// "For technical reasons, stdin is implemented by using an ordinary blocking read on a separate
|
||||
// thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang
|
||||
|
||||
10
deny.toml
10
deny.toml
@@ -11,7 +11,6 @@ ignore = [
|
||||
# when upgrading.
|
||||
# Please keep this list alphabetically sorted.
|
||||
skip = [
|
||||
{ name = "async-channel", version = "1.9.0" },
|
||||
{ name = "base16ct", version = "0.1.1" },
|
||||
{ name = "base64", version = "<0.21" },
|
||||
{ name = "bitflags", version = "1.3.2" },
|
||||
@@ -25,7 +24,7 @@ skip = [
|
||||
{ name = "digest", version = "<0.10" },
|
||||
{ name = "ed25519-dalek", version = "1.0.1" },
|
||||
{ name = "ed25519", version = "1.5.3" },
|
||||
{ name = "event-listener", version = "2.5.3" },
|
||||
{ name = "fastrand", version = "1.9.0" },
|
||||
{ name = "getrandom", version = "<0.2" },
|
||||
{ name = "hashbrown", version = "<0.14.0" },
|
||||
{ name = "indexmap", version = "<2.0.0" },
|
||||
@@ -37,7 +36,6 @@ skip = [
|
||||
{ name = "rand", version = "<0.8" },
|
||||
{ name = "redox_syscall", version = "0.2.16" },
|
||||
{ name = "regex-automata", version = "0.1.10" },
|
||||
{ name = "ring", version = "0.16.20" },
|
||||
{ name = "regex-syntax", version = "0.6.29" },
|
||||
{ name = "sec1", version = "0.3.0" },
|
||||
{ name = "sha2", version = "<0.10" },
|
||||
@@ -47,12 +45,15 @@ skip = [
|
||||
{ name = "spki", version = "0.6.0" },
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "time", version = "<0.3" },
|
||||
{ name = "untrusted", version = "0.7.1" },
|
||||
{ name = "wasi", version = "<0.11" },
|
||||
{ name = "windows_aarch64_gnullvm", version = "<0.48" },
|
||||
{ name = "windows_aarch64_msvc", version = "<0.48" },
|
||||
{ name = "windows_i686_gnu", version = "<0.48" },
|
||||
{ name = "windows_i686_msvc", version = "<0.48" },
|
||||
{ name = "windows-sys", version = "<0.48" },
|
||||
{ name = "windows-targets", version = "<0.48" },
|
||||
{ name = "windows", version = "0.32.0" },
|
||||
{ name = "windows_x86_64_gnullvm", version = "<0.48" },
|
||||
{ name = "windows_x86_64_gnu", version = "<0.48" },
|
||||
{ name = "windows_x86_64_msvc", version = "<0.48" },
|
||||
]
|
||||
@@ -86,4 +87,5 @@ license-files = [
|
||||
github = [
|
||||
"async-email",
|
||||
"deltachat",
|
||||
"quinn-rs",
|
||||
]
|
||||
|
||||
@@ -270,7 +270,7 @@ describe('Basic offline Tests', function () {
|
||||
'quota_exceeding',
|
||||
'scan_all_folders_debounce_secs',
|
||||
'selfavatar',
|
||||
'sync_msgs',
|
||||
'send_sync_msgs',
|
||||
'sentbox_watch',
|
||||
'show_emails',
|
||||
'socks5_enabled',
|
||||
|
||||
@@ -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.127.1"
|
||||
"version": "1.126.1"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2023-10-27
|
||||
2023-10-24
|
||||
@@ -14,23 +14,16 @@ and an own build machine.
|
||||
|
||||
- `../.github/workflows` contains jobs run by GitHub Actions.
|
||||
|
||||
- `run-python-test.sh` runs CFFI Python tests.
|
||||
|
||||
- `run-rpc-test.sh` runs JSON-RPC Python tests.
|
||||
|
||||
- `make-python-testenv.sh` creates a local python test development environment with CFFI bindings.
|
||||
Reusing the same environment is faster than running `run-python-test.sh` which always
|
||||
recreates environment from scratch and runs additional lints.
|
||||
|
||||
- `make-rpc-testenv.sh` creates a local python development environment with JSON-RPC bindings,
|
||||
i.e. `deltachat-rpc-client` and `deltachat-rpc-server`.
|
||||
|
||||
- `remote_tests_python.sh` rsyncs to a build machine and runs
|
||||
`run-python-test.sh` remotely on the build machine.
|
||||
|
||||
- `remote_tests_rust.sh` rsyncs to the build machine and runs
|
||||
`run-rust-test.sh` remotely on the build machine.
|
||||
|
||||
- `make-python-testenv.sh` creates local python test development environment.
|
||||
Reusing the same environment is faster than running `run-python-test.sh` which always
|
||||
recreates environment from scratch and runs additional lints.
|
||||
|
||||
- `run-doxygen.sh` generates C-docs which are then uploaded to https://c.delta.chat/
|
||||
|
||||
- `run_all.sh` builds Python wheels
|
||||
|
||||
@@ -3,14 +3,14 @@ resources:
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
branch: main
|
||||
branch: master
|
||||
uri: https://github.com/deltachat/deltachat-core-rust.git
|
||||
|
||||
- name: deltachat-core-rust-release
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
branch: main
|
||||
branch: master
|
||||
uri: https://github.com/deltachat/deltachat-core-rust.git
|
||||
tag_filter: "v*"
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
tox -c deltachat-rpc-client -e py --devenv venv
|
||||
venv/bin/pip install --upgrade pip
|
||||
cargo install --path deltachat-rpc-server/ --root "$PWD/venv"
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cargo install --path deltachat-rpc-server/ --root "$PWD/venv"
|
||||
PATH="$PWD/venv/bin:$PATH" tox -c deltachat-rpc-client
|
||||
@@ -1,7 +1,6 @@
|
||||
//! # Account manager module.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
@@ -156,7 +155,7 @@ impl Accounts {
|
||||
if let Some(cfg) = self.config.get_account(id) {
|
||||
let account_path = self.dir.join(cfg.dir);
|
||||
|
||||
try_many_times(|| fs::remove_dir_all(&account_path))
|
||||
fs::remove_dir_all(&account_path)
|
||||
.await
|
||||
.context("failed to remove account data")?;
|
||||
}
|
||||
@@ -192,10 +191,10 @@ impl Accounts {
|
||||
fs::create_dir_all(self.dir.join(&account_config.dir))
|
||||
.await
|
||||
.context("failed to create dir")?;
|
||||
try_many_times(|| fs::rename(&dbfile, &new_dbfile))
|
||||
fs::rename(&dbfile, &new_dbfile)
|
||||
.await
|
||||
.context("failed to rename dbfile")?;
|
||||
try_many_times(|| fs::rename(&blobdir, &new_blobdir))
|
||||
fs::rename(&blobdir, &new_blobdir)
|
||||
.await
|
||||
.context("failed to rename blobdir")?;
|
||||
if walfile.exists() {
|
||||
@@ -220,7 +219,7 @@ impl Accounts {
|
||||
}
|
||||
Err(err) => {
|
||||
let account_path = std::path::PathBuf::from(&account_config.dir);
|
||||
try_many_times(|| fs::remove_dir_all(&account_path))
|
||||
fs::remove_dir_all(&account_path)
|
||||
.await
|
||||
.context("failed to remove account data")?;
|
||||
self.config.remove_account(account_config.id).await?;
|
||||
@@ -561,37 +560,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Spend up to 1 minute trying to do the operation.
|
||||
///
|
||||
/// Even if Delta Chat itself does not hold the file lock,
|
||||
/// there may be other processes such as antivirus,
|
||||
/// or the filesystem may be network-mounted.
|
||||
///
|
||||
/// Without this workaround removing account may fail on Windows with an error
|
||||
/// "The process cannot access the file because it is being used by another process. (os error 32)".
|
||||
async fn try_many_times<F, Fut, T>(f: F) -> std::result::Result<(), T>
|
||||
where
|
||||
F: Fn() -> Fut,
|
||||
Fut: Future<Output = std::result::Result<(), T>>,
|
||||
{
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
counter += 1;
|
||||
|
||||
if let Err(err) = f().await {
|
||||
if counter > 60 {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// Wait 1 second and try again.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configuration of a single account.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
struct AccountConfig {
|
||||
|
||||
@@ -297,9 +297,10 @@ pub enum Config {
|
||||
#[strum(props(default = "0"))]
|
||||
DownloadLimit,
|
||||
|
||||
/// Enable sending and executing (applying) sync messages. Sending requires `BccSelf` to be set.
|
||||
/// Send sync messages, requires `BccSelf` to be set as well.
|
||||
/// In a future versions, this switch may be removed.
|
||||
#[strum(props(default = "0"))]
|
||||
SyncMsgs,
|
||||
SendSyncMsgs,
|
||||
|
||||
/// Space-separated list of all the authserv-ids which we believe
|
||||
/// may be the one of our email server.
|
||||
@@ -500,7 +501,7 @@ impl Context {
|
||||
| Config::Configured
|
||||
| Config::Bot
|
||||
| Config::NotifyAboutWrongPw
|
||||
| Config::SyncMsgs
|
||||
| Config::SendSyncMsgs
|
||||
| Config::SignUnencrypted
|
||||
| Config::DisableIdle => {
|
||||
ensure!(
|
||||
|
||||
@@ -73,18 +73,16 @@ impl Context {
|
||||
self.sql.is_open().await,
|
||||
"cannot configure, database not opened."
|
||||
);
|
||||
let cancel_channel = self.alloc_ongoing().await?;
|
||||
let ongoing_guard = self.alloc_ongoing().await?;
|
||||
|
||||
let res = self
|
||||
.inner_configure()
|
||||
.race(cancel_channel.recv().map(|_| {
|
||||
.race(ongoing_guard.map(|_| {
|
||||
progress!(self, 0);
|
||||
Ok(())
|
||||
}))
|
||||
.await;
|
||||
|
||||
self.free_ongoing().await;
|
||||
|
||||
if let Err(err) = res.as_ref() {
|
||||
progress!(
|
||||
self,
|
||||
|
||||
169
src/context.rs
169
src/context.rs
@@ -2,16 +2,18 @@
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::ffi::OsString;
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use async_channel::{self as channel, Receiver, Sender};
|
||||
use ratelimit::Ratelimit;
|
||||
use tokio::sync::{Mutex, Notify, RwLock};
|
||||
use tokio::sync::{oneshot, Mutex, Notify, RwLock};
|
||||
|
||||
use crate::chat::{get_chat_cnt, ChatId};
|
||||
use crate::config::Config;
|
||||
@@ -250,7 +252,7 @@ pub struct InnerContext {
|
||||
#[derive(Debug)]
|
||||
enum RunningState {
|
||||
/// Ongoing process is allocated.
|
||||
Running { cancel_sender: Sender<()> },
|
||||
Running { cancel_sender: oneshot::Sender<()> },
|
||||
|
||||
/// Cancel signal has been sent, waiting for ongoing process to be freed.
|
||||
ShallStop { request: Instant },
|
||||
@@ -502,51 +504,66 @@ impl Context {
|
||||
/// This is for modal operations during which no other user actions are allowed. Only
|
||||
/// one such operation is allowed at any given time.
|
||||
///
|
||||
/// The return value is a cancel token, which will release the ongoing mutex when
|
||||
/// dropped.
|
||||
pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
|
||||
/// The return value is a guard which does two things:
|
||||
///
|
||||
/// - It is a Future which will complete when the ongoing process is cancelled using
|
||||
/// [`Context::stop_ongoing`] and must stop.
|
||||
/// - It will free the ongoing process, aka release the mutex, when dropped.
|
||||
pub(crate) async fn alloc_ongoing(&self) -> Result<OngoingGuard> {
|
||||
let mut s = self.running_state.write().await;
|
||||
ensure!(
|
||||
matches!(*s, RunningState::Stopped),
|
||||
"There is already another ongoing process running."
|
||||
);
|
||||
|
||||
let (sender, receiver) = channel::bounded(1);
|
||||
let (cancel_tx, cancel_rx) = oneshot::channel();
|
||||
*s = RunningState::Running {
|
||||
cancel_sender: sender,
|
||||
cancel_sender: cancel_tx,
|
||||
};
|
||||
let (drop_tx, drop_rx) = oneshot::channel();
|
||||
let context = self.clone();
|
||||
|
||||
Ok(receiver)
|
||||
}
|
||||
tokio::spawn(async move {
|
||||
drop_rx.await.ok();
|
||||
let mut s = context.running_state.write().await;
|
||||
if let RunningState::ShallStop { request } = *s {
|
||||
info!(context, "Ongoing stopped in {:?}", request.elapsed());
|
||||
}
|
||||
*s = RunningState::Stopped;
|
||||
});
|
||||
|
||||
pub(crate) async fn free_ongoing(&self) {
|
||||
let mut s = self.running_state.write().await;
|
||||
if let RunningState::ShallStop { request } = *s {
|
||||
info!(self, "Ongoing stopped in {:?}", request.elapsed());
|
||||
}
|
||||
*s = RunningState::Stopped;
|
||||
Ok(OngoingGuard {
|
||||
cancel_rx,
|
||||
drop_tx: Some(drop_tx),
|
||||
})
|
||||
}
|
||||
|
||||
/// Signal an ongoing process to stop.
|
||||
pub async fn stop_ongoing(&self) {
|
||||
let mut s = self.running_state.write().await;
|
||||
match &*s {
|
||||
RunningState::Running { cancel_sender } => {
|
||||
if let Err(err) = cancel_sender.send(()).await {
|
||||
warn!(self, "could not cancel ongoing: {:#}", err);
|
||||
}
|
||||
info!(self, "Signaling the ongoing process to stop ASAP.",);
|
||||
*s = RunningState::ShallStop {
|
||||
request: Instant::now(),
|
||||
};
|
||||
}
|
||||
|
||||
// Take out the state so we can call the oneshot sender (which takes ownership).
|
||||
let current_state = std::mem::replace(
|
||||
&mut *s,
|
||||
RunningState::ShallStop {
|
||||
request: Instant::now(),
|
||||
},
|
||||
);
|
||||
|
||||
match current_state {
|
||||
RunningState::Running { cancel_sender } => match cancel_sender.send(()) {
|
||||
Ok(()) => info!(self, "Signaling the ongoing process to stop ASAP."),
|
||||
Err(()) => warn!(self, "could not cancel ongoing"),
|
||||
},
|
||||
RunningState::ShallStop { .. } | RunningState::Stopped => {
|
||||
// Put back the current state
|
||||
*s = current_state;
|
||||
info!(self, "No ongoing process to stop.",);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn shall_stop_ongoing(&self) -> bool {
|
||||
match &*self.running_state.read().await {
|
||||
RunningState::Running { .. } => false,
|
||||
@@ -584,7 +601,7 @@ impl Context {
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
|
||||
let bcc_self = self.get_config_int(Config::BccSelf).await?;
|
||||
let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
|
||||
let send_sync_msgs = self.get_config_int(Config::SendSyncMsgs).await?;
|
||||
let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
|
||||
|
||||
let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
|
||||
@@ -697,7 +714,7 @@ impl Context {
|
||||
self.get_config_int(Config::KeyGenType).await?.to_string(),
|
||||
);
|
||||
res.insert("bcc_self", bcc_self.to_string());
|
||||
res.insert("sync_msgs", sync_msgs.to_string());
|
||||
res.insert("send_sync_msgs", send_sync_msgs.to_string());
|
||||
res.insert("disable_idle", disable_idle.to_string());
|
||||
res.insert("private_key_count", prv_key_cnt.to_string());
|
||||
res.insert("public_key_count", pub_key_cnt.to_string());
|
||||
@@ -1034,6 +1051,54 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Guard received when calling [`Context::alloc_ongoing`].
|
||||
///
|
||||
/// While holding this guard the ongoing mutex is held, dropping this guard frees the
|
||||
/// ongoing process.
|
||||
///
|
||||
/// The ongoing process can also be cancelled by unrelated code calling
|
||||
/// [`Context::stop_ongoing`]. This guard implements [`Future`] and the future will
|
||||
/// complete when the ongoing process is cancelled and must be aborted. Freeing the ongoing
|
||||
/// process works as usual in this case: when this guard is dropped. So if you need to do
|
||||
/// some more work before freeing make sure to keep ownership of the guard, e.g.:
|
||||
///
|
||||
/// ```no_compile
|
||||
/// let mut guard = context.alloc_ongoing().await?;
|
||||
/// tokio::select!{
|
||||
/// biased;
|
||||
/// _ = &mut guard => (), // guard is not moved, so we keep ownership.
|
||||
/// _ = do_work() => (),
|
||||
/// };
|
||||
/// do_cleaup().await;
|
||||
/// drop(guard);
|
||||
/// ```
|
||||
pub(crate) struct OngoingGuard {
|
||||
/// Receives a message when the ongoing process should be cancelled.
|
||||
cancel_rx: oneshot::Receiver<()>,
|
||||
/// Used by `Drop` to send a message which will free the ongoing process.
|
||||
drop_tx: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl Future for OngoingGuard {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
match Pin::new(&mut self.cancel_rx).poll(cx) {
|
||||
Poll::Ready(_) => Poll::Ready(()),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OngoingGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(sender) = self.drop_tx.take() {
|
||||
// TODO: Maybe this should log? But we'd need to have a context.
|
||||
sender.send(()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns core version as a string.
|
||||
pub fn get_version_str() -> &'static str {
|
||||
&DC_VERSION_STR
|
||||
@@ -1524,38 +1589,52 @@ mod tests {
|
||||
async fn test_ongoing() -> Result<()> {
|
||||
let context = TestContext::new().await;
|
||||
|
||||
// No ongoing process allocated.
|
||||
println!("No ongoing process allocated.");
|
||||
assert!(context.shall_stop_ongoing().await);
|
||||
|
||||
let receiver = context.alloc_ongoing().await?;
|
||||
let mut guard = context.alloc_ongoing().await?;
|
||||
|
||||
// Cannot allocate another ongoing process while the first one is running.
|
||||
println!("Cannot allocate another ongoing process while the first one is running.");
|
||||
assert!(context.alloc_ongoing().await.is_err());
|
||||
|
||||
// Stop signal is not sent yet.
|
||||
assert!(receiver.try_recv().is_err());
|
||||
println!("Stop signal is not sent yet.");
|
||||
assert!(matches!(futures::poll!(&mut guard), Poll::Pending));
|
||||
|
||||
assert!(!context.shall_stop_ongoing().await);
|
||||
|
||||
// Send the stop signal.
|
||||
println!("Send the stop signal.");
|
||||
context.stop_ongoing().await;
|
||||
|
||||
// Receive stop signal.
|
||||
receiver.recv().await?;
|
||||
println!("Receive stop signal.");
|
||||
(&mut guard).await;
|
||||
|
||||
assert!(context.shall_stop_ongoing().await);
|
||||
|
||||
// Ongoing process is still running even though stop signal was received,
|
||||
// so another one cannot be allocated.
|
||||
println!("Ongoing process still running even though stop signal was received");
|
||||
assert!(context.alloc_ongoing().await.is_err());
|
||||
|
||||
context.free_ongoing().await;
|
||||
println!("free the ongoing process");
|
||||
// context.free_ongoing().await;
|
||||
drop(guard);
|
||||
|
||||
// No ongoing process allocated, should have been stopped already.
|
||||
assert!(context.shall_stop_ongoing().await);
|
||||
|
||||
// Another ongoing process can be allocated now.
|
||||
let _receiver = context.alloc_ongoing().await?;
|
||||
println!("re-acquire the ongoing process");
|
||||
// Since the drop guard needs to send a message and the receiving task must run and
|
||||
// acquire a lock this needs some time so won't succeed immediately.
|
||||
#[allow(clippy::async_yields_async)]
|
||||
let _guard = tokio::time::timeout(Duration::from_secs(10), async {
|
||||
loop {
|
||||
match context.alloc_ongoing().await {
|
||||
Ok(guard) => break guard,
|
||||
Err(_) => {
|
||||
// tokio::task::yield_now() results in a lot hotter loop, it takes a
|
||||
// lot of yields.
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("timeout");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! # Events specification.
|
||||
|
||||
use async_channel::{self as channel, Receiver, Sender, TrySendError};
|
||||
use pin_project::pin_project;
|
||||
|
||||
mod payload;
|
||||
|
||||
@@ -65,8 +64,7 @@ impl Events {
|
||||
/// [`Context::get_event_emitter`]: crate::context::Context::get_event_emitter
|
||||
/// [`Stream`]: futures::stream::Stream
|
||||
#[derive(Debug, Clone)]
|
||||
#[pin_project]
|
||||
pub struct EventEmitter(#[pin] Receiver<Event>);
|
||||
pub struct EventEmitter(Receiver<Event>);
|
||||
|
||||
impl EventEmitter {
|
||||
/// Async recv of an event. Return `None` if the `Sender` has been dropped.
|
||||
@@ -79,10 +77,10 @@ impl futures::stream::Stream for EventEmitter {
|
||||
type Item = Event;
|
||||
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
self.project().0.poll_next(cx)
|
||||
std::pin::Pin::new(&mut self.0).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,18 +89,17 @@ pub async fn imex(
|
||||
path: &Path,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<()> {
|
||||
let cancel = context.alloc_ongoing().await?;
|
||||
let ongoing_guard = context.alloc_ongoing().await?;
|
||||
|
||||
let res = {
|
||||
let _guard = context.scheduler.pause(context.clone()).await?;
|
||||
imex_inner(context, what, path, passphrase)
|
||||
.race(async {
|
||||
cancel.recv().await.ok();
|
||||
Err(format_err!("canceled"))
|
||||
ongoing_guard.await;
|
||||
Err(format_err!("cancelled"))
|
||||
})
|
||||
.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 {:#}:
|
||||
|
||||
@@ -30,7 +30,6 @@ use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, format_err, Context as _, Result};
|
||||
use async_channel::Receiver;
|
||||
use futures_lite::StreamExt;
|
||||
use iroh::blobs::Collection;
|
||||
use iroh::get::DataStream;
|
||||
@@ -48,7 +47,7 @@ use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::blob::BlobDirContents;
|
||||
use crate::chat::{add_device_msg, delete_and_reset_all_device_msgs};
|
||||
use crate::context::Context;
|
||||
use crate::context::{Context, OngoingGuard};
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::qr::{self, Qr};
|
||||
use crate::stock_str::backup_transfer_msg_body;
|
||||
@@ -98,8 +97,8 @@ impl BackupProvider {
|
||||
.context("Private key not available, aborting backup export")?;
|
||||
|
||||
// Acquire global "ongoing" mutex.
|
||||
let cancel_token = context.alloc_ongoing().await?;
|
||||
let paused_guard = context.scheduler.pause(context.clone()).await?;
|
||||
let mut ongoing_guard = context.alloc_ongoing().await?;
|
||||
let paused_guard = context.scheduler.pause(context.clone()).await;
|
||||
let context_dir = context
|
||||
.get_blobdir()
|
||||
.parent()
|
||||
@@ -110,7 +109,7 @@ impl BackupProvider {
|
||||
warn!(context, "Previous database export deleted");
|
||||
}
|
||||
let dbfile = TempPathGuard::new(dbfile);
|
||||
let res = tokio::select! {
|
||||
let (provider, ticket) = tokio::select! {
|
||||
biased;
|
||||
res = Self::prepare_inner(context, &dbfile) => {
|
||||
match res {
|
||||
@@ -121,22 +120,14 @@ impl BackupProvider {
|
||||
},
|
||||
}
|
||||
},
|
||||
_ = cancel_token.recv() => Err(format_err!("cancelled")),
|
||||
};
|
||||
let (provider, ticket) = match res {
|
||||
Ok((provider, ticket)) => (provider, ticket),
|
||||
Err(err) => {
|
||||
context.free_ongoing().await;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
_ = &mut ongoing_guard => Err(format_err!("cancelled")),
|
||||
}?;
|
||||
let drop_token = CancellationToken::new();
|
||||
let handle = {
|
||||
let context = context.clone();
|
||||
let drop_token = drop_token.clone();
|
||||
tokio::spawn(async move {
|
||||
let res = Self::watch_provider(&context, provider, cancel_token, drop_token).await;
|
||||
context.free_ongoing().await;
|
||||
let res = Self::watch_provider(&context, provider, ongoing_guard, drop_token).await;
|
||||
|
||||
// Explicit drop to move the guards into this future
|
||||
drop(paused_guard);
|
||||
@@ -201,7 +192,7 @@ impl BackupProvider {
|
||||
async fn watch_provider(
|
||||
context: &Context,
|
||||
mut provider: Provider,
|
||||
cancel_token: Receiver<()>,
|
||||
mut cancel_token: OngoingGuard,
|
||||
drop_token: CancellationToken,
|
||||
) -> Result<()> {
|
||||
let mut events = provider.subscribe();
|
||||
@@ -261,7 +252,7 @@ impl BackupProvider {
|
||||
}
|
||||
}
|
||||
},
|
||||
_ = cancel_token.recv() => {
|
||||
_ = &mut cancel_token => {
|
||||
provider.shutdown();
|
||||
break Err(anyhow!("BackupProvider cancelled"));
|
||||
},
|
||||
@@ -394,20 +385,18 @@ pub async fn get_backup(context: &Context, qr: Qr) -> Result<()> {
|
||||
"Cannot import backups to accounts in use."
|
||||
);
|
||||
// Acquire global "ongoing" mutex.
|
||||
let cancel_token = context.alloc_ongoing().await?;
|
||||
let mut cancel_token = context.alloc_ongoing().await?;
|
||||
let _guard = context.scheduler.pause(context.clone()).await;
|
||||
info!(
|
||||
context,
|
||||
"Running get_backup for {}",
|
||||
qr::format_backup(&qr)?
|
||||
);
|
||||
let res = tokio::select! {
|
||||
tokio::select! {
|
||||
biased;
|
||||
res = get_backup_inner(context, qr) => res,
|
||||
_ = cancel_token.recv() => Err(format_err!("cancelled")),
|
||||
};
|
||||
context.free_ongoing().await;
|
||||
res
|
||||
_ = &mut cancel_token => Err(format_err!("cancelled")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_backup_inner(context: &Context, qr: Qr) -> Result<()> {
|
||||
|
||||
@@ -1208,9 +1208,6 @@ impl MimeMessage {
|
||||
}
|
||||
msg_type
|
||||
} else if filename == "multi-device-sync.json" {
|
||||
if !context.get_config_bool(Config::SyncMsgs).await? {
|
||||
return Ok(());
|
||||
}
|
||||
let serialized = String::from_utf8_lossy(decoded_data)
|
||||
.parse()
|
||||
.unwrap_or_default();
|
||||
|
||||
30
src/sync.rs
30
src/sync.rs
@@ -43,6 +43,13 @@ pub(crate) struct SyncItems {
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Checks if sync messages shall be sent.
|
||||
/// Receiving sync messages is currently always enabled;
|
||||
/// the messages are force-encrypted anyway.
|
||||
async fn is_sync_sending_enabled(&self) -> Result<bool> {
|
||||
self.get_config_bool(Config::SendSyncMsgs).await
|
||||
}
|
||||
|
||||
/// Adds an item to the list of items that should be synchronized to other devices.
|
||||
pub(crate) async fn add_sync_item(&self, data: SyncData) -> Result<()> {
|
||||
self.add_sync_item_with_timestamp(data, time()).await
|
||||
@@ -51,7 +58,7 @@ impl Context {
|
||||
/// Adds item and timestamp to the list of items that should be synchronized to other devices.
|
||||
/// If device synchronization is disabled, the function does nothing.
|
||||
async fn add_sync_item_with_timestamp(&self, data: SyncData, timestamp: i64) -> Result<()> {
|
||||
if !self.get_config_bool(Config::SyncMsgs).await? {
|
||||
if !self.is_sync_sending_enabled().await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -68,7 +75,7 @@ impl Context {
|
||||
/// If device synchronization is disabled,
|
||||
/// no tokens exist or the chat is unpromoted, the function does nothing.
|
||||
pub(crate) async fn sync_qr_code_tokens(&self, chat_id: Option<ChatId>) -> Result<()> {
|
||||
if !self.get_config_bool(Config::SyncMsgs).await? {
|
||||
if !self.is_sync_sending_enabled().await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -260,20 +267,20 @@ mod tests {
|
||||
use crate::token::Namespace;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_config_sync_msgs() -> Result<()> {
|
||||
async fn test_is_sync_sending_enabled() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert!(!t.get_config_bool(Config::SyncMsgs).await?);
|
||||
t.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
assert!(t.get_config_bool(Config::SyncMsgs).await?);
|
||||
t.set_config_bool(Config::SyncMsgs, false).await?;
|
||||
assert!(!t.get_config_bool(Config::SyncMsgs).await?);
|
||||
assert!(!t.is_sync_sending_enabled().await?);
|
||||
t.set_config_bool(Config::SendSyncMsgs, true).await?;
|
||||
assert!(t.is_sync_sending_enabled().await?);
|
||||
t.set_config_bool(Config::SendSyncMsgs, false).await?;
|
||||
assert!(!t.is_sync_sending_enabled().await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_build_sync_json() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
t.set_config_bool(Config::SendSyncMsgs, true).await?;
|
||||
|
||||
assert!(t.build_sync_json().await?.is_none());
|
||||
|
||||
@@ -318,7 +325,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_build_sync_json_sync_msgs_off() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config_bool(Config::SyncMsgs, false).await?;
|
||||
t.set_config_bool(Config::SendSyncMsgs, false).await?;
|
||||
t.add_sync_item(SyncData::AddQrToken(QrTokenData {
|
||||
invitenumber: "testinvite".to_string(),
|
||||
auth: "testauth".to_string(),
|
||||
@@ -446,7 +453,7 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_send_sync_msg() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
alice.set_config_bool(Config::SendSyncMsgs, true).await?;
|
||||
alice
|
||||
.add_sync_item(SyncData::AddQrToken(QrTokenData {
|
||||
invitenumber: "in".to_string(),
|
||||
@@ -473,7 +480,6 @@ mod tests {
|
||||
// also here, self-talk should stay hidden
|
||||
let sent_msg = alice.pop_sent_msg().await;
|
||||
let alice2 = TestContext::new_alice().await;
|
||||
alice2.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
alice2.recv_msg(&sent_msg).await;
|
||||
assert!(token::exists(&alice2, token::Namespace::Auth, "testtoken").await);
|
||||
assert_eq!(Chatlist::try_load(&alice2, 0, None, None).await?.len(), 0);
|
||||
|
||||
106
src/tools.rs
106
src/tools.rs
@@ -20,7 +20,6 @@ use mailparse::headers::Headers;
|
||||
use mailparse::MailHeaderMap;
|
||||
use rand::{thread_rng, Rng};
|
||||
use tokio::{fs, io};
|
||||
use url::Url;
|
||||
|
||||
use crate::chat::{add_device_msg, add_device_msg_with_importance};
|
||||
use crate::constants::{DC_ELLIPSIS, DC_OUTDATED_WARNING_DAYS};
|
||||
@@ -482,43 +481,7 @@ pub(crate) fn time() -> i64 {
|
||||
.as_secs() as i64
|
||||
}
|
||||
|
||||
/// Struct containing all mailto information
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
pub struct MailTo {
|
||||
pub to: Vec<EmailAddress>,
|
||||
pub subject: Option<String>,
|
||||
pub body: Option<String>,
|
||||
}
|
||||
|
||||
/// Parse mailto urls
|
||||
pub fn parse_mailto(mailto_url: &str) -> Option<MailTo> {
|
||||
if let Ok(url) = Url::parse(mailto_url) {
|
||||
if url.scheme() == "mailto" {
|
||||
let mut mailto: MailTo = Default::default();
|
||||
// Extract the email address
|
||||
url.path().split(',').for_each(|email| {
|
||||
if let Ok(email) = EmailAddress::new(email) {
|
||||
mailto.to.push(email);
|
||||
}
|
||||
});
|
||||
|
||||
// Extract query parameters
|
||||
for (key, value) in url.query_pairs() {
|
||||
if key == "subject" {
|
||||
mailto.subject = Some(value.to_string());
|
||||
} else if key == "body" {
|
||||
mailto.body = Some(value.to_string());
|
||||
}
|
||||
}
|
||||
Some(mailto)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Very simple email address wrapper.
|
||||
///
|
||||
/// Represents an email address, right now just the `name@domain` portion.
|
||||
///
|
||||
@@ -761,18 +724,18 @@ mod tests {
|
||||
|
||||
let raw = include_bytes!("../test-data/message/wrong-html.eml");
|
||||
let expected =
|
||||
"Hop: From: oxbsltgw18.schlund.de; By: mrelayeu.kundenserver.de; Date: Thu, 6 Aug 2020 16:40:31 +0000\n\
|
||||
Hop: From: mout.kundenserver.de; By: dd37930.kasserver.com; Date: Thu, 6 Aug 2020 16:40:32 +0000";
|
||||
"Hop: From: oxbsltgw18.schlund.de; By: mrelayeu.kundenserver.de; Date: Thu, 06 Aug 2020 16:40:31 +0000\n\
|
||||
Hop: From: mout.kundenserver.de; By: dd37930.kasserver.com; Date: Thu, 06 Aug 2020 16:40:32 +0000";
|
||||
check_parse_receive_headers(raw, expected);
|
||||
|
||||
let raw = include_bytes!("../test-data/message/posteo_ndn.eml");
|
||||
let expected =
|
||||
"Hop: By: mout01.posteo.de; Date: Tue, 9 Jun 2020 18:44:22 +0000\n\
|
||||
Hop: From: mout01.posteo.de; By: mx04.posteo.de; Date: Tue, 9 Jun 2020 18:44:22 +0000\n\
|
||||
Hop: From: mx04.posteo.de; By: mailin06.posteo.de; Date: Tue, 9 Jun 2020 18:44:23 +0000\n\
|
||||
Hop: From: mailin06.posteo.de; By: proxy02.posteo.de; Date: Tue, 9 Jun 2020 18:44:23 +0000\n\
|
||||
Hop: From: proxy02.posteo.de; By: proxy02.posteo.name; Date: Tue, 9 Jun 2020 18:44:23 +0000\n\
|
||||
Hop: From: proxy02.posteo.name; By: dovecot03.posteo.local; Date: Tue, 9 Jun 2020 18:44:24 +0000";
|
||||
"Hop: By: mout01.posteo.de; Date: Tue, 09 Jun 2020 18:44:22 +0000\n\
|
||||
Hop: From: mout01.posteo.de; By: mx04.posteo.de; Date: Tue, 09 Jun 2020 18:44:22 +0000\n\
|
||||
Hop: From: mx04.posteo.de; By: mailin06.posteo.de; Date: Tue, 09 Jun 2020 18:44:23 +0000\n\
|
||||
Hop: From: mailin06.posteo.de; By: proxy02.posteo.de; Date: Tue, 09 Jun 2020 18:44:23 +0000\n\
|
||||
Hop: From: proxy02.posteo.de; By: proxy02.posteo.name; Date: Tue, 09 Jun 2020 18:44:23 +0000\n\
|
||||
Hop: From: proxy02.posteo.name; By: dovecot03.posteo.local; Date: Tue, 09 Jun 2020 18:44:24 +0000";
|
||||
check_parse_receive_headers(raw, expected);
|
||||
}
|
||||
|
||||
@@ -1320,55 +1283,4 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
|
||||
assert_eq!(remove_subject_prefix("Fwd: Subject"), "Subject");
|
||||
assert_eq!(remove_subject_prefix("Fw: Subject"), "Subject");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mailto() {
|
||||
let mailto_url = "mailto:someone@example.com";
|
||||
let reps = parse_mailto(mailto_url);
|
||||
assert_eq!(
|
||||
Some(MailTo {
|
||||
to: vec![EmailAddress {
|
||||
local: "someone".to_string(),
|
||||
domain: "example.com".to_string()
|
||||
}],
|
||||
subject: None,
|
||||
body: None
|
||||
}),
|
||||
reps
|
||||
);
|
||||
|
||||
let mailto_url = "mailto:someone@example.com?subject=Hello%20World";
|
||||
let reps = parse_mailto(mailto_url);
|
||||
assert_eq!(
|
||||
Some(MailTo {
|
||||
to: vec![EmailAddress {
|
||||
local: "someone".to_string(),
|
||||
domain: "example.com".to_string()
|
||||
}],
|
||||
subject: Some("Hello World".to_string()),
|
||||
body: None
|
||||
}),
|
||||
reps
|
||||
);
|
||||
|
||||
let mailto_url = "mailto:someone@example.com,someoneelse@example.com?subject=Hello%20World&body=This%20is%20a%20test";
|
||||
let reps = parse_mailto(mailto_url);
|
||||
assert_eq!(
|
||||
Some(MailTo {
|
||||
to: vec![
|
||||
EmailAddress {
|
||||
local: "someone".to_string(),
|
||||
domain: "example.com".to_string()
|
||||
},
|
||||
EmailAddress {
|
||||
local: "someoneelse".to_string(),
|
||||
domain: "example.com".to_string()
|
||||
}
|
||||
],
|
||||
subject: Some("Hello World".to_string()),
|
||||
body: Some("This is a test".to_string())
|
||||
}),
|
||||
reps
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user