mirror of
https://github.com/chatmail/core.git
synced 2026-06-26 01:26:36 +03:00
Compare commits
25 Commits
hoc/fix-tw
...
integrate-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c14e5086d7 | ||
|
|
4779383401 | ||
|
|
47d30ef6d3 | ||
|
|
3a38ffdfe0 | ||
|
|
33d548eccc | ||
|
|
1700af2c8d | ||
|
|
01920a1a00 | ||
|
|
7c67ea0b8a | ||
|
|
0c64701984 | ||
|
|
d2d35fe26b | ||
|
|
a0b4d016d5 | ||
|
|
3ce70ee244 | ||
|
|
c43c9b9107 | ||
|
|
638d2ff932 | ||
|
|
0213bb372f | ||
|
|
d54fa65ff3 | ||
|
|
564d283852 | ||
|
|
8357b3a98c | ||
|
|
177f89f678 | ||
|
|
372425f38f | ||
|
|
53f8274c6f | ||
|
|
65b242aa5c | ||
|
|
bb6d7767b5 | ||
|
|
227a75a5f7 | ||
|
|
8fb46d0b56 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
fmt:
|
||||
@@ -83,13 +83,13 @@ jobs:
|
||||
rust: 1.61.0
|
||||
python: false # Python bindings compilation on Windows is not supported.
|
||||
|
||||
# Minimum Supported Rust Version = 1.56.1
|
||||
# Minimum Supported Rust Version = 1.56.0
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.56.1
|
||||
rust: 1.56.0
|
||||
python: 3.7
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
||||
66
.github/workflows/jsonrpc_api.yml
vendored
Normal file
66
.github/workflows/jsonrpc_api.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: JSON-RPC API Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
- name: Build
|
||||
run: cargo build --verbose --features webserver -p deltachat-jsonrpc
|
||||
- name: Run tests
|
||||
run: cargo test --verbose --features webserver -p deltachat-jsonrpc
|
||||
|
||||
ts_bindings:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
- name: npm i
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm i
|
||||
- name: npm run generate-bindings
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run generate-bindings
|
||||
- name: npm run check ts
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npx tsc --noEmit
|
||||
- name: run integration tests
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run build
|
||||
cargo build --features webserver
|
||||
npm run test:integration
|
||||
- name: run prettier
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run prettier:check
|
||||
14
.npmignore
14
.npmignore
@@ -40,3 +40,17 @@ node/old_docs.md
|
||||
.vscode/
|
||||
.github/
|
||||
node/.prettierrc.yml
|
||||
|
||||
deltachat-jsonrpc/TODO.md
|
||||
deltachat-jsonrpc/README.MD
|
||||
deltachat-jsonrpc/.gitignore
|
||||
deltachat-jsonrpc/typescript/.gitignore
|
||||
deltachat-jsonrpc/typescript/.prettierignore
|
||||
deltachat-jsonrpc/typescript/accounts/
|
||||
deltachat-jsonrpc/typescript/index.html
|
||||
deltachat-jsonrpc/typescript/node-demo.js
|
||||
deltachat-jsonrpc/typescript/report_api_coverage.mjs
|
||||
deltachat-jsonrpc/typescript/test
|
||||
deltachat-jsonrpc/typescript/example.ts
|
||||
|
||||
.DS_Store
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -2,16 +2,6 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Changes
|
||||
- switch from `async-std` to `tokio` as the async runtime #3449
|
||||
|
||||
### Fixes
|
||||
- mailing list: remove square-brackets only for first name #3452
|
||||
- do not use footers from mailinglists as the contact status #3460
|
||||
|
||||
|
||||
## 1.87.0
|
||||
|
||||
### Changes
|
||||
- limit the rate of MDN sending #3402
|
||||
- ignore ratelimits for bots #3439
|
||||
@@ -19,7 +9,6 @@
|
||||
- format message lines starting with `>` as quotes #3434
|
||||
- node: remove `split2` dependency #3418
|
||||
- node: add git installation info to readme #3418
|
||||
- limit the rate of webxdc update sending #3417
|
||||
|
||||
### Fixes
|
||||
- set a default error if NDN does not provide an error
|
||||
@@ -35,7 +24,6 @@
|
||||
- python: added `Message.is_videochat_invitation()` #3416
|
||||
- python: added support for "videochat" and "webxdc" view types to `Message.new_empty()` #3416
|
||||
|
||||
|
||||
## 1.86.0
|
||||
|
||||
### API-Changes
|
||||
|
||||
@@ -21,7 +21,7 @@ add_custom_command(
|
||||
PREFIX=${CMAKE_INSTALL_PREFIX}
|
||||
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
|
||||
${CARGO} build --release --no-default-features
|
||||
${CARGO} build --release --no-default-features --features jsonrpc
|
||||
|
||||
# Build in `deltachat-ffi` directory instead of using
|
||||
# `--package deltachat_ffi` to avoid feature resolver version
|
||||
|
||||
1864
Cargo.lock
generated
1864
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
37
Cargo.toml
37
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.87.0"
|
||||
version = "1.86.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
@@ -9,7 +9,6 @@ rust-version = "1.56"
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
panic = 'abort'
|
||||
opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
@@ -20,19 +19,19 @@ deltachat_derive = { path = "./deltachat_derive" }
|
||||
|
||||
ansi_term = { version = "0.12.1", optional = true }
|
||||
anyhow = "1"
|
||||
async-imap = { version = "0.6", default-features = false, features = ["runtime-tokio"] }
|
||||
async-native-tls = { version = "0.4", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.5", default-features = false, features = ["smtp-transport", "socks5", "runtime-tokio"] }
|
||||
trust-dns-resolver = "0.21"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||
async-imap = { git = "https://github.com/async-email/async-imap" }
|
||||
async-native-tls = { version = "0.3" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", branch="master", default-features=false, features = ["smtp-transport", "socks5"] }
|
||||
async-std-resolver = "0.21"
|
||||
async-std = { version = "1" }
|
||||
async-tar = { version = "0.4", default-features=false }
|
||||
backtrace = "0.3"
|
||||
base64 = "0.13"
|
||||
bitflags = "1.3"
|
||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||
dirs = { version = "4", optional=true }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
encoded-words = "0.2"
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
escaper = "0.1"
|
||||
futures = "0.3"
|
||||
hex = "0.4.0"
|
||||
@@ -66,24 +65,22 @@ sha2 = "0.10"
|
||||
smallvec = "1"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
surf = { version = "2.3", default-features = false, features = ["h1-client"] }
|
||||
thiserror = "1"
|
||||
toml = "0.5"
|
||||
url = "2"
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
fast-socks5 = "0.8"
|
||||
fast-socks5 = "0.4"
|
||||
humansize = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
tagger = "4.3.3"
|
||||
textwrap = "0.15.0"
|
||||
async-channel = "1.6.1"
|
||||
futures-lite = "1.12.0"
|
||||
tokio-stream = { version = "0.1.9", features = ["fs"] }
|
||||
reqwest = { version = "0.11.11", features = ["json"] }
|
||||
async_zip = { git = "https://github.com/dignifiedquire/rs-async-zip", branch = "main", default-features = false, features = ["deflate"] }
|
||||
zip = { version = "0.6.2", default-features = false, features = ["deflate"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
criterion = { version = "0.3.4", features = ["async_tokio"] }
|
||||
async-std = { version = "1", features = ["unstable", "attributes"] }
|
||||
criterion = { version = "0.3.4", features = ["async_std"] }
|
||||
futures-lite = "1.12"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
@@ -94,6 +91,7 @@ tempfile = "3"
|
||||
members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-jsonrpc"
|
||||
]
|
||||
|
||||
[[example]]
|
||||
@@ -135,10 +133,5 @@ harness = false
|
||||
default = ["vendored"]
|
||||
internals = []
|
||||
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term", "dirs"]
|
||||
vendored = [
|
||||
"async-native-tls/vendored",
|
||||
"async-smtp/native-tls-vendored",
|
||||
"rusqlite/bundled-sqlcipher-vendored-openssl",
|
||||
"reqwest/native-tls-vendored"
|
||||
]
|
||||
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored", "rusqlite/bundled-sqlcipher-vendored-openssl"]
|
||||
nightly = ["pgp/nightly"]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use async_std::task::block_on;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
@@ -8,7 +9,9 @@ async fn address_book_benchmark(n: u32, read_count: u32) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new(&dbfile, id, Events::new()).await.unwrap();
|
||||
let context = Context::new(dbfile.into(), id, Events::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let book = (0..n)
|
||||
.map(|i| format!("Name {}\naddr{}@example.org\n", i, i))
|
||||
@@ -24,16 +27,12 @@ async fn address_book_benchmark(n: u32, read_count: u32) {
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("create 500 contacts", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { address_book_benchmark(black_box(500), black_box(0)).await })
|
||||
b.iter(|| block_on(async { address_book_benchmark(black_box(500), black_box(0)).await }))
|
||||
});
|
||||
|
||||
c.bench_function("create 100 contacts and read it 1000 times", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { address_book_benchmark(black_box(100), black_box(1000)).await })
|
||||
b.iter(|| block_on(async { address_book_benchmark(black_box(100), black_box(1000)).await }))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use async_std::path::PathBuf;
|
||||
use async_std::task::block_on;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::accounts::Accounts;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn create_accounts(n: u32) {
|
||||
let dir = tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new(p.clone()).await.unwrap();
|
||||
|
||||
@@ -17,8 +18,7 @@ async fn create_accounts(n: u32) {
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("create 1 account", |b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
b.to_async(&rt).iter(|| create_accounts(black_box(1)))
|
||||
b.iter(|| block_on(async { create_accounts(black_box(1)).await }))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::path::Path;
|
||||
use async_std::path::Path;
|
||||
|
||||
use criterion::async_executor::AsyncStdExecutor;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use deltachat::chat::{self, ChatId};
|
||||
@@ -9,7 +10,9 @@ use deltachat::Events;
|
||||
|
||||
async fn get_chat_msgs_benchmark(dbfile: &Path, chats: &[ChatId]) {
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile, id, Events::new()).await.unwrap();
|
||||
let context = Context::new(dbfile.into(), id, Events::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for c in chats.iter().take(10) {
|
||||
black_box(chat::get_chat_msgs(&context, *c, 0).await.ok());
|
||||
@@ -20,10 +23,8 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let chats: Vec<_> = rt.block_on(async {
|
||||
let context = Context::new(Path::new(&path), 100, Events::new())
|
||||
let chats: Vec<_> = async_std::task::block_on(async {
|
||||
let context = Context::new((&path).into(), 100, Events::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let chatlist = Chatlist::try_load(&context, 0, None, None).await.unwrap();
|
||||
@@ -32,7 +33,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
});
|
||||
|
||||
c.bench_function("chat::get_chat_msgs (load messages from 10 chats)", |b| {
|
||||
b.to_async(&rt)
|
||||
b.to_async(AsyncStdExecutor)
|
||||
.iter(|| get_chat_msgs_benchmark(black_box(path.as_ref()), black_box(&chats)))
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::async_executor::AsyncStdExecutor;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use deltachat::chatlist::Chatlist;
|
||||
@@ -14,14 +13,11 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(async {
|
||||
Context::new(Path::new(&path), 100, Events::new())
|
||||
.await
|
||||
.unwrap()
|
||||
let context = async_std::task::block_on(async {
|
||||
Context::new(path.into(), 100, Events::new()).await.unwrap()
|
||||
});
|
||||
c.bench_function("chatlist:try_load (Get Chatlist)", |b| {
|
||||
b.to_async(&rt)
|
||||
b.to_async(AsyncStdExecutor)
|
||||
.iter(|| get_chat_list_benchmark(black_box(&context)))
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use async_std::{path::PathBuf, task::block_on};
|
||||
use criterion::{
|
||||
async_executor::AsyncStdExecutor, black_box, criterion_group, criterion_main, BatchSize,
|
||||
Criterion,
|
||||
};
|
||||
use deltachat::{
|
||||
config::Config,
|
||||
context::Context,
|
||||
@@ -41,13 +43,15 @@ async fn create_context() -> Context {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new(&dbfile, id, Events::new()).await.unwrap();
|
||||
let context = Context::new(dbfile.into(), id, Events::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let backup: PathBuf = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("delta-chat-backup.tar");
|
||||
|
||||
if backup.exists() {
|
||||
.join("delta-chat-backup.tar")
|
||||
.into();
|
||||
if backup.exists().await {
|
||||
println!("Importing backup");
|
||||
imex(&context, ImexMode::ImportBackup, &backup, None)
|
||||
.await
|
||||
@@ -70,15 +74,11 @@ async fn create_context() -> Context {
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Receive messages");
|
||||
group.bench_function("Receive 100 simple text msgs", |b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(create_context());
|
||||
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
async move {
|
||||
recv_all_emails(black_box(ctx)).await;
|
||||
}
|
||||
});
|
||||
b.to_async(AsyncStdExecutor).iter_batched(
|
||||
|| block_on(create_context()),
|
||||
|context| recv_all_emails(black_box(context)),
|
||||
BatchSize::LargeInput,
|
||||
);
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use async_std::task::block_on;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::context::Context;
|
||||
use deltachat::Events;
|
||||
use std::path::Path;
|
||||
|
||||
async fn search_benchmark(dbfile: impl AsRef<Path>) {
|
||||
async fn search_benchmark(path: impl AsRef<Path>) {
|
||||
let dbfile = path.as_ref();
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile.as_ref(), id, Events::new())
|
||||
let context = Context::new(dbfile.into(), id, Events::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -18,10 +20,8 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("search hello", |b| {
|
||||
b.to_async(&rt).iter(|| search_benchmark(black_box(&path)))
|
||||
b.iter(|| block_on(async { search_benchmark(black_box(&path)).await }))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.87.0"
|
||||
version = "1.86.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
@@ -16,18 +16,19 @@ crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
deltachat = { path = "../", default-features = false }
|
||||
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", optional = true }
|
||||
libc = "0.2"
|
||||
human-panic = "1"
|
||||
num-traits = "0.2"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||
async-std = "1"
|
||||
anyhow = "1"
|
||||
thiserror = "1"
|
||||
rand = "0.7"
|
||||
once_cell = "1.12.0"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
nightly = ["deltachat/nightly"]
|
||||
jsonrpc = ["deltachat-jsonrpc"]
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ typedef struct _dc_provider dc_provider_t;
|
||||
typedef struct _dc_event dc_event_t;
|
||||
typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
typedef struct _dc_accounts_event_emitter dc_accounts_event_emitter_t;
|
||||
|
||||
typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t;
|
||||
|
||||
/**
|
||||
* @mainpage Getting started
|
||||
@@ -5178,6 +5178,55 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_jsonrpc_instance_t
|
||||
*
|
||||
* Opaque object for using the json rpc api from the cffi bindings.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create the jsonrpc instance that is used to call the jsonrpc.
|
||||
*
|
||||
* @memberof dc_accounts_t
|
||||
* @param account_manager The accounts object as created by dc_accounts_new().
|
||||
* @return Returns the jsonrpc instance, NULL on errors.
|
||||
* Must be freed using dc_jsonrpc_unref() after usage.
|
||||
*
|
||||
*/
|
||||
dc_jsonrpc_instance_t* dc_jsonrpc_init(dc_accounts_t* account_manager);
|
||||
|
||||
/**
|
||||
* Free a jsonrpc instance.
|
||||
*
|
||||
* @memberof dc_jsonrpc_instance_t
|
||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||
* If NULL is given, nothing is done and an error is logged.
|
||||
*/
|
||||
void dc_jsonrpc_unref(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||
|
||||
/**
|
||||
* Makes an asynchronous jsonrpc request,
|
||||
* returns immediately and once the result is ready it can be retrieved via dc_jsonrpc_next_response()
|
||||
* the jsonrpc specification defines an invocation id that can then be used to match request and response.
|
||||
*
|
||||
* @memberof dc_jsonrpc_instance_t
|
||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||
* @param request JSON-RPC request as string
|
||||
*/
|
||||
void dc_jsonrpc_request(dc_jsonrpc_instance_t* jsonrpc_instance, char* request);
|
||||
|
||||
/**
|
||||
* Get the next json_rpc response, blocks until there is a new event, so call this in a loop from a thread.
|
||||
*
|
||||
* @memberof dc_jsonrpc_instance_t
|
||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||
* @return JSON-RPC response as string
|
||||
* If NULL is returned, the accounts_t belonging to the jsonrpc instance is unref'd and no more events will come;
|
||||
* in this case, free the jsonrpc instance using dc_jsonrpc_unref().
|
||||
*/
|
||||
char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||
|
||||
/**
|
||||
* @class dc_event_emitter_t
|
||||
*
|
||||
|
||||
@@ -15,19 +15,18 @@ extern crate human_panic;
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Write;
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use std::ptr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use async_std::sync::RwLock;
|
||||
use async_std::task::{block_on, spawn};
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::Rng;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use deltachat::chat::{ChatId, ChatVisibility, MuteDuration, ProtectionStatus};
|
||||
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
|
||||
@@ -40,7 +39,6 @@ use deltachat::stock_str::StockMessage;
|
||||
use deltachat::webxdc::StatusUpdateSerial;
|
||||
use deltachat::*;
|
||||
use deltachat::{accounts::Accounts, log::LogExt};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
mod dc_array;
|
||||
mod lot;
|
||||
@@ -64,23 +62,6 @@ use deltachat::chatlist::Chatlist;
|
||||
/// Struct representing the deltachat context.
|
||||
pub type dc_context_t = Context;
|
||||
|
||||
static RT: Lazy<Runtime> = Lazy::new(|| Runtime::new().expect("unable to create tokio runtime"));
|
||||
|
||||
fn block_on<T>(fut: T) -> T::Output
|
||||
where
|
||||
T: Future,
|
||||
{
|
||||
RT.block_on(fut)
|
||||
}
|
||||
|
||||
fn spawn<T>(fut: T) -> JoinHandle<T::Output>
|
||||
where
|
||||
T: Future + Send + 'static,
|
||||
T::Output: Send + 'static,
|
||||
{
|
||||
RT.spawn(fut)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_context_new(
|
||||
_os_name: *const libc::c_char,
|
||||
@@ -97,7 +78,11 @@ pub unsafe extern "C" fn dc_context_new(
|
||||
let ctx = if blobdir.is_null() || *blobdir == 0 {
|
||||
// generate random ID as this functionality is not yet available on the C-api.
|
||||
let id = rand::thread_rng().gen();
|
||||
block_on(Context::new(as_path(dbfile), id, Events::new()))
|
||||
block_on(Context::new(
|
||||
as_path(dbfile).to_path_buf().into(),
|
||||
id,
|
||||
Events::new(),
|
||||
))
|
||||
} else {
|
||||
eprintln!("blobdir can not be defined explicitly anymore");
|
||||
return ptr::null_mut();
|
||||
@@ -121,7 +106,11 @@ pub unsafe extern "C" fn dc_context_new_closed(dbfile: *const libc::c_char) -> *
|
||||
}
|
||||
|
||||
let id = rand::thread_rng().gen();
|
||||
match block_on(Context::new_closed(as_path(dbfile), id, Events::new())) {
|
||||
match block_on(Context::new_closed(
|
||||
as_path(dbfile).to_path_buf().into(),
|
||||
id,
|
||||
Events::new(),
|
||||
)) {
|
||||
Ok(context) => Box::into_raw(Box::new(context)),
|
||||
Err(err) => {
|
||||
eprintln!("failed to create context: {:#}", err);
|
||||
@@ -693,13 +682,10 @@ pub unsafe extern "C" fn dc_get_next_event(events: *mut dc_event_emitter_t) -> *
|
||||
}
|
||||
let events = &*events;
|
||||
|
||||
block_on(async move {
|
||||
events
|
||||
.recv()
|
||||
.await
|
||||
.map(|ev| Box::into_raw(Box::new(ev)))
|
||||
.unwrap_or_else(ptr::null_mut)
|
||||
})
|
||||
events
|
||||
.recv_sync()
|
||||
.map(|ev| Box::into_raw(Box::new(ev)))
|
||||
.unwrap_or_else(ptr::null_mut)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2408,7 +2394,7 @@ pub unsafe extern "C" fn dc_get_last_error(context: *mut dc_context_t) -> *mut l
|
||||
return "".strdup();
|
||||
}
|
||||
let ctx = &*context;
|
||||
ctx.get_last_error().strdup()
|
||||
block_on(ctx.get_last_error()).strdup()
|
||||
}
|
||||
|
||||
// dc_array_t
|
||||
@@ -4108,11 +4094,11 @@ pub unsafe extern "C" fn dc_provider_unref(provider: *mut dc_provider_t) {
|
||||
/// Reader-writer lock wrapper for accounts manager to guarantee thread safety when using
|
||||
/// `dc_accounts_t` in multiple threads at once.
|
||||
pub struct AccountsWrapper {
|
||||
inner: RwLock<Accounts>,
|
||||
inner: Arc<RwLock<Accounts>>,
|
||||
}
|
||||
|
||||
impl Deref for AccountsWrapper {
|
||||
type Target = RwLock<Accounts>;
|
||||
type Target = Arc<RwLock<Accounts>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
@@ -4121,7 +4107,7 @@ impl Deref for AccountsWrapper {
|
||||
|
||||
impl AccountsWrapper {
|
||||
fn new(accounts: Accounts) -> Self {
|
||||
let inner = RwLock::new(accounts);
|
||||
let inner = Arc::new(RwLock::new(accounts));
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
@@ -4141,7 +4127,7 @@ pub unsafe extern "C" fn dc_accounts_new(
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let accs = block_on(Accounts::new(as_path(dbfile).into()));
|
||||
let accs = block_on(Accounts::new(as_path(dbfile).to_path_buf().into()));
|
||||
|
||||
match accs {
|
||||
Ok(accs) => Box::into_raw(Box::new(AccountsWrapper::new(accs))),
|
||||
@@ -4313,7 +4299,7 @@ pub unsafe extern "C" fn dc_accounts_migrate_account(
|
||||
block_on(async move {
|
||||
let mut accounts = accounts.write().await;
|
||||
match accounts
|
||||
.migrate_account(std::path::PathBuf::from(dbfile))
|
||||
.migrate_account(async_std::path::PathBuf::from(dbfile))
|
||||
.await
|
||||
{
|
||||
Ok(id) => id,
|
||||
@@ -4433,7 +4419,83 @@ pub unsafe extern "C" fn dc_accounts_get_next_event(
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let emitter = &mut *emitter;
|
||||
block_on(emitter.recv())
|
||||
|
||||
emitter
|
||||
.recv_sync()
|
||||
.map(|ev| Box::into_raw(Box::new(ev)))
|
||||
.unwrap_or_else(ptr::null_mut)
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonrpc")]
|
||||
mod jsonrpc {
|
||||
use super::*;
|
||||
use deltachat_jsonrpc::api::CommandApi;
|
||||
use deltachat_jsonrpc::yerpc::{MessageHandle, RpcHandle};
|
||||
|
||||
pub struct dc_jsonrpc_instance_t {
|
||||
receiver: async_std::channel::Receiver<deltachat_jsonrpc::yerpc::Message>,
|
||||
handle: MessageHandle<CommandApi>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_jsonrpc_init(
|
||||
account_manager: *mut dc_accounts_t,
|
||||
) -> *mut dc_jsonrpc_instance_t {
|
||||
if account_manager.is_null() {
|
||||
eprintln!("ignoring careless call to dc_jsonrpc_init()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let cmd_api =
|
||||
deltachat_jsonrpc::api::CommandApi::new_from_arc((*account_manager).inner.clone());
|
||||
|
||||
let (request_handle, receiver) = RpcHandle::new();
|
||||
let handle = MessageHandle::new(request_handle, cmd_api);
|
||||
|
||||
let instance = dc_jsonrpc_instance_t { receiver, handle };
|
||||
|
||||
Box::into_raw(Box::new(instance))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_jsonrpc_unref(jsonrpc_instance: *mut dc_jsonrpc_instance_t) {
|
||||
if jsonrpc_instance.is_null() {
|
||||
eprintln!("ignoring careless call to dc_jsonrpc_unref()");
|
||||
return;
|
||||
}
|
||||
|
||||
Box::from_raw(jsonrpc_instance);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_jsonrpc_request(
|
||||
jsonrpc_instance: *mut dc_jsonrpc_instance_t,
|
||||
request: *const libc::c_char,
|
||||
) {
|
||||
if jsonrpc_instance.is_null() || request.is_null() {
|
||||
eprintln!("ignoring careless call to dc_jsonrpc_request()");
|
||||
return;
|
||||
}
|
||||
|
||||
let api = &*jsonrpc_instance;
|
||||
let handle = &api.handle;
|
||||
let request = to_string_lossy(request);
|
||||
async_std::task::spawn(async move {
|
||||
handle.handle_message(&request).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_jsonrpc_next_response(
|
||||
jsonrpc_instance: *mut dc_jsonrpc_instance_t,
|
||||
) -> *mut libc::c_char {
|
||||
if jsonrpc_instance.is_null() {
|
||||
eprintln!("ignoring careless call to dc_jsonrpc_next_response()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let api = &*jsonrpc_instance;
|
||||
async_std::task::block_on(api.receiver.recv())
|
||||
.map(|result| serde_json::to_string(&result).unwrap_or_default().strdup())
|
||||
.unwrap_or(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
3
deltachat-jsonrpc/.gitignore
vendored
Normal file
3
deltachat-jsonrpc/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
accounts/
|
||||
|
||||
.cargo
|
||||
39
deltachat-jsonrpc/Cargo.toml
Normal file
39
deltachat-jsonrpc/Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.86.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2021"
|
||||
default-run = "webserver"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "webserver"
|
||||
path = "src/webserver.rs"
|
||||
required-features = ["webserver"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
async-std = { version = "1", features = ["attributes"] }
|
||||
deltachat = { path = ".." }
|
||||
num-traits = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.3.0"
|
||||
log = "0.4"
|
||||
async-channel = { version = "1.6.1" }
|
||||
futures = { version = "0.3.19" }
|
||||
serde_json = "1.0.75"
|
||||
yerpc = { git = "https://github.com/Frando/yerpc", features = ["anyhow"] }
|
||||
typescript-type-def = { git = "https://github.com/Frando/rust-typescript-type-def", branch = "yerpc", features = ["json_value"] }
|
||||
# optional, depended on features
|
||||
env_logger = { version = "0.9.0", optional = true }
|
||||
tide = { version = "0.16.0", optional = true }
|
||||
tide-websockets = { version = "0.4.0", optional = true }
|
||||
yerpc-tide = { git = "https://github.com/Frando/yerpc", optional = true }
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
webserver = ["env_logger", "tide", "tide-websockets", "yerpc-tide"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
75
deltachat-jsonrpc/README.MD
Normal file
75
deltachat-jsonrpc/README.MD
Normal file
@@ -0,0 +1,75 @@
|
||||
# deltachat-jsonrpc
|
||||
|
||||
## Build Requirements
|
||||
|
||||
- Linux or Mac, scrips make use of features like `>` pipes and `&&` (maybe the newer versions of powershell support them, but I didn't try that.)
|
||||
- rust (installed via rustup)
|
||||
|
||||
## Start the webserver
|
||||
|
||||
The webserver is an example usage. Goal of it is to be usable both as example and as base for deltachat-kaiOS.
|
||||
|
||||
```sh
|
||||
RUST_LOG=info cargo run --features webserver
|
||||
```
|
||||
|
||||
## Generate Typescript Bindings
|
||||
|
||||
```sh
|
||||
cd typescript
|
||||
npm i
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Run the development example
|
||||
|
||||
Mac
|
||||
|
||||
```sh
|
||||
alias firefox=/Applications/Firefox.app/Contents/MacOS/firefox
|
||||
npm run example:build && firefox --devtools $(pwd)/example/browser-example.html
|
||||
```
|
||||
|
||||
Linux:
|
||||
|
||||
```sh
|
||||
npm run example:run
|
||||
```
|
||||
|
||||
## Compiling server for kaiOS or android:
|
||||
|
||||
```sh
|
||||
cross build --features=webserver --target armv7-linux-androideabi --release
|
||||
```
|
||||
|
||||
## Run the tests
|
||||
|
||||
### Rust tests
|
||||
|
||||
```
|
||||
cargo test --features=webserver
|
||||
```
|
||||
|
||||
### Typescript
|
||||
|
||||
```
|
||||
cd typescript
|
||||
npm run test
|
||||
```
|
||||
|
||||
For the online tests to run you need a test account token for a mailadm instance,
|
||||
you can use docker to spin up a local instance: https://github.com/deltachat/docker-mailadm
|
||||
|
||||
> set the env var `DCC_NEW_TMP_EMAIL` to your mailadm token: example:
|
||||
> `DCC_NEW_TMP_EMAIL=https://testrun.org/new_email?t=1h_195dksa6544`
|
||||
|
||||
If your test fail with server shutdown at the start, then you might have a process from a last run still running probably and you need to kill that process manually to continue.
|
||||
|
||||
#### Test Coverage
|
||||
|
||||
You can test coverage with `npm run coverage`, but you need to have `DCC_NEW_TMP_EMAIL` set, otherwise the result will be useless because some functions can only be tested with the online tests.
|
||||
|
||||
> If you are offline and want to see the coverage results anyway (even though they are NOT correct), you can bypass the error with `COVERAGE_OFFLINE=1 npm run coverage`
|
||||
|
||||
Open `coverage/index.html` for a detailed report.
|
||||
`bindings.ts` is probably the most interesting file for coverage, because it describes the api functions.
|
||||
347
deltachat-jsonrpc/TODO.md
Normal file
347
deltachat-jsonrpc/TODO.md
Normal file
@@ -0,0 +1,347 @@
|
||||
## Core system
|
||||
|
||||
- [X] Base structure of JSON API code
|
||||
- [X] Implement the first methods for testing + the code that should later be generated by the proc macro
|
||||
- [X] Create the proc macro
|
||||
- [X] json api
|
||||
- [X] ts types
|
||||
- [X] arguments (no args, one argument, multiple args)
|
||||
- [X] return type
|
||||
- [X] custom types as type aliases that ts file looks prettier
|
||||
|
||||
|
||||
## Pre - MVP
|
||||
|
||||
- [X] Web socket server
|
||||
- [WIP] Web socket client (ts)
|
||||
- [X] backend connection state changed events
|
||||
- [X] Reconnect on connection loss / connection state
|
||||
- [ ] find a way to type the event emitter callback functions
|
||||
- [X] Events
|
||||
|
||||
## MVP
|
||||
|
||||
- [X] mocha integration test for ts api
|
||||
- [X] basic tests
|
||||
- [X] advanced / "online tests" (mailadm for burner accounts)
|
||||
- [ ] coverage for a majority of the API
|
||||
- [ ] Blobs served
|
||||
- [ ] Blob upload (for attachments, setting profile-picture, importing backup and so on)
|
||||
- [ ] Web push API? At least some kind of notification hook closure this lib can accept.
|
||||
|
||||
## Other Ideas
|
||||
|
||||
- [ ] make sure there can only be one connection at a time to the ws
|
||||
- why? , it could give problems if its commanded from multiple connections
|
||||
- [ ] encrypted connection?
|
||||
- [ ] authenticated connection?
|
||||
- [ ] Look into unit-testing for the proc macros?
|
||||
- [ ] proc macro taking over doc comments to generated typescript file
|
||||
- [X] GH action for tests (rust and typescript)
|
||||
- [X] rust test
|
||||
- [X] rust fmt
|
||||
- [X] rust clippy
|
||||
- [X] tsc check
|
||||
- [X] prettier
|
||||
- [X] mocha
|
||||
- [X] scripts to check&fix prettier formatting
|
||||
|
||||
|
||||
|
||||
## Apis
|
||||
|
||||
replicate desktop api feature set:
|
||||
|
||||
(this feature set is based on desktop version `1.20`, needs to be updated in the future)
|
||||
|
||||
```rs
|
||||
|
||||
|
||||
struct sendMessageParams {
|
||||
text: Option<String>,
|
||||
filename: Option<String>, // TODO we need to think about blobs some more
|
||||
location: Option<(u32,u32)>,
|
||||
quote_message_id: Option<u32>,
|
||||
}
|
||||
|
||||
struct QrCodeResponse = {
|
||||
state: u32 // also enum in reality, for simlicity u32 here
|
||||
id: u32
|
||||
text1: String
|
||||
}
|
||||
|
||||
impl Api {
|
||||
|
||||
// root ---------------------------------------------------------------
|
||||
|
||||
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
|
||||
async fn sc_set_profile_picture(&self, new_image: String) -> Result<()> {}
|
||||
|
||||
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
|
||||
// 'getProfilePicture' equals to `dc.getContact(C.DC_CONTACT_ID_SELF).getProfileImage()` or `dc.get_config("selfavatar")`
|
||||
|
||||
async fn sc_join_secure_join(&self, qrCode: String) -> Result<u32> {}
|
||||
async fn sc_stop_ongoing_process(&self) -> Result<u32> {}
|
||||
async fn sc_check_qr_code(&self, qrCode: String) -> Result<QrCodeResponse> {}
|
||||
|
||||
// login ----------------------------------------------------
|
||||
|
||||
// INFO: login functions need to call stop&start io where applicable
|
||||
|
||||
// login.newLogin:
|
||||
// do instead in frontend:
|
||||
// 1. call `add_account`
|
||||
// 2. call `select_account`
|
||||
// 3. set credentials via set config
|
||||
// 4. call `sc_configure`
|
||||
|
||||
// login.getLogins - is already implemented: `get_all_accounts`
|
||||
|
||||
// login.loadAccount - Basically `select_account`
|
||||
|
||||
// login.logout -> TODO: unselect account, which isn't implemented in the core yet
|
||||
|
||||
// login.forgetAccount -> `remove_account`
|
||||
|
||||
// login.getLastLoggedInAccount -> `get_selected_account_id`
|
||||
|
||||
// login.updateCredentials -> do instead: set config then call `sc_configure`
|
||||
|
||||
// backup -------------------------------------------------------------
|
||||
|
||||
// INFO: backup functions need to call stop&start io
|
||||
|
||||
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
|
||||
async fn sc_backup_export(&self, out_dir: String) -> Result<()> {}
|
||||
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
|
||||
async fn sc_backup_import(&self, file: String) -> Result<()> {} // will not return the same as in desktop because this function imports backup to the current context unlike it was in desktop
|
||||
|
||||
// chatList -----------------------------------------------------------
|
||||
|
||||
// chatList.selectChat - will be removed from desktop
|
||||
// chatList.getSelectedChatId - will be removed from desktop
|
||||
// chatList.onChatModified - will be removed from desktop
|
||||
|
||||
async fn sc_chatlist_get_general_fresh_message_counter(&self) -> Result<u32> // this method might be used for a favicon badge counter
|
||||
|
||||
// contacts ------------------------------------------------------------
|
||||
|
||||
async fn sc_contacts_change_nickname(&self, contact_id: u32, new_name: String) -> Result<()>
|
||||
|
||||
|
||||
// contacts.getChatIdByContactId - very similar to sc_contacts_create_chat_by_contact_id
|
||||
// contacts.getDMChatId - very similar to sc_contacts_create_chat_by_contact_id
|
||||
|
||||
async fn sc_contacts_get_encryption_info(&self, contact_id: u32) -> Result<String>
|
||||
|
||||
async fn sc_contacts_lookup_contact_id_by_addr(&self, email: String) -> Result<u32>
|
||||
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
```ts
|
||||
|
||||
class DeltaRemote {
|
||||
// chat ---------------------------------------------------------------
|
||||
call(
|
||||
fnName: 'chat.getChatMedia',
|
||||
chatId: number,
|
||||
msgType1: number,
|
||||
msgType2: number
|
||||
): Promise<MessageType[]>
|
||||
call(fnName: 'chat.getEncryptionInfo', chatId: number): Promise<string>
|
||||
call(fnName: 'chat.getQrCode', chatId?: number): Promise<string>
|
||||
call(fnName: 'chat.leaveGroup', chatId: number): Promise<void>
|
||||
call(fnName: 'chat.setName', chatId: number, name: string): Promise<boolean>
|
||||
call(
|
||||
fnName: 'chat.modifyGroup',
|
||||
chatId: number,
|
||||
name: string,
|
||||
image: string,
|
||||
remove: number[],
|
||||
add: number[]
|
||||
): Promise<boolean>
|
||||
call(
|
||||
fnName: 'chat.addContactToChat',
|
||||
chatId: number,
|
||||
contactId: number
|
||||
): Promise<boolean>
|
||||
call(
|
||||
fnName: 'chat.setProfileImage',
|
||||
chatId: number,
|
||||
newImage: string
|
||||
): Promise<boolean>
|
||||
call(
|
||||
fnName: 'chat.setMuteDuration',
|
||||
chatId: number,
|
||||
duration: MuteDuration
|
||||
): Promise<boolean>
|
||||
call(
|
||||
fnName: 'chat.createGroupChat',
|
||||
verified: boolean,
|
||||
name: string
|
||||
): Promise<number>
|
||||
call(fnName: 'chat.delete', chatId: number): Promise<void>
|
||||
call(
|
||||
fnName: 'chat.setVisibility',
|
||||
chatId: number,
|
||||
visibility:
|
||||
| C.DC_CERTCK_AUTO
|
||||
| C.DC_CERTCK_STRICT
|
||||
| C.DC_CHAT_VISIBILITY_PINNED
|
||||
): Promise<void>
|
||||
call(fnName: 'chat.getChatContacts', chatId: number): Promise<number[]>
|
||||
call(fnName: 'chat.markNoticedChat', chatId: number): Promise<void>
|
||||
call(fnName: 'chat.getChatEphemeralTimer', chatId: number): Promise<number>
|
||||
call(
|
||||
fnName: 'chat.setChatEphemeralTimer',
|
||||
chatId: number,
|
||||
ephemeralTimer: number
|
||||
): Promise<void>
|
||||
call(fnName: 'chat.sendVideoChatInvitation', chatId: number): Promise<number>
|
||||
call(
|
||||
fnName: 'chat.decideOnContactRequest',
|
||||
messageId: number,
|
||||
decision:
|
||||
| C.DC_DECISION_START_CHAT
|
||||
| C.DC_DECISION_NOT_NOW
|
||||
| C.DC_DECISION_BLOCK
|
||||
): Promise<number>
|
||||
// locations ----------------------------------------------------------
|
||||
call(
|
||||
fnName: 'locations.setLocation',
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
accuracy: number
|
||||
): Promise<void>
|
||||
call(
|
||||
fnName: 'locations.getLocations',
|
||||
chatId: number,
|
||||
contactId: number,
|
||||
timestampFrom: number,
|
||||
timestampTo: number
|
||||
): Promise<JsonLocations>
|
||||
|
||||
// NOTHING HERE that is called directly from the frontend, yet
|
||||
// messageList --------------------------------------------------------
|
||||
call(
|
||||
fnName: 'messageList.sendMessage',
|
||||
chatId: number,
|
||||
params: sendMessageParams
|
||||
): Promise<[number, MessageType | null]>
|
||||
call(
|
||||
fnName: 'messageList.sendSticker',
|
||||
chatId: number,
|
||||
stickerPath: string
|
||||
): Promise<void>
|
||||
call(fnName: 'messageList.deleteMessage', id: number): Promise<void>
|
||||
call(fnName: 'messageList.getMessageInfo', msgId: number): Promise<string>
|
||||
call(
|
||||
fnName: 'messageList.getDraft',
|
||||
chatId: number
|
||||
): Promise<MessageType | null>
|
||||
call(
|
||||
fnName: 'messageList.setDraft',
|
||||
chatId: number,
|
||||
{
|
||||
text,
|
||||
file,
|
||||
quotedMessageId,
|
||||
}: { text?: string; file?: string; quotedMessageId?: number }
|
||||
): Promise<void>
|
||||
call(
|
||||
fnName: 'messageList.messageIdToJson',
|
||||
id: number
|
||||
): Promise<{ msg: null } | MessageType>
|
||||
call(
|
||||
fnName: 'messageList.forwardMessage',
|
||||
msgId: number,
|
||||
chatId: number
|
||||
): Promise<void>
|
||||
call(
|
||||
fnName: 'messageList.searchMessages',
|
||||
query: string,
|
||||
chatId?: number
|
||||
): Promise<number[]>
|
||||
call(
|
||||
fnName: 'messageList.msgIds2SearchResultItems',
|
||||
msgIds: number[]
|
||||
): Promise<{ [id: number]: MessageSearchResult }>
|
||||
call(
|
||||
fnName: 'messageList.saveMessageHTML2Disk',
|
||||
messageId: number
|
||||
): Promise<string>
|
||||
// settings -----------------------------------------------------------
|
||||
call(fnName: 'settings.keysImport', directory: string): Promise<void>
|
||||
call(fnName: 'settings.keysExport', directory: string): Promise<void>
|
||||
call(
|
||||
fnName: 'settings.serverFlags',
|
||||
{
|
||||
mail_security,
|
||||
send_security,
|
||||
}: {
|
||||
mail_security?: string
|
||||
send_security?: string
|
||||
}
|
||||
): Promise<number | ''>
|
||||
call(
|
||||
fnName: 'settings.setDesktopSetting',
|
||||
key: keyof DesktopSettings,
|
||||
value: string | number | boolean
|
||||
): Promise<boolean>
|
||||
call(fnName: 'settings.getDesktopSettings'): Promise<DesktopSettings>
|
||||
call(
|
||||
fnName: 'settings.saveBackgroundImage',
|
||||
file: string,
|
||||
isDefaultPicture: boolean
|
||||
): Promise<string>
|
||||
call(
|
||||
fnName: 'settings.estimateAutodeleteCount',
|
||||
fromServer: boolean,
|
||||
seconds: number
|
||||
): Promise<number>
|
||||
// stickers -----------------------------------------------------------
|
||||
call(
|
||||
fnName: 'stickers.getStickers'
|
||||
): Promise<{
|
||||
[key: string]: string[]
|
||||
}> // todo move to extras? because its not directly elated to core
|
||||
// context ------------------------------------------------------------
|
||||
call(fnName: 'context.maybeNetwork'): Promise<void>
|
||||
// burner accounts ------------------------------------------------------------
|
||||
call(
|
||||
fnName: 'burnerAccounts.create',
|
||||
url: string
|
||||
): Promise<{ email: string; password: string }> // think about how to improve that api - probably use core api instead
|
||||
// extras -------------------------------------------------------------
|
||||
call(fnName: 'extras.getLocaleData', locale: string): Promise<LocaleData>
|
||||
call(fnName: 'extras.setLocale', locale: string): Promise<void>
|
||||
call(
|
||||
fnName: 'extras.getActiveTheme'
|
||||
): Promise<{
|
||||
theme: Theme
|
||||
data: string
|
||||
} | null>
|
||||
call(fnName: 'extras.setThemeFilePath', address: string): void
|
||||
call(fnName: 'extras.getAvailableThemes'): Promise<Theme[]>
|
||||
call(fnName: 'extras.setTheme', address: string): Promise<boolean>
|
||||
// catchall: ----------------------------------------------------------
|
||||
call(fnName: string): Promise<any>
|
||||
call(fnName: string, ...args: any[]): Promise<any> {
|
||||
return _callDcMethodAsync(fnName, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
export const DeltaBackend = new DeltaRemote()
|
||||
```
|
||||
|
||||
|
||||
after that, or while doing it adjust api to be more complete
|
||||
|
||||
|
||||
|
||||
|
||||
TODO different test to simulate two devices:
|
||||
|
||||
to test autocrypt_initiate_key_transfer & autocrypt_continue_key_transfer
|
||||
154
deltachat-jsonrpc/src/api/events.rs
Normal file
154
deltachat-jsonrpc/src/api/events.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use deltachat::{Event, EventType};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
pub fn event_to_json_rpc_notification(event: Event) -> Value {
|
||||
let (field1, field2): (Value, Value) = match &event.typ {
|
||||
// events with a single string in field1
|
||||
EventType::Info(txt)
|
||||
| EventType::SmtpConnected(txt)
|
||||
| EventType::ImapConnected(txt)
|
||||
| EventType::SmtpMessageSent(txt)
|
||||
| EventType::ImapMessageDeleted(txt)
|
||||
| EventType::ImapMessageMoved(txt)
|
||||
| EventType::NewBlobFile(txt)
|
||||
| EventType::DeletedBlobFile(txt)
|
||||
| EventType::Warning(txt)
|
||||
| EventType::Error(txt)
|
||||
| EventType::ErrorSelfNotInGroup(txt) => (json!(txt), Value::Null),
|
||||
EventType::ImexFileWritten(path) => (json!(path.to_str()), Value::Null),
|
||||
// single number
|
||||
EventType::MsgsNoticed(chat_id) | EventType::ChatModified(chat_id) => {
|
||||
(json!(chat_id), Value::Null)
|
||||
}
|
||||
EventType::ImexProgress(progress) => (json!(progress), Value::Null),
|
||||
// both fields contain numbers
|
||||
EventType::MsgsChanged { chat_id, msg_id }
|
||||
| EventType::IncomingMsg { chat_id, msg_id }
|
||||
| EventType::MsgDelivered { chat_id, msg_id }
|
||||
| EventType::MsgFailed { chat_id, msg_id }
|
||||
| EventType::MsgRead { chat_id, msg_id } => (json!(chat_id), json!(msg_id)),
|
||||
EventType::ChatEphemeralTimerModified { chat_id, timer } => (json!(chat_id), json!(timer)),
|
||||
EventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
}
|
||||
| EventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => (json!(contact_id), json!(progress)),
|
||||
// field 1 number or null
|
||||
EventType::ContactsChanged(maybe_number) | EventType::LocationChanged(maybe_number) => (
|
||||
match maybe_number {
|
||||
Some(number) => json!(number),
|
||||
None => Value::Null,
|
||||
},
|
||||
Value::Null,
|
||||
),
|
||||
// number and maybe string
|
||||
EventType::ConfigureProgress { progress, comment } => (
|
||||
json!(progress),
|
||||
match comment {
|
||||
Some(content) => json!(content),
|
||||
None => Value::Null,
|
||||
},
|
||||
),
|
||||
EventType::ConnectivityChanged => (Value::Null, Value::Null),
|
||||
EventType::SelfavatarChanged => (Value::Null, Value::Null),
|
||||
EventType::WebxdcStatusUpdate {
|
||||
msg_id,
|
||||
status_update_serial,
|
||||
} => (json!(msg_id), json!(status_update_serial)),
|
||||
};
|
||||
|
||||
json!({
|
||||
"id": event_type_to_string(event.typ),
|
||||
"contextId": event.id,
|
||||
"field1": field1,
|
||||
"field2": field2
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
pub enum EventTypeName {
|
||||
Info,
|
||||
SmtpConnected,
|
||||
ImapConnected,
|
||||
SmtpMessageSent,
|
||||
ImapMessageDeleted,
|
||||
ImapMessageMoved,
|
||||
NewBlobFile,
|
||||
DeletedBlobFile,
|
||||
Warning,
|
||||
Error,
|
||||
ErrorSelfNotInGroup,
|
||||
MsgsChanged,
|
||||
IncomingMsg,
|
||||
MsgsNoticed,
|
||||
MsgDelivered,
|
||||
MsgFailed,
|
||||
MsgRead,
|
||||
ChatModified,
|
||||
ChatEphemeralTimerModified,
|
||||
ContactsChanged,
|
||||
LocationChanged,
|
||||
ConfigureProgress,
|
||||
ImexProgress,
|
||||
ImexFileWritten,
|
||||
SecurejoinInviterProgress,
|
||||
SecurejoinJoinerProgress,
|
||||
ConnectivityChanged,
|
||||
SelfavatarChanged,
|
||||
WebxdcStatusUpdate,
|
||||
}
|
||||
|
||||
fn event_type_to_string(event: EventType) -> EventTypeName {
|
||||
use EventTypeName::*;
|
||||
match event {
|
||||
EventType::Info(_) => Info,
|
||||
EventType::SmtpConnected(_) => SmtpConnected,
|
||||
EventType::ImapConnected(_) => ImapConnected,
|
||||
EventType::SmtpMessageSent(_) => SmtpMessageSent,
|
||||
EventType::ImapMessageDeleted(_) => ImapMessageDeleted,
|
||||
EventType::ImapMessageMoved(_) => ImapMessageMoved,
|
||||
EventType::NewBlobFile(_) => NewBlobFile,
|
||||
EventType::DeletedBlobFile(_) => DeletedBlobFile,
|
||||
EventType::Warning(_) => Warning,
|
||||
EventType::Error(_) => Error,
|
||||
EventType::ErrorSelfNotInGroup(_) => ErrorSelfNotInGroup,
|
||||
EventType::MsgsChanged { .. } => MsgsChanged,
|
||||
EventType::IncomingMsg { .. } => IncomingMsg,
|
||||
EventType::MsgsNoticed(_) => MsgsNoticed,
|
||||
EventType::MsgDelivered { .. } => MsgDelivered,
|
||||
EventType::MsgFailed { .. } => MsgFailed,
|
||||
EventType::MsgRead { .. } => MsgRead,
|
||||
EventType::ChatModified(_) => ChatModified,
|
||||
EventType::ChatEphemeralTimerModified { .. } => ChatEphemeralTimerModified,
|
||||
EventType::ContactsChanged(_) => ContactsChanged,
|
||||
EventType::LocationChanged(_) => LocationChanged,
|
||||
EventType::ConfigureProgress { .. } => ConfigureProgress,
|
||||
EventType::ImexProgress(_) => ImexProgress,
|
||||
EventType::ImexFileWritten(_) => ImexFileWritten,
|
||||
EventType::SecurejoinInviterProgress { .. } => SecurejoinInviterProgress,
|
||||
EventType::SecurejoinJoinerProgress { .. } => SecurejoinJoinerProgress,
|
||||
EventType::ConnectivityChanged => ConnectivityChanged,
|
||||
EventType::SelfavatarChanged => SelfavatarChanged,
|
||||
EventType::WebxdcStatusUpdate { .. } => WebxdcStatusUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn generate_events_ts_types_definition() {
|
||||
let events = {
|
||||
let mut buf = Vec::new();
|
||||
let options = typescript_type_def::DefinitionFileOptions {
|
||||
root_namespace: None,
|
||||
..typescript_type_def::DefinitionFileOptions::default()
|
||||
};
|
||||
typescript_type_def::write_definition_file::<_, EventTypeName>(&mut buf, options).unwrap();
|
||||
String::from_utf8(buf).unwrap()
|
||||
};
|
||||
std::fs::write("typescript/generated/events.ts", events).unwrap();
|
||||
}
|
||||
536
deltachat-jsonrpc/src/api/mod.rs
Normal file
536
deltachat-jsonrpc/src/api/mod.rs
Normal file
@@ -0,0 +1,536 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_std::sync::{Arc, RwLock};
|
||||
use deltachat::{
|
||||
chat::{get_chat_msgs, ChatId},
|
||||
chatlist::Chatlist,
|
||||
config::Config,
|
||||
contact::{may_be_valid_addr, Contact, ContactId},
|
||||
context::get_info,
|
||||
message::{Message, MsgId, Viewtype},
|
||||
provider::get_provider_info,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use yerpc::rpc;
|
||||
|
||||
pub use deltachat::accounts::Accounts;
|
||||
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
||||
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
|
||||
|
||||
use types::account::Account;
|
||||
use types::chat::FullChat;
|
||||
use types::chat_list::ChatListEntry;
|
||||
use types::contact::ContactObject;
|
||||
use types::message::MessageObject;
|
||||
use types::provider_info::ProviderInfo;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandApi {
|
||||
pub(crate) accounts: Arc<RwLock<Accounts>>,
|
||||
}
|
||||
|
||||
impl CommandApi {
|
||||
pub fn new(accounts: Accounts) -> Self {
|
||||
CommandApi {
|
||||
accounts: Arc::new(RwLock::new(accounts)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_arc(accounts: Arc<RwLock<Accounts>>) -> Self {
|
||||
CommandApi { accounts }
|
||||
}
|
||||
|
||||
async fn get_context(&self, id: u32) -> Result<deltachat::context::Context> {
|
||||
let sc = self
|
||||
.accounts
|
||||
.read()
|
||||
.await
|
||||
.get_account(id)
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("account with id {} not found", id))?;
|
||||
Ok(sc)
|
||||
}
|
||||
}
|
||||
|
||||
#[rpc(all_positional, ts_outdir = "typescript/generated")]
|
||||
impl CommandApi {
|
||||
// ---------------------------------------------
|
||||
// Misc top level functions
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Check if an email address is valid.
|
||||
async fn check_email_validity(&self, email: String) -> bool {
|
||||
may_be_valid_addr(&email)
|
||||
}
|
||||
|
||||
/// Get general system info.
|
||||
async fn get_system_info(&self) -> BTreeMap<&'static str, String> {
|
||||
get_info()
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// Account Management
|
||||
// ---------------------------------------------
|
||||
|
||||
async fn add_account(&self) -> Result<u32> {
|
||||
self.accounts.write().await.add_account().await
|
||||
}
|
||||
|
||||
async fn remove_account(&self, account_id: u32) -> Result<()> {
|
||||
self.accounts.write().await.remove_account(account_id).await
|
||||
}
|
||||
|
||||
async fn get_all_account_ids(&self) -> Vec<u32> {
|
||||
self.accounts.read().await.get_all().await
|
||||
}
|
||||
|
||||
/// Select account id for internally selected state.
|
||||
/// TODO: Likely this is deprecated as all methods take an account id now.
|
||||
async fn select_account(&self, id: u32) -> Result<()> {
|
||||
self.accounts.write().await.select_account(id).await
|
||||
}
|
||||
|
||||
/// Get the selected account id of the internal state..
|
||||
/// TODO: Likely this is deprecated as all methods take an account id now.
|
||||
async fn get_selected_account_id(&self) -> Option<u32> {
|
||||
self.accounts.read().await.get_selected_account_id().await
|
||||
}
|
||||
|
||||
/// Get a list of all configured accounts.
|
||||
async fn get_all_accounts(&self) -> Result<Vec<Account>> {
|
||||
let mut accounts = Vec::new();
|
||||
for id in self.accounts.read().await.get_all().await {
|
||||
let context_option = self.accounts.read().await.get_account(id).await;
|
||||
if let Some(ctx) = context_option {
|
||||
accounts.push(Account::from_context(&ctx, id).await?)
|
||||
} else {
|
||||
println!("account with id {} doesn't exist anymore", id);
|
||||
}
|
||||
}
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// Methods that work on individual accounts
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Get top-level info for an account.
|
||||
async fn get_account_info(&self, account_id: u32) -> Result<Account> {
|
||||
let context_option = self.accounts.read().await.get_account(account_id).await;
|
||||
if let Some(ctx) = context_option {
|
||||
Ok(Account::from_context(&ctx, account_id).await?)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"account with id {} doesn't exist anymore",
|
||||
account_id
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns provider for the given domain.
|
||||
///
|
||||
/// This function looks up domain in offline database.
|
||||
///
|
||||
/// For compatibility, email address can be passed to this function
|
||||
/// instead of the domain.
|
||||
async fn get_provider_info(
|
||||
&self,
|
||||
account_id: u32,
|
||||
email: String,
|
||||
) -> Result<Option<ProviderInfo>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
let socks5_enabled = ctx
|
||||
.get_config_bool(deltachat::config::Config::Socks5Enabled)
|
||||
.await?;
|
||||
|
||||
let provider_info =
|
||||
get_provider_info(&ctx, email.split('@').last().unwrap_or(""), socks5_enabled).await;
|
||||
Ok(ProviderInfo::from_dc_type(provider_info))
|
||||
}
|
||||
|
||||
/// Checks if the context is already configured.
|
||||
async fn is_configured(&self, account_id: u32) -> Result<bool> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.is_configured().await
|
||||
}
|
||||
|
||||
/// Get system info for an account.
|
||||
async fn get_info(&self, account_id: u32) -> Result<BTreeMap<&'static str, String>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.get_info().await
|
||||
}
|
||||
|
||||
async fn set_config(&self, account_id: u32, key: String, value: Option<String>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
set_config(&ctx, &key, value.as_deref()).await
|
||||
}
|
||||
|
||||
async fn batch_set_config(
|
||||
&self,
|
||||
account_id: u32,
|
||||
config: HashMap<String, Option<String>>,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
for (key, value) in config.into_iter() {
|
||||
set_config(&ctx, &key, value.as_deref())
|
||||
.await
|
||||
.with_context(|| format!("Can't set {} to {:?}", key, value))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_config(&self, account_id: u32, key: String) -> Result<Option<String>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
get_config(&ctx, &key).await
|
||||
}
|
||||
|
||||
async fn batch_get_config(
|
||||
&self,
|
||||
account_id: u32,
|
||||
keys: Vec<String>,
|
||||
) -> Result<HashMap<String, Option<String>>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut result: HashMap<String, Option<String>> = HashMap::new();
|
||||
for key in keys {
|
||||
result.insert(key.clone(), get_config(&ctx, &key).await?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Configures this account with the currently set parameters.
|
||||
/// Setup the credential config before calling this.
|
||||
async fn configure(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.stop_io().await;
|
||||
ctx.configure().await?;
|
||||
ctx.start_io().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Signal an ongoing process to stop.
|
||||
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.stop_ongoing().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// autocrypt
|
||||
// ---------------------------------------------
|
||||
|
||||
async fn autocrypt_initiate_key_transfer(&self, account_id: u32) -> Result<String> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
deltachat::imex::initiate_key_transfer(&ctx).await
|
||||
}
|
||||
|
||||
async fn autocrypt_continue_key_transfer(
|
||||
&self,
|
||||
account_id: u32,
|
||||
message_id: u32,
|
||||
setup_code: String,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
deltachat::imex::continue_key_transfer(&ctx, MsgId::new(message_id), &setup_code).await
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// chat list
|
||||
// ---------------------------------------------
|
||||
|
||||
async fn get_chatlist_entries(
|
||||
&self,
|
||||
account_id: u32,
|
||||
list_flags: Option<u32>,
|
||||
query_string: Option<String>,
|
||||
query_contact_id: Option<u32>,
|
||||
) -> Result<Vec<ChatListEntry>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let list = Chatlist::try_load(
|
||||
&ctx,
|
||||
list_flags.unwrap_or(0) as usize,
|
||||
query_string.as_deref(),
|
||||
query_contact_id.map(ContactId::new),
|
||||
)
|
||||
.await?;
|
||||
let mut l: Vec<ChatListEntry> = Vec::new();
|
||||
for i in 0..list.len() {
|
||||
l.push(ChatListEntry(
|
||||
list.get_chat_id(i)?.to_u32(),
|
||||
list.get_msg_id(i)?.unwrap_or_default().to_u32(),
|
||||
));
|
||||
}
|
||||
Ok(l)
|
||||
}
|
||||
|
||||
async fn get_chatlist_items_by_entries(
|
||||
&self,
|
||||
account_id: u32,
|
||||
entries: Vec<ChatListEntry>,
|
||||
) -> Result<HashMap<u32, ChatListItemFetchResult>> {
|
||||
// todo custom json deserializer for ChatListEntry?
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut result: HashMap<u32, ChatListItemFetchResult> = HashMap::new();
|
||||
for (_i, entry) in entries.iter().enumerate() {
|
||||
result.insert(
|
||||
entry.0,
|
||||
match get_chat_list_item_by_id(&ctx, entry).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => ChatListItemFetchResult::Error {
|
||||
id: entry.0,
|
||||
error: format!("{:?}", err),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// chat
|
||||
// ---------------------------------------------
|
||||
|
||||
async fn chatlist_get_full_chat_by_id(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
) -> Result<FullChat> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
FullChat::from_dc_chat_id(&ctx, chat_id).await
|
||||
}
|
||||
|
||||
async fn accept_chat(&self, account_id: u32, chat_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ChatId::new(chat_id).accept(&ctx).await
|
||||
}
|
||||
|
||||
async fn block_chat(&self, account_id: u32, chat_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ChatId::new(chat_id).block(&ctx).await
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// message list
|
||||
// ---------------------------------------------
|
||||
|
||||
async fn message_list_get_message_ids(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
flags: u32,
|
||||
) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msg = get_chat_msgs(&ctx, ChatId::new(chat_id), flags).await?;
|
||||
Ok(msg
|
||||
.iter()
|
||||
.filter_map(|chat_item| match chat_item {
|
||||
deltachat::chat::ChatItem::Message { msg_id } => Some(msg_id.to_u32()),
|
||||
_ => None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn message_get_message(&self, account_id: u32, message_id: u32) -> Result<MessageObject> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
MessageObject::from_message_id(&ctx, message_id).await
|
||||
}
|
||||
|
||||
async fn message_get_messages(
|
||||
&self,
|
||||
account_id: u32,
|
||||
message_ids: Vec<u32>,
|
||||
) -> Result<HashMap<u32, MessageObject>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut messages: HashMap<u32, MessageObject> = HashMap::new();
|
||||
for message_id in message_ids {
|
||||
messages.insert(
|
||||
message_id,
|
||||
MessageObject::from_message_id(&ctx, message_id).await?,
|
||||
);
|
||||
}
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// contact
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Get a single contact options by ID.
|
||||
async fn contacts_get_contact(
|
||||
&self,
|
||||
account_id: u32,
|
||||
contact_id: u32,
|
||||
) -> Result<ContactObject> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let contact_id = ContactId::new(contact_id);
|
||||
|
||||
ContactObject::from_dc_contact(
|
||||
&ctx,
|
||||
deltachat::contact::Contact::get_by_id(&ctx, contact_id).await?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Add a single contact as a result of an explicit user action.
|
||||
///
|
||||
/// Returns contact id of the created or existing contact
|
||||
async fn contacts_create_contact(
|
||||
&self,
|
||||
account_id: u32,
|
||||
email: String,
|
||||
name: Option<String>,
|
||||
) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
if !may_be_valid_addr(&email) {
|
||||
bail!(anyhow!(
|
||||
"provided email address is not a valid email address"
|
||||
))
|
||||
}
|
||||
let contact_id = Contact::create(&ctx, &name.unwrap_or_default(), &email).await?;
|
||||
Ok(contact_id.to_u32())
|
||||
}
|
||||
|
||||
/// Returns contact id of the created or existing DM chat with that contact
|
||||
async fn contacts_create_chat_by_contact_id(
|
||||
&self,
|
||||
account_id: u32,
|
||||
contact_id: u32,
|
||||
) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let contact = Contact::get_by_id(&ctx, ContactId::new(contact_id)).await?;
|
||||
ChatId::create_for_contact(&ctx, contact.id)
|
||||
.await
|
||||
.map(|id| id.to_u32())
|
||||
}
|
||||
|
||||
async fn contacts_block(&self, account_id: u32, contact_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
Contact::block(&ctx, ContactId::new(contact_id)).await
|
||||
}
|
||||
|
||||
async fn contacts_unblock(&self, account_id: u32, contact_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
Contact::unblock(&ctx, ContactId::new(contact_id)).await
|
||||
}
|
||||
|
||||
async fn contacts_get_blocked(&self, account_id: u32) -> Result<Vec<ContactObject>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let blocked_ids = Contact::get_all_blocked(&ctx).await?;
|
||||
let mut contacts: Vec<ContactObject> = Vec::with_capacity(blocked_ids.len());
|
||||
for id in blocked_ids {
|
||||
contacts.push(
|
||||
ContactObject::from_dc_contact(
|
||||
&ctx,
|
||||
deltachat::contact::Contact::get_by_id(&ctx, id).await?,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
Ok(contacts)
|
||||
}
|
||||
|
||||
async fn contacts_get_contact_ids(
|
||||
&self,
|
||||
account_id: u32,
|
||||
list_flags: u32,
|
||||
query: Option<String>,
|
||||
) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let contacts = Contact::get_all(&ctx, list_flags, query.as_deref()).await?;
|
||||
Ok(contacts.into_iter().map(|c| c.to_u32()).collect())
|
||||
}
|
||||
|
||||
/// Get a list of contacts.
|
||||
/// (formerly called getContacts2 in desktop)
|
||||
async fn contacts_get_contacts(
|
||||
&self,
|
||||
account_id: u32,
|
||||
list_flags: u32,
|
||||
query: Option<String>,
|
||||
) -> Result<Vec<ContactObject>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let contact_ids = Contact::get_all(&ctx, list_flags, query.as_deref()).await?;
|
||||
let mut contacts: Vec<ContactObject> = Vec::with_capacity(contact_ids.len());
|
||||
for id in contact_ids {
|
||||
contacts.push(
|
||||
ContactObject::from_dc_contact(
|
||||
&ctx,
|
||||
deltachat::contact::Contact::get_by_id(&ctx, id).await?,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
Ok(contacts)
|
||||
}
|
||||
|
||||
async fn contacts_get_contacts_by_ids(
|
||||
&self,
|
||||
account_id: u32,
|
||||
ids: Vec<u32>,
|
||||
) -> Result<HashMap<u32, ContactObject>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
let mut contacts = HashMap::with_capacity(ids.len());
|
||||
for id in ids {
|
||||
contacts.insert(
|
||||
id,
|
||||
ContactObject::from_dc_contact(
|
||||
&ctx,
|
||||
deltachat::contact::Contact::get_by_id(&ctx, ContactId::new(id)).await?,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
Ok(contacts)
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// misc prototyping functions
|
||||
// that might get removed later again
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Returns the messageid of the sent message
|
||||
async fn misc_send_text_message(
|
||||
&self,
|
||||
account_id: u32,
|
||||
text: String,
|
||||
chat_id: u32,
|
||||
) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some(text));
|
||||
|
||||
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
|
||||
Ok(message_id.to_u32())
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions (to prevent code duplication)
|
||||
async fn set_config(
|
||||
ctx: &deltachat::context::Context,
|
||||
key: &str,
|
||||
value: Option<&str>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if key.starts_with("ui.") {
|
||||
ctx.set_ui_config(key, value).await
|
||||
} else {
|
||||
ctx.set_config(Config::from_str(key).context("unknown key")?, value)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_config(
|
||||
ctx: &deltachat::context::Context,
|
||||
key: &str,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
if key.starts_with("ui.") {
|
||||
ctx.get_ui_config(key).await
|
||||
} else {
|
||||
ctx.get_config(Config::from_str(key).context("unknown key")?)
|
||||
.await
|
||||
}
|
||||
}
|
||||
46
deltachat-jsonrpc/src/api/types/account.rs
Normal file
46
deltachat-jsonrpc/src/api/types/account.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::config::Config;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Account {
|
||||
//#[serde(rename_all = "camelCase")]
|
||||
Configured {
|
||||
id: u32,
|
||||
display_name: Option<String>,
|
||||
addr: Option<String>,
|
||||
// size: u32,
|
||||
profile_image: Option<String>, // TODO: This needs to be converted to work with blob http server.
|
||||
color: String,
|
||||
},
|
||||
Unconfigured {
|
||||
id: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub async fn from_context(ctx: &deltachat::context::Context, id: u32) -> Result<Self> {
|
||||
if ctx.is_configured().await? {
|
||||
let display_name = ctx.get_config(Config::Displayname).await?;
|
||||
let addr = ctx.get_config(Config::Addr).await?;
|
||||
let profile_image = ctx.get_config(Config::Selfavatar).await?;
|
||||
let color = color_int_to_hex_string(
|
||||
Contact::get_by_id(ctx, ContactId::SELF).await?.get_color(),
|
||||
);
|
||||
Ok(Account::Configured {
|
||||
id,
|
||||
display_name,
|
||||
addr,
|
||||
profile_image,
|
||||
color,
|
||||
})
|
||||
} else {
|
||||
Ok(Account::Unconfigured { id })
|
||||
}
|
||||
}
|
||||
}
|
||||
91
deltachat-jsonrpc/src/api/types/chat.rs
Normal file
91
deltachat-jsonrpc/src/api/types/chat.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use deltachat::chat::get_chat_contacts;
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use deltachat::context::Context;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
use super::contact::ContactObject;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
pub struct FullChat {
|
||||
id: u32,
|
||||
name: String,
|
||||
is_protected: bool,
|
||||
profile_image: Option<String>, //BLOBS ?
|
||||
archived: bool,
|
||||
// subtitle - will be moved to frontend because it uses translation functions
|
||||
chat_type: u32,
|
||||
is_unpromoted: bool,
|
||||
is_self_talk: bool,
|
||||
contacts: Vec<ContactObject>,
|
||||
contact_ids: Vec<u32>,
|
||||
color: String,
|
||||
fresh_message_counter: usize,
|
||||
// is_group - please check over chat.type in frontend instead
|
||||
is_contact_request: bool,
|
||||
is_device_chat: bool,
|
||||
self_in_group: bool,
|
||||
is_muted: bool,
|
||||
ephemeral_timer: u32, //TODO look if there are more important properties in newer core versions
|
||||
can_send: bool,
|
||||
}
|
||||
|
||||
impl FullChat {
|
||||
pub async fn from_dc_chat_id(context: &Context, chat_id: u32) -> Result<Self> {
|
||||
let rust_chat_id = ChatId::new(chat_id);
|
||||
let chat = Chat::load_from_db(context, rust_chat_id).await?;
|
||||
|
||||
let contact_ids = get_chat_contacts(context, rust_chat_id).await?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
|
||||
for contact_id in &contact_ids {
|
||||
contacts.push(
|
||||
ContactObject::from_dc_contact(
|
||||
context,
|
||||
Contact::load_from_db(context, *contact_id).await?,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
let profile_image = match chat.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||
let fresh_message_counter = rust_chat_id.get_fresh_msg_cnt(context).await?;
|
||||
let ephemeral_timer = rust_chat_id.get_ephemeral_timer(context).await?.to_u32();
|
||||
|
||||
let can_send = chat.can_send(context).await?;
|
||||
|
||||
Ok(FullChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
is_protected: chat.is_protected(),
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == deltachat::chat::ChatVisibility::Archived,
|
||||
chat_type: chat
|
||||
.get_type()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
|
||||
is_unpromoted: chat.is_unpromoted(),
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
contacts,
|
||||
contact_ids: contact_ids.iter().map(|id| id.to_u32()).collect(),
|
||||
color,
|
||||
fresh_message_counter,
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_device_chat: chat.is_device_talk(),
|
||||
self_in_group: contact_ids.contains(&ContactId::SELF),
|
||||
is_muted: chat.is_muted(),
|
||||
ephemeral_timer,
|
||||
can_send,
|
||||
})
|
||||
}
|
||||
}
|
||||
117
deltachat-jsonrpc/src/api/types/chat_list.rs
Normal file
117
deltachat-jsonrpc/src/api/types/chat_list.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::ContactId;
|
||||
use deltachat::{
|
||||
chat::{get_chat_contacts, ChatVisibility},
|
||||
chatlist::Chatlist,
|
||||
};
|
||||
use deltachat::{
|
||||
chat::{Chat, ChatId},
|
||||
message::MsgId,
|
||||
};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Deserialize, Serialize, TypeDef)]
|
||||
pub struct ChatListEntry(pub u32, pub u32);
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ChatListItemFetchResult {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatListItem {
|
||||
id: u32,
|
||||
name: String,
|
||||
avatar_path: Option<String>,
|
||||
color: String,
|
||||
last_updated: Option<i64>,
|
||||
summary_text1: String,
|
||||
summary_text2: String,
|
||||
summary_status: u32,
|
||||
is_protected: bool,
|
||||
is_group: bool,
|
||||
fresh_message_counter: usize,
|
||||
is_self_talk: bool,
|
||||
is_device_talk: bool,
|
||||
is_sending_location: bool,
|
||||
is_self_in_group: bool,
|
||||
is_archived: bool,
|
||||
is_pinned: bool,
|
||||
is_muted: bool,
|
||||
is_contact_request: bool,
|
||||
},
|
||||
ArchiveLink,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Error {
|
||||
id: u32,
|
||||
error: String,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) async fn get_chat_list_item_by_id(
|
||||
ctx: &deltachat::context::Context,
|
||||
entry: &ChatListEntry,
|
||||
) -> Result<ChatListItemFetchResult> {
|
||||
let chat_id = ChatId::new(entry.0);
|
||||
let last_msgid = match entry.1 {
|
||||
0 => None,
|
||||
_ => Some(MsgId::new(entry.1)),
|
||||
};
|
||||
|
||||
if chat_id.is_archived_link() {
|
||||
return Ok(ChatListItemFetchResult::ArchiveLink);
|
||||
}
|
||||
|
||||
let chat = Chat::load_from_db(ctx, chat_id).await?;
|
||||
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat)).await?;
|
||||
|
||||
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
|
||||
let summary_text2 = summary.text.to_owned();
|
||||
|
||||
let visibility = chat.get_visibility();
|
||||
|
||||
let avatar_path = chat
|
||||
.get_profile_image(ctx)
|
||||
.await?
|
||||
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
|
||||
|
||||
let last_updated = match last_msgid {
|
||||
Some(id) => {
|
||||
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
|
||||
Some(last_message.get_timestamp() * 1000)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let self_in_group = get_chat_contacts(ctx, chat_id)
|
||||
.await?
|
||||
.contains(&ContactId::SELF);
|
||||
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
let color = color_int_to_hex_string(chat.get_color(ctx).await?);
|
||||
|
||||
Ok(ChatListItemFetchResult::ChatListItem {
|
||||
id: chat_id.to_u32(),
|
||||
name: chat.get_name().to_owned(),
|
||||
avatar_path,
|
||||
color,
|
||||
last_updated,
|
||||
summary_text1,
|
||||
summary_text2,
|
||||
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
|
||||
is_protected: chat.is_protected(),
|
||||
is_group: chat.get_type() == Chattype::Group,
|
||||
fresh_message_counter,
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
is_device_talk: chat.is_device_talk(),
|
||||
is_self_in_group: self_in_group,
|
||||
is_sending_location: chat.is_sending_locations(),
|
||||
is_archived: visibility == ChatVisibility::Archived,
|
||||
is_pinned: visibility == ChatVisibility::Pinned,
|
||||
is_muted: chat.is_muted(),
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
})
|
||||
}
|
||||
50
deltachat-jsonrpc/src/api/types/contact.rs
Normal file
50
deltachat-jsonrpc/src/api/types/contact.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::contact::VerifiedStatus;
|
||||
use deltachat::context::Context;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Contact")]
|
||||
pub struct ContactObject {
|
||||
address: String,
|
||||
color: String,
|
||||
auth_name: String,
|
||||
status: String,
|
||||
display_name: String,
|
||||
id: u32,
|
||||
name: String,
|
||||
profile_image: Option<String>, // BLOBS
|
||||
name_and_addr: String,
|
||||
is_blocked: bool,
|
||||
is_verified: bool,
|
||||
}
|
||||
|
||||
impl ContactObject {
|
||||
pub async fn from_dc_contact(
|
||||
context: &Context,
|
||||
contact: deltachat::contact::Contact,
|
||||
) -> Result<Self> {
|
||||
let profile_image = match contact.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let is_verified = contact.is_verified(context).await? == VerifiedStatus::BidirectVerified;
|
||||
|
||||
Ok(ContactObject {
|
||||
address: contact.get_addr().to_owned(),
|
||||
color: color_int_to_hex_string(contact.get_color()),
|
||||
auth_name: contact.get_authname().to_owned(),
|
||||
status: contact.get_status().to_owned(),
|
||||
display_name: contact.get_display_name().to_owned(),
|
||||
id: contact.id.to_u32(),
|
||||
name: contact.get_name().to_owned(),
|
||||
profile_image, //BLOBS
|
||||
name_and_addr: contact.get_name_n_addr(),
|
||||
is_blocked: contact.is_blocked(),
|
||||
is_verified,
|
||||
})
|
||||
}
|
||||
}
|
||||
127
deltachat-jsonrpc/src/api/types/message.rs
Normal file
127
deltachat-jsonrpc/src/api/types/message.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::message::Message;
|
||||
use deltachat::message::MsgId;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::contact::ContactObject;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Message")]
|
||||
pub struct MessageObject {
|
||||
id: u32,
|
||||
chat_id: u32,
|
||||
from_id: u32,
|
||||
quoted_text: Option<String>,
|
||||
quoted_message_id: Option<u32>,
|
||||
text: Option<String>,
|
||||
has_location: bool,
|
||||
has_html: bool,
|
||||
view_type: u32,
|
||||
state: u32,
|
||||
|
||||
timestamp: i64,
|
||||
sort_timestamp: i64,
|
||||
received_timestamp: i64,
|
||||
has_deviating_timestamp: bool,
|
||||
|
||||
// summary - use/create another function if you need it
|
||||
subject: String,
|
||||
show_padlock: bool,
|
||||
is_setupmessage: bool,
|
||||
is_info: bool,
|
||||
is_forwarded: bool,
|
||||
|
||||
duration: i32,
|
||||
dimensions_height: i32,
|
||||
dimensions_width: i32,
|
||||
|
||||
videochat_type: Option<u32>,
|
||||
videochat_url: Option<String>,
|
||||
|
||||
override_sender_name: Option<String>,
|
||||
sender: ContactObject,
|
||||
|
||||
setup_code_begin: Option<String>,
|
||||
|
||||
file: Option<String>,
|
||||
file_mime: Option<String>,
|
||||
file_bytes: u64,
|
||||
file_name: Option<String>,
|
||||
}
|
||||
|
||||
impl MessageObject {
|
||||
pub async fn from_message_id(context: &Context, message_id: u32) -> Result<Self> {
|
||||
let msg_id = MsgId::new(message_id);
|
||||
let message = Message::load_from_db(context, msg_id).await?;
|
||||
|
||||
let quoted_message_id = message
|
||||
.quoted_message(context)
|
||||
.await?
|
||||
.map(|m| m.get_id().to_u32());
|
||||
|
||||
let sender_contact = Contact::load_from_db(context, message.get_from_id()).await?;
|
||||
let sender = ContactObject::from_dc_contact(context, sender_contact).await?;
|
||||
let file_bytes = message.get_filebytes(context).await;
|
||||
let override_sender_name = message.get_override_sender_name();
|
||||
|
||||
Ok(MessageObject {
|
||||
id: message_id,
|
||||
chat_id: message.get_chat_id().to_u32(),
|
||||
from_id: message.get_from_id().to_u32(),
|
||||
quoted_text: message.quoted_text(),
|
||||
quoted_message_id,
|
||||
text: message.get_text(),
|
||||
has_location: message.has_location(),
|
||||
has_html: message.has_html(),
|
||||
view_type: message
|
||||
.get_viewtype()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("viewtype conversion to number failed"))?,
|
||||
state: message
|
||||
.get_state()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
||||
|
||||
timestamp: message.get_timestamp(),
|
||||
sort_timestamp: message.get_sort_timestamp(),
|
||||
received_timestamp: message.get_received_timestamp(),
|
||||
has_deviating_timestamp: message.has_deviating_timestamp(),
|
||||
|
||||
subject: message.get_subject().to_owned(),
|
||||
show_padlock: message.get_showpadlock(),
|
||||
is_setupmessage: message.is_setupmessage(),
|
||||
is_info: message.is_info(),
|
||||
is_forwarded: message.is_forwarded(),
|
||||
|
||||
duration: message.get_duration(),
|
||||
dimensions_height: message.get_height(),
|
||||
dimensions_width: message.get_width(),
|
||||
|
||||
videochat_type: match message.get_videochat_type() {
|
||||
Some(vct) => Some(
|
||||
vct.to_u32()
|
||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
||||
),
|
||||
None => None,
|
||||
},
|
||||
videochat_url: message.get_videochat_url(),
|
||||
|
||||
override_sender_name,
|
||||
sender,
|
||||
|
||||
setup_code_begin: message.get_setupcodebegin(context).await,
|
||||
|
||||
file: match message.get_file(context) {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
}, //BLOBS
|
||||
file_mime: message.get_filemime(),
|
||||
file_bytes,
|
||||
file_name: message.get_filename(),
|
||||
})
|
||||
}
|
||||
}
|
||||
10
deltachat-jsonrpc/src/api/types/mod.rs
Normal file
10
deltachat-jsonrpc/src/api/types/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
pub mod account;
|
||||
pub mod chat;
|
||||
pub mod chat_list;
|
||||
pub mod contact;
|
||||
pub mod message;
|
||||
pub mod provider_info;
|
||||
|
||||
pub fn color_int_to_hex_string(color: u32) -> String {
|
||||
format!("{:#08x}", color).replace("0x", "#")
|
||||
}
|
||||
21
deltachat-jsonrpc/src/api/types/provider_info.rs
Normal file
21
deltachat-jsonrpc/src/api/types/provider_info.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use deltachat::provider::Provider;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
pub struct ProviderInfo {
|
||||
pub before_login_hint: String,
|
||||
pub overview_page: String,
|
||||
pub status: u32, // in reality this is an enum, but for simlicity and because it gets converted into a number anyway, we use an u32 here.
|
||||
}
|
||||
|
||||
impl ProviderInfo {
|
||||
pub fn from_dc_type(provider: Option<&Provider>) -> Option<Self> {
|
||||
provider.map(|p| ProviderInfo {
|
||||
before_login_hint: p.before_login_hint.to_owned(),
|
||||
overview_page: p.overview_page.to_owned(),
|
||||
status: p.status.to_u32().unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
60
deltachat-jsonrpc/src/lib.rs
Normal file
60
deltachat-jsonrpc/src/lib.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
pub mod api;
|
||||
pub use api::events;
|
||||
|
||||
pub use yerpc;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::api::{Accounts, CommandApi};
|
||||
use async_channel::unbounded;
|
||||
use async_std::task;
|
||||
use futures::StreamExt;
|
||||
use tempfile::TempDir;
|
||||
use yerpc::{MessageHandle, RpcHandle};
|
||||
|
||||
#[async_std::test]
|
||||
async fn basic_json_rpc_functionality() -> anyhow::Result<()> {
|
||||
// println!("{}", "");
|
||||
let tmp_dir = TempDir::new().unwrap().path().into();
|
||||
println!("tmp_dir: {:?}", tmp_dir);
|
||||
|
||||
let accounts = Accounts::new(tmp_dir).await?;
|
||||
let cmd_api = CommandApi::new(accounts);
|
||||
|
||||
let (sender, mut receiver) = unbounded::<String>();
|
||||
|
||||
let (request_handle, mut rx) = RpcHandle::new();
|
||||
let session = cmd_api;
|
||||
let handle = MessageHandle::new(request_handle, session);
|
||||
task::spawn({
|
||||
async move {
|
||||
while let Some(message) = rx.next().await {
|
||||
let message = serde_json::to_string(&message)?;
|
||||
// Abort serialization on error.
|
||||
sender.send(message).await?;
|
||||
}
|
||||
let res: Result<(), anyhow::Error> = Ok(());
|
||||
res
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"add_account","params":[],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
|
||||
handle.handle_message(request).await;
|
||||
let result = receiver.next().await;
|
||||
println!("{:?}", result);
|
||||
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]}"#;
|
||||
handle.handle_message(request).await;
|
||||
let result = receiver.next().await;
|
||||
println!("{:?}", result);
|
||||
assert_eq!(result, Some(response.to_owned()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
44
deltachat-jsonrpc/src/webserver.rs
Normal file
44
deltachat-jsonrpc/src/webserver.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use async_std::path::PathBuf;
|
||||
use async_std::task;
|
||||
use tide::Request;
|
||||
use yerpc::RpcHandle;
|
||||
use yerpc_tide::yerpc_handler;
|
||||
|
||||
mod api;
|
||||
use api::events::event_to_json_rpc_notification;
|
||||
use api::{Accounts, CommandApi};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
env_logger::init();
|
||||
log::info!("Starting");
|
||||
|
||||
let accounts = Accounts::new(PathBuf::from("./accounts")).await.unwrap();
|
||||
let state = CommandApi::new(accounts);
|
||||
|
||||
let mut app = tide::with_state(state.clone());
|
||||
app.at("/ws").get(yerpc_handler(request_handler));
|
||||
|
||||
state.accounts.read().await.start_io().await;
|
||||
app.listen("127.0.0.1:20808").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn request_handler(
|
||||
request: Request<CommandApi>,
|
||||
rpc: RpcHandle,
|
||||
) -> anyhow::Result<CommandApi> {
|
||||
let state = request.state().clone();
|
||||
task::spawn(event_loop(state.clone(), rpc));
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
async fn event_loop(state: CommandApi, rpc: RpcHandle) -> anyhow::Result<()> {
|
||||
let events = state.accounts.read().await.get_event_emitter().await;
|
||||
while let Some(event) = events.recv().await {
|
||||
// log::debug!("event {:?}", event);
|
||||
let event = event_to_json_rpc_notification(event);
|
||||
rpc.notify("event", Some(event)).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
6
deltachat-jsonrpc/typescript/.gitignore
vendored
Normal file
6
deltachat-jsonrpc/typescript/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
test_dist
|
||||
coverage
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
3
deltachat-jsonrpc/typescript/.prettierignore
Normal file
3
deltachat-jsonrpc/typescript/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
coverage
|
||||
dist
|
||||
generated
|
||||
1
deltachat-jsonrpc/typescript/deltachat.ts
Normal file
1
deltachat-jsonrpc/typescript/deltachat.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src/lib.js";
|
||||
107
deltachat-jsonrpc/typescript/example.ts
Normal file
107
deltachat-jsonrpc/typescript/example.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { RawClient, RPC } from "./src/lib";
|
||||
import { WebsocketTransport, Request } from "yerpc";
|
||||
|
||||
type DeltaEvent = { id: string; contextId: number; field1: any; field2: any };
|
||||
var selectedAccount = 0;
|
||||
window.addEventListener("DOMContentLoaded", (_event) => {
|
||||
(window as any).selectDeltaAccount = (id: string) => {
|
||||
selectedAccount = Number(id);
|
||||
window.dispatchEvent(new Event("account-changed"));
|
||||
};
|
||||
run().catch((err) => console.error("run failed", err));
|
||||
});
|
||||
|
||||
async function run() {
|
||||
const $main = document.getElementById("main")!;
|
||||
const $side = document.getElementById("side")!;
|
||||
const $head = document.getElementById("header")!;
|
||||
|
||||
const transport = new WebsocketTransport("ws://localhost:20808/ws");
|
||||
const client = new RawClient(transport);
|
||||
|
||||
(window as any).client = client;
|
||||
|
||||
transport.on("request", (request: Request) => {
|
||||
const method = request.method;
|
||||
if (method === "event") {
|
||||
const params = request.params! as DeltaEvent;
|
||||
onIncomingEvent(params, params.id);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("account-changed", async (_event: Event) => {
|
||||
await client.selectAccount(selectedAccount);
|
||||
listChatsForSelectedAccount();
|
||||
});
|
||||
|
||||
await Promise.all([loadAccountsInHeader(), listChatsForSelectedAccount()]);
|
||||
|
||||
async function loadAccountsInHeader() {
|
||||
const accounts = await client.getAllAccounts();
|
||||
for (const account of accounts) {
|
||||
if (account.type === "Configured") {
|
||||
write(
|
||||
$head,
|
||||
`<a href="#" onclick="selectDeltaAccount(${account.id})">
|
||||
${account.addr!}
|
||||
</a> `
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function listChatsForSelectedAccount() {
|
||||
clear($main);
|
||||
const selectedAccount = await client.getSelectedAccountId();
|
||||
if (!selectedAccount) return write($main, "No account selected");
|
||||
const info = await client.getAccountInfo(selectedAccount);
|
||||
if (info.type !== "Configured") {
|
||||
return write($main, "Account is not configured");
|
||||
}
|
||||
write($main, `<h2>${info.addr!}</h2>`);
|
||||
const chats = await client.getChatlistEntries(
|
||||
selectedAccount,
|
||||
0,
|
||||
null,
|
||||
null
|
||||
);
|
||||
for (const [chatId, _messageId] of chats) {
|
||||
const chat = await client.chatlistGetFullChatById(
|
||||
selectedAccount,
|
||||
chatId
|
||||
);
|
||||
write($main, `<h3>${chat.name}</h3>`);
|
||||
const messageIds = await client.messageListGetMessageIds(
|
||||
selectedAccount,
|
||||
chatId,
|
||||
0
|
||||
);
|
||||
const messages = await client.messageGetMessages(
|
||||
selectedAccount,
|
||||
messageIds
|
||||
);
|
||||
for (const [_messageId, message] of Object.entries(messages)) {
|
||||
write($main, `<p>${message.text}</p>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onIncomingEvent(event: DeltaEvent, name: string) {
|
||||
write(
|
||||
$side,
|
||||
`
|
||||
<p class="message">
|
||||
[<strong>${name}</strong> on account ${event.contextId}]<br>
|
||||
<em>f1:</em> ${JSON.stringify(event.field1)}<br>
|
||||
<em>f2:</em> ${JSON.stringify(event.field2)}
|
||||
</p>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function write(el: HTMLElement, html: string) {
|
||||
el.innerHTML += html;
|
||||
}
|
||||
function clear(el: HTMLElement) {
|
||||
el.innerHTML = "";
|
||||
}
|
||||
251
deltachat-jsonrpc/typescript/generated/client.ts
Normal file
251
deltachat-jsonrpc/typescript/generated/client.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
// AUTO-GENERATED by yerpc-derive
|
||||
|
||||
import * as T from "./types.js"
|
||||
import * as RPC from "./jsonrpc.js"
|
||||
|
||||
type RequestMethod = (method: string, params?: RPC.Params) => Promise<unknown>;
|
||||
type NotificationMethod = (method: string, params?: RPC.Params) => void;
|
||||
|
||||
interface Transport {
|
||||
request: RequestMethod,
|
||||
notification: NotificationMethod
|
||||
}
|
||||
|
||||
export class RawClient {
|
||||
constructor(private _transport: Transport) {}
|
||||
|
||||
/**
|
||||
* Check if an email address is valid.
|
||||
*/
|
||||
public checkEmailValidity(email: string): Promise<boolean> {
|
||||
return (this._transport.request('check_email_validity', [email] as RPC.Params)) as Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get general system info.
|
||||
*/
|
||||
public getSystemInfo(): Promise<Record<string,string>> {
|
||||
return (this._transport.request('get_system_info', [] as RPC.Params)) as Promise<Record<string,string>>;
|
||||
}
|
||||
|
||||
|
||||
public addAccount(): Promise<T.U32> {
|
||||
return (this._transport.request('add_account', [] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
|
||||
public removeAccount(accountId: T.U32): Promise<null> {
|
||||
return (this._transport.request('remove_account', [accountId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public getAllAccountIds(): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('get_all_account_ids', [] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select account id for internally selected state.
|
||||
* TODO: Likely this is deprecated as all methods take an account id now.
|
||||
*/
|
||||
public selectAccount(id: T.U32): Promise<null> {
|
||||
return (this._transport.request('select_account', [id] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected account id of the internal state..
|
||||
* TODO: Likely this is deprecated as all methods take an account id now.
|
||||
*/
|
||||
public getSelectedAccountId(): Promise<(T.U32|null)> {
|
||||
return (this._transport.request('get_selected_account_id', [] as RPC.Params)) as Promise<(T.U32|null)>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all configured accounts.
|
||||
*/
|
||||
public getAllAccounts(): Promise<(T.Account)[]> {
|
||||
return (this._transport.request('get_all_accounts', [] as RPC.Params)) as Promise<(T.Account)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top-level info for an account.
|
||||
*/
|
||||
public getAccountInfo(accountId: T.U32): Promise<T.Account> {
|
||||
return (this._transport.request('get_account_info', [accountId] as RPC.Params)) as Promise<T.Account>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns provider for the given domain.
|
||||
*
|
||||
* This function looks up domain in offline database.
|
||||
*
|
||||
* For compatibility, email address can be passed to this function
|
||||
* instead of the domain.
|
||||
*/
|
||||
public getProviderInfo(accountId: T.U32, email: string): Promise<(T.ProviderInfo|null)> {
|
||||
return (this._transport.request('get_provider_info', [accountId, email] as RPC.Params)) as Promise<(T.ProviderInfo|null)>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the context is already configured.
|
||||
*/
|
||||
public isConfigured(accountId: T.U32): Promise<boolean> {
|
||||
return (this._transport.request('is_configured', [accountId] as RPC.Params)) as Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system info for an account.
|
||||
*/
|
||||
public getInfo(accountId: T.U32): Promise<Record<string,string>> {
|
||||
return (this._transport.request('get_info', [accountId] as RPC.Params)) as Promise<Record<string,string>>;
|
||||
}
|
||||
|
||||
|
||||
public setConfig(accountId: T.U32, key: string, value: (string|null)): Promise<null> {
|
||||
return (this._transport.request('set_config', [accountId, key, value] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public batchSetConfig(accountId: T.U32, config: Record<string,(string|null)>): Promise<null> {
|
||||
return (this._transport.request('batch_set_config', [accountId, config] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public getConfig(accountId: T.U32, key: string): Promise<(string|null)> {
|
||||
return (this._transport.request('get_config', [accountId, key] as RPC.Params)) as Promise<(string|null)>;
|
||||
}
|
||||
|
||||
|
||||
public batchGetConfig(accountId: T.U32, keys: (string)[]): Promise<Record<string,(string|null)>> {
|
||||
return (this._transport.request('batch_get_config', [accountId, keys] as RPC.Params)) as Promise<Record<string,(string|null)>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this account with the currently set parameters.
|
||||
* Setup the credential config before calling this.
|
||||
*/
|
||||
public configure(accountId: T.U32): Promise<null> {
|
||||
return (this._transport.request('configure', [accountId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal an ongoing process to stop.
|
||||
*/
|
||||
public stopOngoingProcess(accountId: T.U32): Promise<null> {
|
||||
return (this._transport.request('stop_ongoing_process', [accountId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public autocryptInitiateKeyTransfer(accountId: T.U32): Promise<string> {
|
||||
return (this._transport.request('autocrypt_initiate_key_transfer', [accountId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
public autocryptContinueKeyTransfer(accountId: T.U32, messageId: T.U32, setupCode: string): Promise<null> {
|
||||
return (this._transport.request('autocrypt_continue_key_transfer', [accountId, messageId, setupCode] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public getChatlistEntries(accountId: T.U32, listFlags: (T.U32|null), queryString: (string|null), queryContactId: (T.U32|null)): Promise<(T.ChatListEntry)[]> {
|
||||
return (this._transport.request('get_chatlist_entries', [accountId, listFlags, queryString, queryContactId] as RPC.Params)) as Promise<(T.ChatListEntry)[]>;
|
||||
}
|
||||
|
||||
|
||||
public getChatlistItemsByEntries(accountId: T.U32, entries: (T.ChatListEntry)[]): Promise<Record<T.U32,T.ChatListItemFetchResult>> {
|
||||
return (this._transport.request('get_chatlist_items_by_entries', [accountId, entries] as RPC.Params)) as Promise<Record<T.U32,T.ChatListItemFetchResult>>;
|
||||
}
|
||||
|
||||
|
||||
public chatlistGetFullChatById(accountId: T.U32, chatId: T.U32): Promise<T.FullChat> {
|
||||
return (this._transport.request('chatlist_get_full_chat_by_id', [accountId, chatId] as RPC.Params)) as Promise<T.FullChat>;
|
||||
}
|
||||
|
||||
|
||||
public acceptChat(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('accept_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public blockChat(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('block_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public messageListGetMessageIds(accountId: T.U32, chatId: T.U32, flags: T.U32): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('message_list_get_message_ids', [accountId, chatId, flags] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
|
||||
public messageGetMessage(accountId: T.U32, messageId: T.U32): Promise<T.Message> {
|
||||
return (this._transport.request('message_get_message', [accountId, messageId] as RPC.Params)) as Promise<T.Message>;
|
||||
}
|
||||
|
||||
|
||||
public messageGetMessages(accountId: T.U32, messageIds: (T.U32)[]): Promise<Record<T.U32,T.Message>> {
|
||||
return (this._transport.request('message_get_messages', [accountId, messageIds] as RPC.Params)) as Promise<Record<T.U32,T.Message>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single contact options by ID.
|
||||
*/
|
||||
public contactsGetContact(accountId: T.U32, contactId: T.U32): Promise<T.Contact> {
|
||||
return (this._transport.request('contacts_get_contact', [accountId, contactId] as RPC.Params)) as Promise<T.Contact>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single contact as a result of an explicit user action.
|
||||
*
|
||||
* Returns contact id of the created or existing contact
|
||||
*/
|
||||
public contactsCreateContact(accountId: T.U32, email: string, name: (string|null)): Promise<T.U32> {
|
||||
return (this._transport.request('contacts_create_contact', [accountId, email, name] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns contact id of the created or existing DM chat with that contact
|
||||
*/
|
||||
public contactsCreateChatByContactId(accountId: T.U32, contactId: T.U32): Promise<T.U32> {
|
||||
return (this._transport.request('contacts_create_chat_by_contact_id', [accountId, contactId] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
|
||||
public contactsBlock(accountId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('contacts_block', [accountId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public contactsUnblock(accountId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('contacts_unblock', [accountId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public contactsGetBlocked(accountId: T.U32): Promise<(T.Contact)[]> {
|
||||
return (this._transport.request('contacts_get_blocked', [accountId] as RPC.Params)) as Promise<(T.Contact)[]>;
|
||||
}
|
||||
|
||||
|
||||
public contactsGetContactIds(accountId: T.U32, listFlags: T.U32, query: (string|null)): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('contacts_get_contact_ids', [accountId, listFlags, query] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of contacts.
|
||||
* (formerly called getContacts2 in desktop)
|
||||
*/
|
||||
public contactsGetContacts(accountId: T.U32, listFlags: T.U32, query: (string|null)): Promise<(T.Contact)[]> {
|
||||
return (this._transport.request('contacts_get_contacts', [accountId, listFlags, query] as RPC.Params)) as Promise<(T.Contact)[]>;
|
||||
}
|
||||
|
||||
|
||||
public contactsGetContactsByIds(accountId: T.U32, ids: (T.U32)[]): Promise<Record<T.U32,T.Contact>> {
|
||||
return (this._transport.request('contacts_get_contacts_by_ids', [accountId, ids] as RPC.Params)) as Promise<Record<T.U32,T.Contact>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the messageid of the sent message
|
||||
*/
|
||||
public miscSendTextMessage(accountId: T.U32, text: string, chatId: T.U32): Promise<T.U32> {
|
||||
return (this._transport.request('misc_send_text_message', [accountId, text, chatId] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
3
deltachat-jsonrpc/typescript/generated/events.ts
Normal file
3
deltachat-jsonrpc/typescript/generated/events.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// AUTO-GENERATED by typescript-type-def
|
||||
|
||||
export type EventTypeName=("Info"|"SmtpConnected"|"ImapConnected"|"SmtpMessageSent"|"ImapMessageDeleted"|"ImapMessageMoved"|"NewBlobFile"|"DeletedBlobFile"|"Warning"|"Error"|"ErrorSelfNotInGroup"|"MsgsChanged"|"IncomingMsg"|"MsgsNoticed"|"MsgDelivered"|"MsgFailed"|"MsgRead"|"ChatModified"|"ChatEphemeralTimerModified"|"ContactsChanged"|"LocationChanged"|"ConfigureProgress"|"ImexProgress"|"ImexFileWritten"|"SecurejoinInviterProgress"|"SecurejoinJoinerProgress"|"ConnectivityChanged"|"SelfavatarChanged"|"WebxdcStatusUpdate");
|
||||
10
deltachat-jsonrpc/typescript/generated/jsonrpc.ts
Normal file
10
deltachat-jsonrpc/typescript/generated/jsonrpc.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// AUTO-GENERATED by typescript-type-def
|
||||
|
||||
export type JSONValue=(null|boolean|number|string|(JSONValue)[]|{[key:string]:JSONValue;});
|
||||
export type Params=((JSONValue)[]|Record<string,JSONValue>);
|
||||
export type U32=number;
|
||||
export type Request={"jsonrpc":"2.0";"method":string;"params"?:Params;"id"?:U32;};
|
||||
export type I32=number;
|
||||
export type Error={"code":I32;"message":string;"data"?:JSONValue;};
|
||||
export type Response={"jsonrpc":"2.0";"id":(U32|null);"result"?:JSONValue;"error"?:Error;};
|
||||
export type Message=(Request|Response);
|
||||
15
deltachat-jsonrpc/typescript/generated/types.ts
Normal file
15
deltachat-jsonrpc/typescript/generated/types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// AUTO-GENERATED by typescript-type-def
|
||||
|
||||
export type U32=number;
|
||||
export type Account=(({"type":"Configured";}&{"id":U32;"display_name":(string|null);"addr":(string|null);"profile_image":(string|null);"color":string;})|({"type":"Unconfigured";}&{"id":U32;}));
|
||||
export type ProviderInfo={"before_login_hint":string;"overview_page":string;"status":U32;};
|
||||
export type ChatListEntry=[U32,U32];
|
||||
export type I64=number;
|
||||
export type Usize=number;
|
||||
export type ChatListItemFetchResult=(({"type":"ChatListItem";}&{"id":U32;"name":string;"avatarPath":(string|null);"color":string;"lastUpdated":(I64|null);"summaryText1":string;"summaryText2":string;"summaryStatus":U32;"isProtected":boolean;"isGroup":boolean;"freshMessageCounter":Usize;"isSelfTalk":boolean;"isDeviceTalk":boolean;"isSendingLocation":boolean;"isSelfInGroup":boolean;"isArchived":boolean;"isPinned":boolean;"isMuted":boolean;"isContactRequest":boolean;})|{"type":"ArchiveLink";}|({"type":"Error";}&{"id":U32;"error":string;}));
|
||||
export type Contact={"address":string;"color":string;"auth_name":string;"status":string;"display_name":string;"id":U32;"name":string;"profile_image":(string|null);"name_and_addr":string;"is_blocked":boolean;"is_verified":boolean;};
|
||||
export type FullChat={"id":U32;"name":string;"is_protected":boolean;"profile_image":(string|null);"archived":boolean;"chat_type":U32;"is_unpromoted":boolean;"is_self_talk":boolean;"contacts":(Contact)[];"contact_ids":(U32)[];"color":string;"fresh_message_counter":Usize;"is_contact_request":boolean;"is_device_chat":boolean;"self_in_group":boolean;"is_muted":boolean;"ephemeral_timer":U32;"can_send":boolean;};
|
||||
export type I32=number;
|
||||
export type U64=number;
|
||||
export type Message={"id":U32;"chat_id":U32;"from_id":U32;"quoted_text":(string|null);"quoted_message_id":(U32|null);"text":(string|null);"has_location":boolean;"has_html":boolean;"view_type":U32;"state":U32;"timestamp":I64;"sort_timestamp":I64;"received_timestamp":I64;"has_deviating_timestamp":boolean;"subject":string;"show_padlock":boolean;"is_setupmessage":boolean;"is_info":boolean;"is_forwarded":boolean;"duration":I32;"dimensions_height":I32;"dimensions_width":I32;"videochat_type":(U32|null);"videochat_url":(string|null);"override_sender_name":(string|null);"sender":Contact;"setup_code_begin":(string|null);"file":(string|null);"file_mime":(string|null);"file_bytes":U64;"file_name":(string|null);};
|
||||
export type __AllTyps=[string,boolean,Record<string,string>,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],U32,Account,U32,string,(ProviderInfo|null),U32,boolean,U32,Record<string,string>,U32,string,(string|null),null,U32,Record<string,(string|null)>,null,U32,string,(string|null),U32,(string)[],Record<string,(string|null)>,U32,null,U32,null,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record<U32,ChatListItemFetchResult>,U32,U32,FullChat,U32,U32,null,U32,U32,null,U32,U32,U32,(U32)[],U32,U32,Message,U32,(U32)[],Record<U32,Message>,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record<U32,Contact>,U32,string,U32,U32];
|
||||
54
deltachat-jsonrpc/typescript/index.html
Normal file
54
deltachat-jsonrpc/typescript/index.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
background: black;
|
||||
color: grey;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-areas: "a a" "b c";
|
||||
}
|
||||
.message {
|
||||
color: red;
|
||||
}
|
||||
#header {
|
||||
grid-area: a;
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
#header a {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
#main {
|
||||
grid-area: b;
|
||||
color: green;
|
||||
}
|
||||
#main h2,
|
||||
#main h3 {
|
||||
color: blue;
|
||||
}
|
||||
#side {
|
||||
grid-area: c;
|
||||
color: #777;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="dist/example.bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="grid">
|
||||
<div id="header"></div>
|
||||
<div id="main"></div>
|
||||
<div id="side"><h2>log</h2></div>
|
||||
</div>
|
||||
<p>
|
||||
Tip: open the dev console and use the client with
|
||||
<code>window.client</code>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
13
deltachat-jsonrpc/typescript/node-demo.js
Normal file
13
deltachat-jsonrpc/typescript/node-demo.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Deltachat } from "./dist/deltachat.js";
|
||||
|
||||
run().catch(console.error);
|
||||
|
||||
async function run() {
|
||||
const delta = new Deltachat();
|
||||
delta.addEventListener("event", (event) => {
|
||||
console.log("event", event.data);
|
||||
});
|
||||
|
||||
const accounts = await delta.rpc.getAllAccounts();
|
||||
console.log("accounts", accounts);
|
||||
}
|
||||
41
deltachat-jsonrpc/typescript/package.json
Normal file
41
deltachat-jsonrpc/typescript/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@deltachat/jsonrpc-client",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/deltachat.js",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"type": "module",
|
||||
"author": "Delta Chat Developers (ML) <delta@codespeak.net>",
|
||||
"license": "MPL-2.0",
|
||||
"scripts": {
|
||||
"prettier:check": "prettier --check **.ts",
|
||||
"prettier:fix": "prettier --write **.ts",
|
||||
"build": "npm run generate-bindings && tsc",
|
||||
"bundle": "npm run build && esbuild --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
|
||||
"generate-bindings": "cargo test",
|
||||
"example:build": "tsc && esbuild --bundle dist/example.js --outfile=dist/example.bundle.js",
|
||||
"example:dev": "esbuild example.ts --bundle --outdir=dist --servedir=.",
|
||||
"coverage": "tsc -b test && COVERAGE=1 NODE_OPTIONS=--enable-source-maps c8 --include \"dist/*\" -r text -r html -r json mocha test_dist && node report_api_coverage.mjs",
|
||||
"test": "rm -rf dist && npm run build && npm run coverage && npm run prettier:check"
|
||||
},
|
||||
"dependencies": {
|
||||
"isomorphic-ws": "^4.0.1",
|
||||
"tiny-emitter": "git+https://github.com/Simon-Laux/tiny-emitter.git",
|
||||
"yerpc": "^0.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.6.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"@types/chai": "^4.2.21",
|
||||
"@types/chai-as-promised": "^7.1.5",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/ws": "^7.2.4",
|
||||
"c8": "^7.10.0",
|
||||
"chai": "^4.3.4",
|
||||
"esbuild": "^0.14.11",
|
||||
"mocha": "^9.1.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"typescript": "^4.5.5",
|
||||
"ws": "^8.5.0"
|
||||
}
|
||||
}
|
||||
28
deltachat-jsonrpc/typescript/report_api_coverage.mjs
Normal file
28
deltachat-jsonrpc/typescript/report_api_coverage.mjs
Normal file
@@ -0,0 +1,28 @@
|
||||
import { readFileSync } from "fs";
|
||||
// only checks for the coverge of the api functions in bindings.ts for now
|
||||
const generated_file = "typescript/generated/client.ts";
|
||||
const json = JSON.parse(readFileSync("./coverage/coverage-final.json"));
|
||||
const jsonCoverage =
|
||||
json[Object.keys(json).find((k) => k.includes(generated_file))];
|
||||
const fnMap = Object.keys(jsonCoverage.fnMap).map(
|
||||
(key) => jsonCoverage.fnMap[key]
|
||||
);
|
||||
const htmlCoverage = readFileSync(
|
||||
"./coverage/" + generated_file + ".html",
|
||||
"utf8"
|
||||
);
|
||||
const uncoveredLines = htmlCoverage
|
||||
.split("\n")
|
||||
.filter((line) => line.includes(`"function not covered"`));
|
||||
const uncoveredFunctions = uncoveredLines.map(
|
||||
(line) => />([\w_]+)\(/.exec(line)[1]
|
||||
);
|
||||
console.log(
|
||||
"\nUncovered api functions:\n" +
|
||||
uncoveredFunctions
|
||||
.map((uF) => fnMap.find(({ name }) => name === uF))
|
||||
.map(
|
||||
({ name, line }) => `.${name.padEnd(40)} (${generated_file}:${line})`
|
||||
)
|
||||
.join("\n")
|
||||
);
|
||||
77
deltachat-jsonrpc/typescript/src/client.ts
Normal file
77
deltachat-jsonrpc/typescript/src/client.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import * as T from "../generated/types.js";
|
||||
import * as RPC from "../generated/jsonrpc.js";
|
||||
import { RawClient } from "../generated/client.js";
|
||||
import { EventTypeName } from "../generated/events.js";
|
||||
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
||||
import { TinyEmitter } from "tiny-emitter";
|
||||
|
||||
export type DeltachatEvent = {
|
||||
id: EventTypeName;
|
||||
contextId: number;
|
||||
field1: any;
|
||||
field2: any;
|
||||
};
|
||||
export type Events = Record<
|
||||
EventTypeName | "ALL",
|
||||
(event: DeltachatEvent) => void
|
||||
>;
|
||||
|
||||
export class BaseDeltachat<
|
||||
Transport extends BaseTransport
|
||||
> extends TinyEmitter<Events> {
|
||||
rpc: RawClient;
|
||||
account?: T.Account;
|
||||
constructor(protected transport: Transport) {
|
||||
super();
|
||||
this.rpc = new RawClient(this.transport);
|
||||
this.transport.on("request", (request: Request) => {
|
||||
const method = request.method;
|
||||
if (method === "event") {
|
||||
const event = request.params! as DeltachatEvent;
|
||||
this.emit(event.id, event);
|
||||
this.emit("ALL", event);
|
||||
|
||||
if (this.contextEmitters[event.contextId]) {
|
||||
this.contextEmitters[event.contextId].emit(event.id, event);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async listAccounts(): Promise<T.Account[]> {
|
||||
return await this.rpc.getAllAccounts();
|
||||
}
|
||||
|
||||
private contextEmitters: TinyEmitter<Events>[] = [];
|
||||
|
||||
getContextEvents(account_id: number) {
|
||||
if (this.contextEmitters[account_id]) {
|
||||
return this.contextEmitters[account_id];
|
||||
} else {
|
||||
this.contextEmitters[account_id] = new TinyEmitter();
|
||||
return this.contextEmitters[account_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Opts = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_OPTS: Opts = {
|
||||
url: "ws://localhost:20808/ws",
|
||||
};
|
||||
export class Deltachat extends BaseDeltachat<WebsocketTransport> {
|
||||
opts: Opts;
|
||||
close() {
|
||||
this.transport._socket.close();
|
||||
}
|
||||
constructor(opts: Opts | string | undefined) {
|
||||
if (typeof opts === "string") opts = { url: opts };
|
||||
if (opts) opts = { ...DEFAULT_OPTS, ...opts };
|
||||
else opts = { ...DEFAULT_OPTS };
|
||||
super(new WebsocketTransport(opts.url));
|
||||
this.opts = opts;
|
||||
}
|
||||
}
|
||||
6
deltachat-jsonrpc/typescript/src/lib.ts
Normal file
6
deltachat-jsonrpc/typescript/src/lib.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * as RPC from "../generated/jsonrpc.js";
|
||||
export * as T from "../generated/types.js";
|
||||
export * from "../generated/events.js";
|
||||
export { RawClient } from "../generated/client.js";
|
||||
export * from "./client.js";
|
||||
export * as yerpc from "yerpc";
|
||||
1
deltachat-jsonrpc/typescript/test/README.md
Normal file
1
deltachat-jsonrpc/typescript/test/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# tests need to be ported to new API
|
||||
158
deltachat-jsonrpc/typescript/test/basic.ts
Normal file
158
deltachat-jsonrpc/typescript/test/basic.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { strictEqual } from "assert";
|
||||
import chai, { assert, expect } from "chai";
|
||||
import chaiAsPromised from "chai-as-promised";
|
||||
chai.use(chaiAsPromised);
|
||||
import { Deltachat } from "../dist/deltachat.js";
|
||||
|
||||
import {
|
||||
CMD_API_Server_Handle,
|
||||
CMD_API_SERVER_PORT,
|
||||
startCMD_API_Server,
|
||||
} from "./test_base.js";
|
||||
|
||||
describe("basic tests", () => {
|
||||
let server_handle: CMD_API_Server_Handle;
|
||||
let dc: Deltachat;
|
||||
|
||||
before(async () => {
|
||||
server_handle = await startCMD_API_Server(CMD_API_SERVER_PORT);
|
||||
// make sure server is up by the time we continue
|
||||
await new Promise((res) => setTimeout(res, 100));
|
||||
|
||||
dc = new Deltachat({
|
||||
url: "ws://localhost:" + CMD_API_SERVER_PORT + "/ws",
|
||||
});
|
||||
dc.on("ALL", (event) => {
|
||||
//console.log("event", event);
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
dc && dc.close();
|
||||
await server_handle.close();
|
||||
});
|
||||
|
||||
it("check email", async () => {
|
||||
const positive_test_cases = [
|
||||
"email@example.com",
|
||||
"36aa165ae3406424e0c61af17700f397cad3fe8ab83d682d0bddf3338a5dd52e@yggmail@yggmail",
|
||||
];
|
||||
const negative_test_cases = ["email@", "example.com", "emai221"];
|
||||
|
||||
expect(
|
||||
await Promise.all(
|
||||
positive_test_cases.map((email) => dc.rpc.checkEmailValidity(email))
|
||||
)
|
||||
).to.not.contain(false);
|
||||
|
||||
expect(
|
||||
await Promise.all(
|
||||
negative_test_cases.map((email) => dc.rpc.checkEmailValidity(email))
|
||||
)
|
||||
).to.not.contain(true);
|
||||
});
|
||||
|
||||
it("system info", async () => {
|
||||
const system_info = await dc.rpc.getSystemInfo();
|
||||
expect(system_info).to.contain.keys([
|
||||
"arch",
|
||||
"num_cpus",
|
||||
"deltachat_core_version",
|
||||
"sqlite_version",
|
||||
]);
|
||||
});
|
||||
|
||||
describe("account managment", () => {
|
||||
it("should create account", async () => {
|
||||
await dc.rpc.addAccount();
|
||||
assert((await dc.rpc.getAllAccountIds()).length === 1);
|
||||
});
|
||||
|
||||
it("should remove the account again", async () => {
|
||||
await dc.rpc.removeAccount((await dc.rpc.getAllAccountIds())[0]);
|
||||
assert((await dc.rpc.getAllAccountIds()).length === 0);
|
||||
});
|
||||
|
||||
it("should create multiple accounts", async () => {
|
||||
await dc.rpc.addAccount();
|
||||
await dc.rpc.addAccount();
|
||||
await dc.rpc.addAccount();
|
||||
await dc.rpc.addAccount();
|
||||
assert((await dc.rpc.getAllAccountIds()).length === 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("contact managment", function () {
|
||||
let acc: number;
|
||||
before(async () => {
|
||||
acc = await dc.rpc.addAccount();
|
||||
});
|
||||
it("block and unblock contact", async function () {
|
||||
const contactId = await dc.rpc.contactsCreateContact(
|
||||
acc,
|
||||
"example@delta.chat",
|
||||
null
|
||||
);
|
||||
expect((await dc.rpc.contactsGetContact(acc, contactId)).is_blocked).to.be
|
||||
.false;
|
||||
await dc.rpc.contactsBlock(acc, contactId);
|
||||
expect((await dc.rpc.contactsGetContact(acc, contactId)).is_blocked).to.be
|
||||
.true;
|
||||
expect(await dc.rpc.contactsGetBlocked(acc)).to.have.length(1);
|
||||
await dc.rpc.contactsUnblock(acc, contactId);
|
||||
expect((await dc.rpc.contactsGetContact(acc, contactId)).is_blocked).to.be
|
||||
.false;
|
||||
expect(await dc.rpc.contactsGetBlocked(acc)).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("configuration", function () {
|
||||
let acc: number;
|
||||
before(async () => {
|
||||
acc = await dc.rpc.addAccount();
|
||||
});
|
||||
|
||||
it("set and retrive", async function () {
|
||||
await dc.rpc.setConfig(acc, "addr", "valid@email");
|
||||
assert((await dc.rpc.getConfig(acc, "addr")) == "valid@email");
|
||||
});
|
||||
it("set invalid key should throw", async function () {
|
||||
await expect(dc.rpc.setConfig(acc, "invalid_key", "some value")).to.be
|
||||
.eventually.rejected;
|
||||
});
|
||||
it("get invalid key should throw", async function () {
|
||||
await expect(dc.rpc.getConfig(acc, "invalid_key")).to.be.eventually
|
||||
.rejected;
|
||||
});
|
||||
it("set and retrive ui.*", async function () {
|
||||
await dc.rpc.setConfig(acc, "ui.chat_bg", "color:red");
|
||||
assert((await dc.rpc.getConfig(acc, "ui.chat_bg")) == "color:red");
|
||||
});
|
||||
it("set and retrive (batch)", async function () {
|
||||
const config = { addr: "valid@email", mail_pw: "1234" };
|
||||
await dc.rpc.batchSetConfig(acc, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(acc, Object.keys(config));
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
it("set and retrive ui.* (batch)", async function () {
|
||||
const config = {
|
||||
"ui.chat_bg": "color:green",
|
||||
"ui.enter_key_sends": "true",
|
||||
};
|
||||
await dc.rpc.batchSetConfig(acc, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(acc, Object.keys(config));
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
it("set and retrive mixed(ui and core) (batch)", async function () {
|
||||
const config = {
|
||||
"ui.chat_bg": "color:yellow",
|
||||
"ui.enter_key_sends": "false",
|
||||
addr: "valid2@email",
|
||||
mail_pw: "123456",
|
||||
};
|
||||
await dc.rpc.batchSetConfig(acc, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(acc, Object.keys(config));
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
});
|
||||
});
|
||||
203
deltachat-jsonrpc/typescript/test/online.ts
Normal file
203
deltachat-jsonrpc/typescript/test/online.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { assert, expect } from "chai";
|
||||
import { Deltachat, DeltachatEvent, EventTypeName } from "../dist/deltachat.js";
|
||||
import {
|
||||
CMD_API_Server_Handle,
|
||||
CMD_API_SERVER_PORT,
|
||||
createTempUser,
|
||||
startCMD_API_Server,
|
||||
} from "./test_base.js";
|
||||
|
||||
describe("online tests", function () {
|
||||
let server_handle: CMD_API_Server_Handle;
|
||||
let dc: Deltachat;
|
||||
let account: { email: string; password: string };
|
||||
let account2: { email: string; password: string };
|
||||
let acc1: number, acc2: number;
|
||||
|
||||
before(async function () {
|
||||
this.timeout(12000)
|
||||
if (!process.env.DCC_NEW_TMP_EMAIL) {
|
||||
if (process.env.COVERAGE && !process.env.COVERAGE_OFFLINE) {
|
||||
console.error(
|
||||
"CAN NOT RUN COVERAGE correctly: Missing DCC_NEW_TMP_EMAIL environment variable!\n\n",
|
||||
"You can set COVERAGE_OFFLINE=1 to circumvent this check and skip the online tests, but those coverage results will be wrong, because some functions can only be tested in the online test"
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip intergration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
server_handle = await startCMD_API_Server(CMD_API_SERVER_PORT);
|
||||
dc = new Deltachat({
|
||||
url: "ws://localhost:" + CMD_API_SERVER_PORT + "/ws",
|
||||
});
|
||||
|
||||
dc.on("ALL", ({ id, contextId }) => {
|
||||
if (id !== "Info") console.log(contextId, id);
|
||||
});
|
||||
|
||||
account = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
|
||||
if (!account || !account.email || !account.password) {
|
||||
console.log(
|
||||
"We didn't got back an account from the api, skip intergration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
|
||||
account2 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
|
||||
if (!account2 || !account2.email || !account2.password) {
|
||||
console.log(
|
||||
"We didn't got back an account2 from the api, skip intergration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
dc && dc.close();
|
||||
server_handle && (await server_handle.close());
|
||||
});
|
||||
|
||||
let are_configured = false;
|
||||
|
||||
it("configure test accounts", async function () {
|
||||
this.timeout(20000);
|
||||
|
||||
acc1 = await dc.rpc.addAccount();
|
||||
await dc.rpc.setConfig(acc1, "addr", account.email);
|
||||
await dc.rpc.setConfig(acc1, "mail_pw", account.password);
|
||||
let configure_promise = dc.rpc.configure(acc1);
|
||||
|
||||
acc2 = await dc.rpc.addAccount();
|
||||
await dc.rpc.batchSetConfig(acc2, {
|
||||
addr: account2.email,
|
||||
mail_pw: account2.password,
|
||||
});
|
||||
|
||||
await Promise.all([configure_promise, dc.rpc.configure(acc2)]);
|
||||
are_configured = true;
|
||||
});
|
||||
|
||||
it("send and recieve text message", async function () {
|
||||
if (!are_configured) {
|
||||
this.skip();
|
||||
}
|
||||
this.timeout(15000);
|
||||
|
||||
const contactId = await dc.rpc.contactsCreateContact(
|
||||
acc1,
|
||||
account2.email,
|
||||
null
|
||||
);
|
||||
const chatId = await dc.rpc.contactsCreateChatByContactId(acc1, contactId);
|
||||
const eventPromise = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", acc2),
|
||||
waitForEvent(dc, "IncomingMsg", acc2),
|
||||
]);
|
||||
|
||||
dc.rpc.miscSendTextMessage(acc1, "Hello", chatId);
|
||||
const { field1: chatIdOnAccountB } = await eventPromise;
|
||||
await dc.rpc.acceptChat(acc2, chatIdOnAccountB);
|
||||
const messageList = await dc.rpc.messageListGetMessageIds(
|
||||
acc2,
|
||||
chatIdOnAccountB,
|
||||
0
|
||||
);
|
||||
|
||||
expect(messageList).have.length(1);
|
||||
const message = await dc.rpc.messageGetMessage(acc2, messageList[0]);
|
||||
expect(message.text).equal("Hello");
|
||||
});
|
||||
|
||||
it("send and recieve text message roundtrip, encrypted on answer onwards", async function () {
|
||||
if (!are_configured) {
|
||||
this.skip();
|
||||
}
|
||||
this.timeout(10000);
|
||||
|
||||
// send message from A to B
|
||||
const contactId = await dc.rpc.contactsCreateContact(
|
||||
acc1,
|
||||
account2.email,
|
||||
null
|
||||
);
|
||||
const chatId = await dc.rpc.contactsCreateChatByContactId(acc1, contactId);
|
||||
const eventPromise = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", acc2),
|
||||
waitForEvent(dc, "IncomingMsg", acc2),
|
||||
]);
|
||||
dc.rpc.miscSendTextMessage(acc1, "Hello2", chatId);
|
||||
// wait for message from A
|
||||
console.log("wait for message from A");
|
||||
|
||||
const event = await eventPromise;
|
||||
const { field1: chatIdOnAccountB } = event;
|
||||
|
||||
await dc.rpc.acceptChat(acc2, chatIdOnAccountB);
|
||||
const messageList = await dc.rpc.messageListGetMessageIds(
|
||||
acc2,
|
||||
chatIdOnAccountB,
|
||||
0
|
||||
);
|
||||
const message = await dc.rpc.messageGetMessage(
|
||||
acc2,
|
||||
messageList.reverse()[0]
|
||||
);
|
||||
expect(message.text).equal("Hello2");
|
||||
// Send message back from B to A
|
||||
const eventPromise2 = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", acc1),
|
||||
waitForEvent(dc, "IncomingMsg", acc1),
|
||||
]);
|
||||
dc.rpc.miscSendTextMessage(acc2, "super secret message", chatId);
|
||||
// Check if answer arives at A and if it is encrypted
|
||||
await eventPromise2;
|
||||
|
||||
const messageId = (
|
||||
await dc.rpc.messageListGetMessageIds(acc1, chatId, 0)
|
||||
).reverse()[0];
|
||||
const message2 = await dc.rpc.messageGetMessage(acc1, messageId);
|
||||
expect(message2.text).equal("super secret message");
|
||||
expect(message2.show_padlock).equal(true);
|
||||
});
|
||||
|
||||
it("get provider info for example.com", async () => {
|
||||
const acc = await dc.rpc.addAccount();
|
||||
const info = await dc.rpc.getProviderInfo(acc, "example.com");
|
||||
expect(info).to.be.not.null;
|
||||
expect(info?.overview_page).to.equal(
|
||||
"https://providers.delta.chat/example-com"
|
||||
);
|
||||
expect(info?.status).to.equal(3);
|
||||
});
|
||||
|
||||
it("get provider info - domain and email should give same result", async () => {
|
||||
const acc = await dc.rpc.addAccount();
|
||||
const info_domain = await dc.rpc.getProviderInfo(acc, "example.com");
|
||||
const info_email = await dc.rpc.getProviderInfo(acc, "hi@example.com");
|
||||
expect(info_email).to.deep.equal(info_domain);
|
||||
});
|
||||
});
|
||||
|
||||
type event_data = {
|
||||
contextId: number;
|
||||
id: EventTypeName;
|
||||
[key: string]: any;
|
||||
};
|
||||
async function waitForEvent(
|
||||
dc: Deltachat,
|
||||
event: EventTypeName,
|
||||
accountId: number
|
||||
): Promise<event_data> {
|
||||
return new Promise((res, rej) => {
|
||||
const callback = (ev: DeltachatEvent) => {
|
||||
if (ev.contextId == accountId) {
|
||||
dc.off(event, callback);
|
||||
res(ev);
|
||||
}
|
||||
};
|
||||
dc.on(event, callback);
|
||||
});
|
||||
}
|
||||
95
deltachat-jsonrpc/typescript/test/test_base.ts
Normal file
95
deltachat-jsonrpc/typescript/test/test_base.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
import { mkdtemp, rm } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import { spawn, exec } from "child_process";
|
||||
import { unwrapPromise } from "./ts_helpers.js";
|
||||
import fetch from "node-fetch";
|
||||
/* port is not configurable yet */
|
||||
|
||||
|
||||
function getTargetDir(): Promise<string> {
|
||||
return new Promise((res, rej) => {
|
||||
exec(
|
||||
"cargo metadata --no-deps --format-version 1",
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.log("error", error);
|
||||
rej(error);
|
||||
} else {
|
||||
try {
|
||||
const json = JSON.parse(stdout);
|
||||
res(json.target_directory);
|
||||
} catch (error) {
|
||||
console.log("json error", error);
|
||||
rej(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const CMD_API_SERVER_PORT = 20808;
|
||||
export async function startCMD_API_Server(port: typeof CMD_API_SERVER_PORT) {
|
||||
const tmp_dir = await mkdtemp(join(tmpdir(), "test_prefix"));
|
||||
|
||||
const path_of_server = join(await getTargetDir(), "debug/webserver");
|
||||
console.log(path_of_server);
|
||||
|
||||
if (!existsSync(path_of_server)) {
|
||||
throw new Error(
|
||||
"server executable does not exist, you need to build it first" +
|
||||
"\nserver executable not found at " +
|
||||
path_of_server
|
||||
);
|
||||
}
|
||||
|
||||
const server = spawn(path_of_server, {
|
||||
cwd: tmp_dir,
|
||||
env: {
|
||||
RUST_LOG: "info",
|
||||
},
|
||||
});
|
||||
let should_close = false;
|
||||
|
||||
server.on("exit", () => {
|
||||
if (should_close) {
|
||||
return;
|
||||
}
|
||||
throw new Error("Server quit");
|
||||
});
|
||||
|
||||
server.stderr.pipe(process.stderr);
|
||||
|
||||
//server.stdout.pipe(process.stdout)
|
||||
|
||||
return {
|
||||
close: async () => {
|
||||
should_close = true;
|
||||
if (!server.kill(9)) {
|
||||
console.log("server termination failed");
|
||||
}
|
||||
await rm(tmp_dir, { recursive: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type CMD_API_Server_Handle = unwrapPromise<
|
||||
ReturnType<typeof startCMD_API_Server>
|
||||
>;
|
||||
|
||||
export async function createTempUser(url: string) {
|
||||
async function postData(url = "") {
|
||||
// Default options are marked with *
|
||||
const response = await fetch(url, {
|
||||
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
||||
headers: {
|
||||
"cache-control": "no-cache",
|
||||
},
|
||||
});
|
||||
return response.json(); // parses JSON response into native JavaScript objects
|
||||
}
|
||||
|
||||
return await postData(url);
|
||||
}
|
||||
1
deltachat-jsonrpc/typescript/test/ts_helpers.ts
Normal file
1
deltachat-jsonrpc/typescript/test/ts_helpers.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type unwrapPromise<T> = T extends Promise<infer U> ? U : never;
|
||||
17
deltachat-jsonrpc/typescript/test/tsconfig.json
Normal file
17
deltachat-jsonrpc/typescript/test/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "../test_dist",
|
||||
"target": "ES2020",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"declaration": false,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"compileOnSave": true
|
||||
}
|
||||
20
deltachat-jsonrpc/typescript/tsconfig.json
Normal file
20
deltachat-jsonrpc/typescript/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"strictNullChecks": true,
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"lib": ["ES2017", "dom"],
|
||||
"target": "ES2017",
|
||||
"module": "es2015",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"compileOnSave": false
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
extern crate dirs;
|
||||
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use async_std::path::Path;
|
||||
use deltachat::chat::{
|
||||
self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration, ProtectionStatus,
|
||||
};
|
||||
@@ -23,7 +22,8 @@ use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::{config, provider};
|
||||
use tokio::fs;
|
||||
use std::fs;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
/// Reset database tables.
|
||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||
@@ -135,13 +135,17 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
} else {
|
||||
/* import a directory */
|
||||
let dir_name = std::path::Path::new(&real_spec);
|
||||
let dir = fs::read_dir(dir_name).await;
|
||||
let dir = std::fs::read_dir(dir_name);
|
||||
if dir.is_err() {
|
||||
error!(context, "Import: Cannot open directory \"{}\".", &real_spec,);
|
||||
return false;
|
||||
} else {
|
||||
let mut dir = dir.unwrap();
|
||||
while let Ok(Some(entry)) = dir.next_entry().await {
|
||||
let dir = dir.unwrap();
|
||||
for entry in dir {
|
||||
if entry.is_err() {
|
||||
break;
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
@@ -488,7 +492,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let setup_code = create_setup_code(&context);
|
||||
let file_name = blobdir.join("autocrypt-setup-message.html");
|
||||
let file_content = render_setup_file(&context, &setup_code).await?;
|
||||
fs::write(&file_name, file_content).await?;
|
||||
async_std::fs::write(&file_name, file_content).await?;
|
||||
println!(
|
||||
"Setup message written to: {}\nSetup code: {}",
|
||||
file_name.display(),
|
||||
@@ -528,7 +532,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.join("connectivity.html");
|
||||
match context.get_connectivity_html().await {
|
||||
Ok(html) => {
|
||||
fs::write(&file, html).await?;
|
||||
fs::write(&file, html)?;
|
||||
println!("Report written to: {:#?}", file);
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -888,7 +892,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "No html-file given.");
|
||||
let path: &Path = arg1.as_ref();
|
||||
let html = &*fs::read(&path).await?;
|
||||
let html = &*fs::read(&path)?;
|
||||
let html = String::from_utf8_lossy(html);
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
@@ -1075,7 +1079,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.unwrap_or_default()
|
||||
.join(format!("msg-{}.html", id.to_u32()));
|
||||
let html = id.get_html(&context).await?.unwrap_or_default();
|
||||
fs::write(&file, html).await?;
|
||||
fs::write(&file, html)?;
|
||||
println!("HTML written to: {:#?}", file);
|
||||
}
|
||||
"listfresh" => {
|
||||
|
||||
@@ -9,16 +9,15 @@ extern crate deltachat;
|
||||
|
||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use ansi_term::Color;
|
||||
use anyhow::{bail, Error};
|
||||
use async_std::path::Path;
|
||||
use deltachat::chat::ChatId;
|
||||
use deltachat::config;
|
||||
use deltachat::context::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::{EventType, Events};
|
||||
use log::{error, info, warn};
|
||||
@@ -31,11 +30,11 @@ use rustyline::validate::Validator;
|
||||
use rustyline::{
|
||||
Cmd, CompletionType, Config, Context as RustyContext, EditMode, Editor, Helper, KeyEvent,
|
||||
};
|
||||
use tokio::fs;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
mod cmdline;
|
||||
use self::cmdline::*;
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use std::fs;
|
||||
|
||||
/// Event Handler
|
||||
fn receive_event(event: EventType) {
|
||||
@@ -299,10 +298,10 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
println!("Error: Bad arguments, expected [db-name].");
|
||||
bail!("No db-name specified");
|
||||
}
|
||||
let context = Context::new(Path::new(&args[1]), 0, Events::new()).await?;
|
||||
let context = Context::new(Path::new(&args[1]).to_path_buf(), 0, Events::new()).await?;
|
||||
|
||||
let events = context.get_event_emitter();
|
||||
tokio::task::spawn(async move {
|
||||
async_std::task::spawn(async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
receive_event(event.typ);
|
||||
}
|
||||
@@ -317,9 +316,8 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
.output_stream(OutputStreamType::Stdout)
|
||||
.build();
|
||||
let mut selected_chat = ChatId::default();
|
||||
|
||||
let ctx = context.clone();
|
||||
let input_loop = tokio::task::spawn_blocking(move || {
|
||||
let (reader_s, reader_r) = async_std::channel::bounded(100);
|
||||
let input_loop = async_std::task::spawn_blocking(move || {
|
||||
let h = DcHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
highlighter: MatchingBracketHighlighter::new(),
|
||||
@@ -341,30 +339,16 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
Ok(line) => {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str());
|
||||
let contine = Handle::current().block_on(async {
|
||||
match handle_cmd(line.trim(), ctx.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => true,
|
||||
Ok(ExitResult::Exit) => {
|
||||
println!("Exiting ...");
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err);
|
||||
true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !contine {
|
||||
break;
|
||||
}
|
||||
async_std::task::block_on(reader_s.send(line)).unwrap();
|
||||
}
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
println!("Exiting...");
|
||||
drop(reader_s);
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err);
|
||||
drop(reader_s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -375,8 +359,15 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
Ok::<_, Error>(())
|
||||
});
|
||||
|
||||
while let Ok(line) = reader_r.recv().await {
|
||||
match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => {}
|
||||
Ok(ExitResult::Exit) => break,
|
||||
Err(err) => println!("Error: {}", err),
|
||||
}
|
||||
}
|
||||
context.stop_io().await;
|
||||
input_loop.await??;
|
||||
input_loop.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -446,7 +437,7 @@ async fn handle_cmd(
|
||||
let file = dirs::home_dir().unwrap_or_default().join("qr.svg");
|
||||
match get_securejoin_qr_svg(&ctx, group).await {
|
||||
Ok(svg) => {
|
||||
fs::write(&file, svg).await?;
|
||||
fs::write(&file, svg)?;
|
||||
println!("QR code svg written to: {:#?}", file);
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -467,12 +458,11 @@ async fn handle_cmd(
|
||||
Ok(ExitResult::Continue)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
fn main() -> Result<(), Error> {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
let args = std::env::args().collect();
|
||||
start(args).await?;
|
||||
async_std::task::block_on(async move { start(args).await })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,21 +29,21 @@ fn cb(event: EventType) {
|
||||
}
|
||||
|
||||
/// Run with `RUST_LOG=simple=info cargo run --release --example simple --features repl -- email pw`.
|
||||
#[tokio::main]
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::try_init_timed().ok();
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
log::info!("creating database {:?}", dbfile);
|
||||
let ctx = Context::new(&dbfile, 0, Events::new())
|
||||
let ctx = Context::new(dbfile.into(), 0, Events::new())
|
||||
.await
|
||||
.expect("Failed to create context");
|
||||
let info = ctx.get_info().await;
|
||||
log::info!("info: {:#?}", info);
|
||||
|
||||
let events = ctx.get_event_emitter();
|
||||
let events_spawn = tokio::task::spawn(async move {
|
||||
let events_spawn = async_std::task::spawn(async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
cb(event.typ);
|
||||
}
|
||||
@@ -80,7 +80,7 @@ async fn main() {
|
||||
}
|
||||
|
||||
// wait for the message to be sent out
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
|
||||
log::info!("fetching chats..");
|
||||
let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap();
|
||||
@@ -96,5 +96,5 @@ async fn main() {
|
||||
ctx.stop_io().await;
|
||||
log::info!("closing");
|
||||
drop(ctx);
|
||||
events_spawn.await.unwrap();
|
||||
events_spawn.await;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ interface NativeAccount {}
|
||||
export class AccountManager extends EventEmitter {
|
||||
dcn_accounts: NativeAccount
|
||||
accountDir: string
|
||||
jsonRpcStarted = false
|
||||
|
||||
constructor(cwd: string, os = 'deltachat-node') {
|
||||
debug('DeltaChat constructor')
|
||||
super()
|
||||
debug('DeltaChat constructor')
|
||||
|
||||
this.accountDir = cwd
|
||||
this.dcn_accounts = binding.dcn_accounts_new(os, this.accountDir)
|
||||
@@ -114,6 +115,31 @@ export class AccountManager extends EventEmitter {
|
||||
debug('Started event handler')
|
||||
}
|
||||
|
||||
startJSONRPCHandler(callback: ((response: string) => void) | null) {
|
||||
if (this.dcn_accounts === null) {
|
||||
throw new Error('dcn_account is null')
|
||||
}
|
||||
if (!callback) {
|
||||
throw new Error('no callback set')
|
||||
}
|
||||
if (this.jsonRpcStarted) {
|
||||
throw new Error('jsonrpc was started already')
|
||||
}
|
||||
|
||||
binding.dcn_accounts_start_jsonrpc(this.dcn_accounts, callback.bind(this))
|
||||
debug('Started jsonrpc handler')
|
||||
this.jsonRpcStarted = true
|
||||
}
|
||||
|
||||
jsonRPCRequest(message: string) {
|
||||
if (!this.jsonRpcStarted) {
|
||||
throw new Error(
|
||||
'jsonrpc is not active, start it with startJSONRPCHandler first'
|
||||
)
|
||||
}
|
||||
binding.dcn_json_rpc_request(this.dcn_accounts, message)
|
||||
}
|
||||
|
||||
startIO() {
|
||||
binding.dcn_accounts_start_io(this.dcn_accounts)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ const buildArgs = [
|
||||
'build',
|
||||
'--release',
|
||||
'--features',
|
||||
'vendored',
|
||||
'vendored,jsonrpc',
|
||||
'-p',
|
||||
'deltachat_ffi'
|
||||
]
|
||||
|
||||
@@ -34,6 +34,9 @@ typedef struct dcn_accounts_t {
|
||||
dc_accounts_t* dc_accounts;
|
||||
napi_threadsafe_function threadsafe_event_handler;
|
||||
uv_thread_t event_handler_thread;
|
||||
napi_threadsafe_function threadsafe_jsonrpc_handler;
|
||||
uv_thread_t jsonrpc_thread;
|
||||
dc_jsonrpc_instance_t* jsonrpc_instance;
|
||||
int gc;
|
||||
} dcn_accounts_t;
|
||||
|
||||
@@ -2932,6 +2935,11 @@ NAPI_METHOD(dcn_accounts_unref) {
|
||||
uv_thread_join(&dcn_accounts->event_handler_thread);
|
||||
dcn_accounts->event_handler_thread = 0;
|
||||
}
|
||||
if (dcn_accounts->jsonrpc_instance) {
|
||||
dc_jsonrpc_request(dcn_accounts->jsonrpc_instance, "{}");
|
||||
uv_thread_join(&dcn_accounts->jsonrpc_thread);
|
||||
dcn_accounts->jsonrpc_instance = NULL;
|
||||
}
|
||||
dc_accounts_unref(dcn_accounts->dc_accounts);
|
||||
dcn_accounts->dc_accounts = NULL;
|
||||
|
||||
@@ -3090,8 +3098,6 @@ static void accounts_event_handler_thread_func(void* arg)
|
||||
{
|
||||
dcn_accounts_t* dcn_accounts = (dcn_accounts_t*)arg;
|
||||
|
||||
|
||||
|
||||
TRACE("event_handler_thread_func starting");
|
||||
|
||||
dc_accounts_event_emitter_t * dc_accounts_event_emitter = dc_accounts_get_event_emitter(dcn_accounts->dc_accounts);
|
||||
@@ -3242,6 +3248,125 @@ NAPI_METHOD(dcn_accounts_start_event_handler) {
|
||||
NAPI_RETURN_UNDEFINED();
|
||||
}
|
||||
|
||||
// JSON RPC
|
||||
|
||||
static void accounts_jsonrpc_thread_func(void* arg)
|
||||
{
|
||||
dcn_accounts_t* dcn_accounts = (dcn_accounts_t*)arg;
|
||||
TRACE("accounts_jsonrpc_thread_func starting");
|
||||
char* response;
|
||||
while (true) {
|
||||
response = dc_jsonrpc_next_response(dcn_accounts->jsonrpc_instance);
|
||||
if (response == NULL) {
|
||||
// done or broken
|
||||
break;
|
||||
}
|
||||
|
||||
if (!dcn_accounts->threadsafe_jsonrpc_handler) {
|
||||
TRACE("threadsafe_jsonrpc_handler not set, bailing");
|
||||
break;
|
||||
}
|
||||
// Don't process events if we're being garbage collected!
|
||||
if (dcn_accounts->gc == 1) {
|
||||
TRACE("dc_accounts has been destroyed, bailing");
|
||||
break;
|
||||
}
|
||||
|
||||
napi_status status = napi_call_threadsafe_function(dcn_accounts->threadsafe_jsonrpc_handler, response, napi_tsfn_blocking);
|
||||
|
||||
if (status == napi_closing) {
|
||||
TRACE("JS function got released, bailing");
|
||||
break;
|
||||
}
|
||||
}
|
||||
dc_jsonrpc_unref(dcn_accounts->jsonrpc_instance);
|
||||
dcn_accounts->jsonrpc_instance = NULL;
|
||||
TRACE("accounts_jsonrpc_thread_func ended");
|
||||
napi_release_threadsafe_function(dcn_accounts->threadsafe_jsonrpc_handler, napi_tsfn_release);
|
||||
}
|
||||
|
||||
static void call_accounts_js_jsonrpc_handler(napi_env env, napi_value js_callback, void* _context, void* data)
|
||||
{
|
||||
char* response = (char*)data;
|
||||
napi_value global;
|
||||
napi_status status = napi_get_global(env, &global);
|
||||
if (status != napi_ok) {
|
||||
napi_throw_error(env, NULL, "Unable to get global");
|
||||
}
|
||||
|
||||
napi_value argv[1];
|
||||
if (response != 0) {
|
||||
status = napi_create_string_utf8(env, response, NAPI_AUTO_LENGTH, &argv[0]);
|
||||
} else {
|
||||
status = napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &argv[0]);
|
||||
}
|
||||
if (status != napi_ok) {
|
||||
napi_throw_error(env, NULL, "Unable to create argv for js jsonrpc_handler arguments");
|
||||
}
|
||||
free(response);
|
||||
|
||||
TRACE("calling back into js");
|
||||
napi_value result;
|
||||
status = napi_call_function(
|
||||
env,
|
||||
global,
|
||||
js_callback,
|
||||
1,
|
||||
argv,
|
||||
&result);
|
||||
if (status != napi_ok) {
|
||||
TRACE("Unable to call jsonrpc_handler callback2");
|
||||
const napi_extended_error_info* error_result;
|
||||
NAPI_STATUS_THROWS(napi_get_last_error_info(env, &error_result));
|
||||
}
|
||||
}
|
||||
|
||||
NAPI_METHOD(dcn_accounts_start_jsonrpc) {
|
||||
NAPI_ARGV(2);
|
||||
NAPI_DCN_ACCOUNTS();
|
||||
napi_value callback = argv[1];
|
||||
|
||||
TRACE("calling..");
|
||||
napi_value async_resource_name;
|
||||
NAPI_STATUS_THROWS(napi_create_string_utf8(env, "dc_accounts_jsonrpc_callback", NAPI_AUTO_LENGTH, &async_resource_name));
|
||||
|
||||
TRACE("creating threadsafe function..");
|
||||
|
||||
NAPI_STATUS_THROWS(napi_create_threadsafe_function(
|
||||
env,
|
||||
callback,
|
||||
0,
|
||||
async_resource_name,
|
||||
1,
|
||||
1,
|
||||
NULL,
|
||||
NULL,
|
||||
dcn_accounts,
|
||||
call_accounts_js_jsonrpc_handler,
|
||||
&dcn_accounts->threadsafe_jsonrpc_handler));
|
||||
TRACE("done");
|
||||
|
||||
dcn_accounts->gc = 0;
|
||||
dcn_accounts->jsonrpc_instance = dc_jsonrpc_init(dcn_accounts->dc_accounts);
|
||||
|
||||
TRACE("creating uv thread..");
|
||||
uv_thread_create(&dcn_accounts->jsonrpc_thread, accounts_jsonrpc_thread_func, dcn_accounts);
|
||||
|
||||
NAPI_RETURN_UNDEFINED();
|
||||
}
|
||||
|
||||
NAPI_METHOD(dcn_json_rpc_request) {
|
||||
NAPI_ARGV(2);
|
||||
NAPI_DCN_ACCOUNTS();
|
||||
if (!dcn_accounts->jsonrpc_instance) {
|
||||
const char* msg = "dcn_accounts->jsonrpc_instance is null, have you called dcn_accounts_start_jsonrpc()?";
|
||||
NAPI_STATUS_THROWS(napi_throw_type_error(env, NULL, msg));
|
||||
}
|
||||
NAPI_ARGV_UTF8_MALLOC(request, 1);
|
||||
dc_jsonrpc_request(dcn_accounts->jsonrpc_instance, request);
|
||||
free(request);
|
||||
}
|
||||
|
||||
|
||||
NAPI_INIT() {
|
||||
/**
|
||||
@@ -3512,4 +3637,9 @@ NAPI_INIT() {
|
||||
NAPI_EXPORT_FUNCTION(dcn_send_webxdc_status_update);
|
||||
NAPI_EXPORT_FUNCTION(dcn_get_webxdc_status_updates);
|
||||
NAPI_EXPORT_FUNCTION(dcn_msg_get_webxdc_blob);
|
||||
|
||||
|
||||
/** jsonrpc **/
|
||||
NAPI_EXPORT_FUNCTION(dcn_accounts_start_jsonrpc);
|
||||
NAPI_EXPORT_FUNCTION(dcn_json_rpc_request);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
dcn_accounts_t* dcn_accounts; \
|
||||
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&dcn_accounts)); \
|
||||
if (!dcn_accounts) { \
|
||||
const char* msg = "Provided dnc_acounts is null"; \
|
||||
const char* msg = "Provided dcn_acounts is null"; \
|
||||
NAPI_STATUS_THROWS(napi_throw_type_error(env, NULL, msg)); \
|
||||
} \
|
||||
if (!dcn_accounts->dc_accounts) { \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import DeltaChat, { Message } from '../dist'
|
||||
import binding from '../binding'
|
||||
|
||||
import { strictEqual } from 'assert'
|
||||
import { deepEqual, deepStrictEqual, strictEqual } from 'assert'
|
||||
import chai, { expect } from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import { EventId2EventName, C } from '../dist/constants'
|
||||
@@ -84,6 +84,95 @@ describe('static tests', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('JSON RPC', function () {
|
||||
it('smoketest', async function () {
|
||||
const { dc } = DeltaChat.newTemporary()
|
||||
let promise_resolve
|
||||
const promise = new Promise((res, _rej) => {
|
||||
promise_resolve = res
|
||||
})
|
||||
dc.startJSONRPCHandler(promise_resolve)
|
||||
dc.jsonRPCRequest(
|
||||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'get_all_account_ids',
|
||||
params: [],
|
||||
id: 2,
|
||||
})
|
||||
)
|
||||
deepStrictEqual(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
id: 2,
|
||||
result: [1],
|
||||
},
|
||||
JSON.parse(await promise)
|
||||
)
|
||||
dc.close()
|
||||
})
|
||||
|
||||
it('basic test', async function () {
|
||||
const { dc } = DeltaChat.newTemporary()
|
||||
|
||||
const promises = {}
|
||||
dc.startJSONRPCHandler((msg) => {
|
||||
const response = JSON.parse(msg)
|
||||
promises[response.id](response)
|
||||
delete promises[response.id]
|
||||
})
|
||||
const call = (request) => {
|
||||
dc.jsonRPCRequest(JSON.stringify(request))
|
||||
return new Promise((res, _rej) => {
|
||||
promises[request.id] = res
|
||||
})
|
||||
}
|
||||
|
||||
deepStrictEqual(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
id: 2,
|
||||
result: [1],
|
||||
},
|
||||
await call({
|
||||
jsonrpc: '2.0',
|
||||
method: 'get_all_account_ids',
|
||||
params: [],
|
||||
id: 2,
|
||||
})
|
||||
)
|
||||
|
||||
deepStrictEqual(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
id: 3,
|
||||
result: 2,
|
||||
},
|
||||
await call({
|
||||
jsonrpc: '2.0',
|
||||
method: 'add_account',
|
||||
params: [],
|
||||
id: 3,
|
||||
})
|
||||
)
|
||||
|
||||
deepStrictEqual(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
id: 4,
|
||||
result: [1, 2],
|
||||
},
|
||||
await call({
|
||||
jsonrpc: '2.0',
|
||||
method: 'get_all_account_ids',
|
||||
params: [],
|
||||
id: 4,
|
||||
})
|
||||
)
|
||||
|
||||
dc.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Basic offline Tests', function () {
|
||||
it('opens a context', async function () {
|
||||
const { dc, context } = DeltaChat.newTemporary()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@deltachat/jsonrpc-client": "file:deltachat-jsonrpc/typescript",
|
||||
"debug": "^4.1.1",
|
||||
"napi-macros": "^2.0.0",
|
||||
"node-gyp-build": "^4.1.0"
|
||||
@@ -60,5 +61,5 @@
|
||||
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail"
|
||||
},
|
||||
"types": "node/dist/index.d.ts",
|
||||
"version": "1.87.0"
|
||||
}
|
||||
"version": "1.86.0"
|
||||
}
|
||||
|
||||
@@ -84,9 +84,8 @@ jobs:
|
||||
- name: coredeps-image
|
||||
path: image
|
||||
params:
|
||||
CONTEXT: deltachat-core-rust/scripts/coredeps
|
||||
CONTEXT: deltachat-core-rust/scripts/docker-coredeps
|
||||
UNPACK_ROOTFS: "true"
|
||||
BUILD_ARG_BASEIMAGE: quay.io/pypa/manylinux2014_x86_64
|
||||
platform: linux
|
||||
caches:
|
||||
- path: cache
|
||||
@@ -184,9 +183,8 @@ jobs:
|
||||
- name: coredeps-image
|
||||
path: image
|
||||
params:
|
||||
CONTEXT: deltachat-core-rust/scripts/coredeps
|
||||
CONTEXT: deltachat-core-rust/scripts/docker-coredeps-arm64
|
||||
UNPACK_ROOTFS: "true"
|
||||
BUILD_ARG_BASEIMAGE: quay.io/pypa/manylinux2014_aarch64
|
||||
platform: linux
|
||||
caches:
|
||||
- path: cache
|
||||
@@ -232,73 +230,3 @@ jobs:
|
||||
devpi use https://m.devpi.net/dc/master
|
||||
devpi login ((devpi.login)) --password ((devpi.password))
|
||||
devpi upload py-wheels/*manylinux201*
|
||||
|
||||
- name: python-musl-x86_64
|
||||
plan:
|
||||
- get: deltachat-core-rust
|
||||
- get: deltachat-core-rust-release
|
||||
trigger: true
|
||||
|
||||
# Build manylinux image with additional dependencies
|
||||
- task: build-coredeps
|
||||
privileged: true
|
||||
config:
|
||||
inputs:
|
||||
# Building the latest, not tagged coredeps
|
||||
- name: deltachat-core-rust
|
||||
image_resource:
|
||||
source:
|
||||
repository: concourse/oci-build-task
|
||||
type: registry-image
|
||||
outputs:
|
||||
- name: coredeps-image
|
||||
path: image
|
||||
params:
|
||||
CONTEXT: deltachat-core-rust/scripts/coredeps
|
||||
UNPACK_ROOTFS: "true"
|
||||
BUILD_ARG_BASEIMAGE: quay.io/pypa/musllinux_1_1_x86_64
|
||||
platform: linux
|
||||
caches:
|
||||
- path: cache
|
||||
run:
|
||||
path: build
|
||||
|
||||
# Use built image to build python wheels
|
||||
- task: build-wheels
|
||||
image: coredeps-image
|
||||
config:
|
||||
inputs:
|
||||
- name: deltachat-core-rust-release
|
||||
path: .
|
||||
outputs:
|
||||
- name: py-wheels
|
||||
path: ./python/.docker-tox/wheelhouse/
|
||||
platform: linux
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -exc
|
||||
- |
|
||||
scripts/run_all.sh
|
||||
|
||||
# Upload musl x86_64 wheels
|
||||
- task: upload-wheels
|
||||
config:
|
||||
inputs:
|
||||
- name: py-wheels
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: debian
|
||||
platform: linux
|
||||
run:
|
||||
path: sh
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends python3-pip python3-setuptools
|
||||
pip3 install devpi
|
||||
devpi use https://m.devpi.net/dc/master
|
||||
devpi login ((devpi.login)) --password ((devpi.password))
|
||||
devpi upload py-wheels/*musllinux_1_1_x86_64*
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
ARG BASEIMAGE=quay.io/pypa/manylinux2014_x86_64
|
||||
#ARG BASEIMAGE=quay.io/pypa/musllinux_1_1_x86_64
|
||||
#ARG BASEIMAGE=quay.io/pypa/manylinux2014_aarch64
|
||||
|
||||
FROM $BASEIMAGE
|
||||
RUN pipx install tox
|
||||
COPY install-rust.sh /scripts/
|
||||
RUN /scripts/install-rust.sh
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Install Rust
|
||||
#
|
||||
# Path from https://forge.rust-lang.org/infra/other-installation-methods.html
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
RUST_VERSION=1.61.0
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
test -f "/lib/libc.musl-$ARCH.so.1" && LIBC=musl || LIBC=gnu
|
||||
|
||||
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$ARCH-unknown-linux-$LIBC.tar.gz" | tar xz
|
||||
cd "rust-${RUST_VERSION}-$ARCH-unknown-linux-$LIBC"
|
||||
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$ARCH-unknown-linux-$LIBC"
|
||||
rustc --version
|
||||
cd ..
|
||||
rm -fr "rust-${RUST_VERSION}-$ARCH-unknown-linux-$LIBC"
|
||||
6
scripts/docker-coredeps-arm64/Dockerfile
Normal file
6
scripts/docker-coredeps-arm64/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM quay.io/pypa/manylinux2014_aarch64
|
||||
RUN pipx install tox
|
||||
|
||||
# Install Rust
|
||||
ADD deps/build_rust.sh /builder/build_rust.sh
|
||||
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1
|
||||
18
scripts/docker-coredeps-arm64/deps/build_rust.sh
Executable file
18
scripts/docker-coredeps-arm64/deps/build_rust.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -x
|
||||
|
||||
# Install Rust
|
||||
#
|
||||
# Path from https://forge.rust-lang.org/infra/other-installation-methods.html
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
RUST_VERSION=1.61.0
|
||||
|
||||
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
|
||||
rustc --version
|
||||
cd ..
|
||||
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
6
scripts/docker-coredeps/Dockerfile
Normal file
6
scripts/docker-coredeps/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM quay.io/pypa/manylinux2014_x86_64
|
||||
RUN pipx install tox
|
||||
|
||||
# Install Rust
|
||||
ADD deps/build_rust.sh /builder/build_rust.sh
|
||||
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1
|
||||
18
scripts/docker-coredeps/deps/build_rust.sh
Executable file
18
scripts/docker-coredeps/deps/build_rust.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -x
|
||||
|
||||
# Install Rust
|
||||
#
|
||||
# Path from https://forge.rust-lang.org/infra/other-installation-methods.html
|
||||
#
|
||||
# Avoid using rustup here as it depends on reading /proc/self/exe and
|
||||
# has problems running under QEMU.
|
||||
RUST_VERSION=1.61.0
|
||||
|
||||
curl "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz" | tar xz
|
||||
cd "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
./install.sh --prefix=/usr --components=rustc,cargo,"rust-std-$(uname -m)-unknown-linux-gnu"
|
||||
rustc --version
|
||||
cd ..
|
||||
rm -fr "rust-${RUST_VERSION}-$(uname -m)-unknown-linux-gnu"
|
||||
@@ -63,7 +63,7 @@ def main():
|
||||
parser = ArgumentParser(prog="set_core_version")
|
||||
parser.add_argument("newversion")
|
||||
|
||||
toml_list = ["Cargo.toml", "deltachat-ffi/Cargo.toml"]
|
||||
toml_list = ["Cargo.toml", "deltachat-ffi/Cargo.toml", "deltachat-jsonrpc/Cargo.toml"]
|
||||
try:
|
||||
opts = parser.parse_args()
|
||||
except SystemExit:
|
||||
|
||||
109
src/accounts.rs
109
src/accounts.rs
@@ -1,12 +1,13 @@
|
||||
//! # Account manager module.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use async_std::fs;
|
||||
use async_std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::events::{Event, EventEmitter, EventType, Events};
|
||||
@@ -25,7 +26,7 @@ pub struct Accounts {
|
||||
impl Accounts {
|
||||
/// Loads or creates an accounts folder at the given `dir`.
|
||||
pub async fn new(dir: PathBuf) -> Result<Self> {
|
||||
if !dir.exists() {
|
||||
if !dir.exists().await {
|
||||
Accounts::create(&dir).await?;
|
||||
}
|
||||
|
||||
@@ -46,10 +47,14 @@ impl Accounts {
|
||||
/// Opens an existing accounts structure. Will error if the folder doesn't exist,
|
||||
/// no account exists and no config exists.
|
||||
pub async fn open(dir: PathBuf) -> Result<Self> {
|
||||
ensure!(dir.exists(), "directory does not exist");
|
||||
ensure!(dir.exists().await, "directory does not exist");
|
||||
|
||||
let config_file = dir.join(CONFIG_NAME);
|
||||
ensure!(config_file.exists(), "{:?} does not exist", config_file);
|
||||
ensure!(
|
||||
config_file.exists().await,
|
||||
"{:?} does not exist",
|
||||
config_file
|
||||
);
|
||||
|
||||
let config = Config::from_file(config_file)
|
||||
.await
|
||||
@@ -101,7 +106,7 @@ impl Accounts {
|
||||
let account_config = self.config.new_account(&self.dir).await?;
|
||||
|
||||
let ctx = Context::new(
|
||||
&account_config.dbfile(),
|
||||
account_config.dbfile().into(),
|
||||
account_config.id,
|
||||
self.events.clone(),
|
||||
)
|
||||
@@ -116,7 +121,7 @@ impl Accounts {
|
||||
let account_config = self.config.new_account(&self.dir).await?;
|
||||
|
||||
let ctx = Context::new_closed(
|
||||
&account_config.dbfile(),
|
||||
account_config.dbfile().into(),
|
||||
account_config.id,
|
||||
self.events.clone(),
|
||||
)
|
||||
@@ -143,7 +148,7 @@ impl Accounts {
|
||||
loop {
|
||||
counter += 1;
|
||||
|
||||
if let Err(err) = fs::remove_dir_all(&cfg.dir)
|
||||
if let Err(err) = fs::remove_dir_all(async_std::path::PathBuf::from(&cfg.dir))
|
||||
.await
|
||||
.context("failed to remove account data")
|
||||
{
|
||||
@@ -152,7 +157,7 @@ impl Accounts {
|
||||
}
|
||||
|
||||
// Wait 1 second and try again.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -168,8 +173,16 @@ impl Accounts {
|
||||
let blobdir = Context::derive_blobdir(&dbfile);
|
||||
let walfile = Context::derive_walfile(&dbfile);
|
||||
|
||||
ensure!(dbfile.exists(), "no database found: {}", dbfile.display());
|
||||
ensure!(blobdir.exists(), "no blobdir found: {}", blobdir.display());
|
||||
ensure!(
|
||||
dbfile.exists().await,
|
||||
"no database found: {}",
|
||||
dbfile.display()
|
||||
);
|
||||
ensure!(
|
||||
blobdir.exists().await,
|
||||
"no blobdir found: {}",
|
||||
blobdir.display()
|
||||
);
|
||||
|
||||
let old_id = self.config.get_selected_account().await;
|
||||
|
||||
@@ -180,7 +193,7 @@ impl Accounts {
|
||||
.await
|
||||
.context("failed to create new account")?;
|
||||
|
||||
let new_dbfile = account_config.dbfile();
|
||||
let new_dbfile = account_config.dbfile().into();
|
||||
let new_blobdir = Context::derive_blobdir(&new_dbfile);
|
||||
let new_walfile = Context::derive_walfile(&new_dbfile);
|
||||
|
||||
@@ -194,7 +207,7 @@ impl Accounts {
|
||||
fs::rename(&blobdir, &new_blobdir)
|
||||
.await
|
||||
.context("failed to rename blobdir")?;
|
||||
if walfile.exists() {
|
||||
if walfile.exists().await {
|
||||
fs::rename(&walfile, &new_walfile)
|
||||
.await
|
||||
.context("failed to rename walfile")?;
|
||||
@@ -204,13 +217,13 @@ impl Accounts {
|
||||
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let ctx = Context::new(&new_dbfile, account_config.id, self.events.clone()).await?;
|
||||
let ctx = Context::new(new_dbfile, account_config.id, self.events.clone()).await?;
|
||||
self.accounts.insert(account_config.id, ctx);
|
||||
Ok(account_config.id)
|
||||
}
|
||||
Err(err) => {
|
||||
// remove temp account
|
||||
fs::remove_dir_all(std::path::PathBuf::from(&account_config.dir))
|
||||
fs::remove_dir_all(async_std::path::PathBuf::from(&account_config.dir))
|
||||
.await
|
||||
.context("failed to remove account data")?;
|
||||
|
||||
@@ -308,7 +321,7 @@ struct InnerConfig {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub async fn new(dir: &Path) -> Result<Self> {
|
||||
pub async fn new(dir: &PathBuf) -> Result<Self> {
|
||||
let inner = InnerConfig {
|
||||
accounts: Vec::new(),
|
||||
selected_account: 0,
|
||||
@@ -342,14 +355,18 @@ impl Config {
|
||||
pub async fn load_accounts(&self, events: &Events) -> Result<BTreeMap<u32, Context>> {
|
||||
let mut accounts = BTreeMap::new();
|
||||
for account_config in &self.inner.accounts {
|
||||
let ctx = Context::new(&account_config.dbfile(), account_config.id, events.clone())
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to create context from file {:?}",
|
||||
account_config.dbfile()
|
||||
)
|
||||
})?;
|
||||
let ctx = Context::new(
|
||||
account_config.dbfile().into(),
|
||||
account_config.id,
|
||||
events.clone(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to create context from file {:?}",
|
||||
account_config.dbfile()
|
||||
)
|
||||
})?;
|
||||
|
||||
accounts.insert(account_config.id, ctx);
|
||||
}
|
||||
@@ -358,7 +375,7 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Create a new account in the given root directory.
|
||||
async fn new_account(&mut self, dir: &Path) -> Result<AccountConfig> {
|
||||
async fn new_account(&mut self, dir: &PathBuf) -> Result<AccountConfig> {
|
||||
let id = {
|
||||
let id = self.inner.next_id;
|
||||
let uuid = Uuid::new_v4();
|
||||
@@ -366,7 +383,7 @@ impl Config {
|
||||
|
||||
self.inner.accounts.push(AccountConfig {
|
||||
id,
|
||||
dir: target_dir,
|
||||
dir: target_dir.into(),
|
||||
uuid,
|
||||
});
|
||||
self.inner.next_id += 1;
|
||||
@@ -447,10 +464,10 @@ impl AccountConfig {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_account_new_open() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts1");
|
||||
let p: PathBuf = dir.path().join("accounts1").into();
|
||||
|
||||
let mut accounts1 = Accounts::new(p.clone()).await.unwrap();
|
||||
accounts1.add_account().await.unwrap();
|
||||
@@ -465,10 +482,10 @@ mod tests {
|
||||
assert_eq!(accounts1.accounts.len(), accounts2.accounts.len());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_account_new_add_remove() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new(p.clone()).await.unwrap();
|
||||
assert_eq!(accounts.accounts.len(), 0);
|
||||
@@ -492,10 +509,10 @@ mod tests {
|
||||
assert_eq!(accounts.accounts.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_accounts_remove_last() -> Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new(p.clone()).await?;
|
||||
assert!(accounts.get_selected_account().await.is_none());
|
||||
@@ -513,17 +530,17 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_migrate_account() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new(p.clone()).await.unwrap();
|
||||
assert_eq!(accounts.accounts.len(), 0);
|
||||
assert_eq!(accounts.config.get_selected_account().await, 0);
|
||||
|
||||
let extern_dbfile: PathBuf = dir.path().join("other");
|
||||
let ctx = Context::new(&extern_dbfile, 0, Events::new())
|
||||
let extern_dbfile: PathBuf = dir.path().join("other").into();
|
||||
let ctx = Context::new(extern_dbfile.clone(), 0, Events::new())
|
||||
.await
|
||||
.unwrap();
|
||||
ctx.set_config(crate::config::Config::Addr, Some("me@mail.com"))
|
||||
@@ -550,10 +567,10 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Tests that accounts are sorted by ID.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_accounts_sorted() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new(p.clone()).await.unwrap();
|
||||
|
||||
@@ -568,10 +585,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_accounts_ids_unique_increasing_and_persisted() -> Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
let dummy_accounts = 10;
|
||||
|
||||
let (id0, id1, id2) = {
|
||||
@@ -650,10 +667,10 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_no_accounts_event_emitter() -> Result<()> {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let accounts = Accounts::new(p.clone()).await?;
|
||||
|
||||
@@ -665,7 +682,7 @@ mod tests {
|
||||
|
||||
// Test that event emitter does not return `None` immediately.
|
||||
let duration = std::time::Duration::from_millis(1);
|
||||
assert!(tokio::time::timeout(duration, event_emitter.recv())
|
||||
assert!(async_std::future::timeout(duration, event_emitter.recv())
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
@@ -676,10 +693,10 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_encrypted_account() -> Result<()> {
|
||||
let dir = tempfile::tempdir().context("failed to create tempdir")?;
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
|
||||
let mut accounts = Accounts::new(p.clone())
|
||||
.await
|
||||
|
||||
323
src/blob.rs
323
src/blob.rs
@@ -4,13 +4,14 @@ use core::cmp::max;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use async_std::prelude::*;
|
||||
use async_std::{fs, io};
|
||||
|
||||
use anyhow::{format_err, Context as _, Error, Result};
|
||||
use image::{DynamicImage, ImageFormat};
|
||||
use num_traits::FromPrimitive;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::{fs, io};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
@@ -88,7 +89,7 @@ impl<'a> BlobObject<'a> {
|
||||
Err(err) => {
|
||||
if attempt >= MAX_ATTEMPT {
|
||||
return Err(err).context("failed to create file");
|
||||
} else if attempt == 1 && !dir.exists() {
|
||||
} else if attempt == 1 && !dir.exists().await {
|
||||
fs::create_dir_all(dir).await.ok_or_log(context);
|
||||
} else {
|
||||
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
|
||||
@@ -370,81 +371,108 @@ impl<'a> BlobObject<'a> {
|
||||
mut img_wh: u32,
|
||||
max_bytes: Option<usize>,
|
||||
) -> Result<Option<String>> {
|
||||
tokio::task::block_in_place(move || {
|
||||
let mut img = image::open(&blob_abs).context("image recode failure")?;
|
||||
let orientation = self.get_exif_orientation(context);
|
||||
let mut encoded = Vec::new();
|
||||
let mut changed_name = None;
|
||||
let mut img = image::open(&blob_abs).context("image recode failure")?;
|
||||
let orientation = self.get_exif_orientation(context);
|
||||
let mut encoded = Vec::new();
|
||||
let mut changed_name = None;
|
||||
|
||||
let exceeds_width = img.width() > img_wh || img.height() > img_wh;
|
||||
|
||||
let do_scale =
|
||||
exceeds_width || encoded_img_exceeds_bytes(context, &img, max_bytes, &mut encoded)?;
|
||||
let do_rotate = matches!(orientation, Ok(90) | Ok(180) | Ok(270));
|
||||
|
||||
if do_scale || do_rotate {
|
||||
if do_rotate {
|
||||
img = match orientation {
|
||||
Ok(90) => img.rotate90(),
|
||||
Ok(180) => img.rotate180(),
|
||||
Ok(270) => img.rotate270(),
|
||||
_ => img,
|
||||
}
|
||||
fn encode_img(img: &DynamicImage, encoded: &mut Vec<u8>) -> anyhow::Result<()> {
|
||||
encoded.clear();
|
||||
let mut buf = Cursor::new(encoded);
|
||||
img.write_to(&mut buf, image::ImageFormat::Jpeg)?;
|
||||
Ok(())
|
||||
}
|
||||
fn encoded_img_exceeds_bytes(
|
||||
context: &Context,
|
||||
img: &DynamicImage,
|
||||
max_bytes: Option<usize>,
|
||||
encoded: &mut Vec<u8>,
|
||||
) -> anyhow::Result<bool> {
|
||||
if let Some(max_bytes) = max_bytes {
|
||||
encode_img(img, encoded)?;
|
||||
if encoded.len() > max_bytes {
|
||||
info!(
|
||||
context,
|
||||
"image size {}B ({}x{}px) exceeds {}B, need to scale down",
|
||||
encoded.len(),
|
||||
img.width(),
|
||||
img.height(),
|
||||
max_bytes,
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
let exceeds_width = img.width() > img_wh || img.height() > img_wh;
|
||||
|
||||
if do_scale {
|
||||
if !exceeds_width {
|
||||
// The image is already smaller than img_wh, but exceeds max_bytes
|
||||
// We can directly start with trying to scale down to 2/3 of its current width
|
||||
img_wh = max(img.width(), img.height()) * 2 / 3
|
||||
}
|
||||
let do_scale =
|
||||
exceeds_width || encoded_img_exceeds_bytes(context, &img, max_bytes, &mut encoded)?;
|
||||
let do_rotate = matches!(orientation, Ok(90) | Ok(180) | Ok(270));
|
||||
|
||||
loop {
|
||||
let new_img = img.thumbnail(img_wh, img_wh);
|
||||
|
||||
if encoded_img_exceeds_bytes(context, &new_img, max_bytes, &mut encoded)? {
|
||||
if img_wh < 20 {
|
||||
return Err(format_err!(
|
||||
"Failed to scale image to below {}B",
|
||||
max_bytes.unwrap_or_default()
|
||||
));
|
||||
}
|
||||
|
||||
img_wh = img_wh * 2 / 3;
|
||||
} else {
|
||||
if encoded.is_empty() {
|
||||
encode_img(&new_img, &mut encoded)?;
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"Final scaled-down image size: {}B ({}px)",
|
||||
encoded.len(),
|
||||
img_wh
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if do_scale || do_rotate {
|
||||
if do_rotate {
|
||||
img = match orientation {
|
||||
Ok(90) => img.rotate90(),
|
||||
Ok(180) => img.rotate180(),
|
||||
Ok(270) => img.rotate270(),
|
||||
_ => img,
|
||||
}
|
||||
|
||||
// The file format is JPEG now, we may have to change the file extension
|
||||
if !matches!(ImageFormat::from_path(&blob_abs), Ok(ImageFormat::Jpeg)) {
|
||||
blob_abs = blob_abs.with_extension("jpg");
|
||||
let file_name = blob_abs.file_name().context("No avatar file name (???)")?;
|
||||
let file_name = file_name.to_str().context("Filename is no UTF-8 (???)")?;
|
||||
changed_name = Some(format!("$BLOBDIR/{}", file_name));
|
||||
}
|
||||
|
||||
if encoded.is_empty() {
|
||||
encode_img(&img, &mut encoded)?;
|
||||
}
|
||||
|
||||
std::fs::write(&blob_abs, &encoded)
|
||||
.context("failed to write recoded blob to file")?;
|
||||
}
|
||||
|
||||
Ok(changed_name)
|
||||
})
|
||||
if do_scale {
|
||||
if !exceeds_width {
|
||||
// The image is already smaller than img_wh, but exceeds max_bytes
|
||||
// We can directly start with trying to scale down to 2/3 of its current width
|
||||
img_wh = max(img.width(), img.height()) * 2 / 3
|
||||
}
|
||||
|
||||
loop {
|
||||
let new_img = img.thumbnail(img_wh, img_wh);
|
||||
|
||||
if encoded_img_exceeds_bytes(context, &new_img, max_bytes, &mut encoded)? {
|
||||
if img_wh < 20 {
|
||||
return Err(format_err!(
|
||||
"Failed to scale image to below {}B",
|
||||
max_bytes.unwrap_or_default()
|
||||
));
|
||||
}
|
||||
|
||||
img_wh = img_wh * 2 / 3;
|
||||
} else {
|
||||
if encoded.is_empty() {
|
||||
encode_img(&new_img, &mut encoded)?;
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"Final scaled-down image size: {}B ({}px)",
|
||||
encoded.len(),
|
||||
img_wh
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The file format is JPEG now, we may have to change the file extension
|
||||
if !matches!(ImageFormat::from_path(&blob_abs), Ok(ImageFormat::Jpeg)) {
|
||||
blob_abs = blob_abs.with_extension("jpg");
|
||||
let file_name = blob_abs.file_name().context("No avatar file name (???)")?;
|
||||
let file_name = file_name.to_str().context("Filename is no UTF-8 (???)")?;
|
||||
changed_name = Some(format!("$BLOBDIR/{}", file_name));
|
||||
}
|
||||
|
||||
if encoded.is_empty() {
|
||||
encode_img(&img, &mut encoded)?;
|
||||
}
|
||||
|
||||
fs::write(&blob_abs, &encoded)
|
||||
.await
|
||||
.context("failed to write recoded blob to file")?;
|
||||
}
|
||||
|
||||
Ok(changed_name)
|
||||
}
|
||||
|
||||
pub fn get_exif_orientation(&self, context: &Context) -> Result<i32, Error> {
|
||||
@@ -472,35 +500,6 @@ impl<'a> fmt::Display for BlobObject<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_img(img: &DynamicImage, encoded: &mut Vec<u8>) -> anyhow::Result<()> {
|
||||
encoded.clear();
|
||||
let mut buf = Cursor::new(encoded);
|
||||
img.write_to(&mut buf, image::ImageFormat::Jpeg)?;
|
||||
Ok(())
|
||||
}
|
||||
fn encoded_img_exceeds_bytes(
|
||||
context: &Context,
|
||||
img: &DynamicImage,
|
||||
max_bytes: Option<usize>,
|
||||
encoded: &mut Vec<u8>,
|
||||
) -> anyhow::Result<bool> {
|
||||
if let Some(max_bytes) = max_bytes {
|
||||
encode_img(img, encoded)?;
|
||||
if encoded.len() > max_bytes {
|
||||
info!(
|
||||
context,
|
||||
"image size {}B ({}x{}px) exceeds {}B, need to scale down",
|
||||
encoded.len(),
|
||||
img.width(),
|
||||
img.height(),
|
||||
max_bytes,
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fs::File;
|
||||
@@ -514,16 +513,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
fn check_image_size(path: impl AsRef<Path>, width: u32, height: u32) -> image::DynamicImage {
|
||||
tokio::task::block_in_place(move || {
|
||||
let img = image::open(path).expect("failed to open image");
|
||||
assert_eq!(img.width(), width, "invalid width");
|
||||
assert_eq!(img.height(), height, "invalid height");
|
||||
img
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create() {
|
||||
let t = TestContext::new().await;
|
||||
let blob = BlobObject::create(&t, "foo", b"hello").await.unwrap();
|
||||
@@ -534,28 +524,28 @@ mod tests {
|
||||
assert_eq!(blob.to_abs_path(), t.get_blobdir().join("foo"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_lowercase_ext() {
|
||||
let t = TestContext::new().await;
|
||||
let blob = BlobObject::create(&t, "foo.TXT", b"hello").await.unwrap();
|
||||
assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_as_file_name() {
|
||||
let t = TestContext::new().await;
|
||||
let blob = BlobObject::create(&t, "foo.txt", b"hello").await.unwrap();
|
||||
assert_eq!(blob.as_file_name(), "foo.txt");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_as_rel_path() {
|
||||
let t = TestContext::new().await;
|
||||
let blob = BlobObject::create(&t, "foo.txt", b"hello").await.unwrap();
|
||||
assert_eq!(blob.as_rel_path(), Path::new("foo.txt"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_suffix() {
|
||||
let t = TestContext::new().await;
|
||||
let blob = BlobObject::create(&t, "foo.txt", b"hello").await.unwrap();
|
||||
@@ -564,16 +554,16 @@ mod tests {
|
||||
assert_eq!(blob.suffix(), None);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_dup() {
|
||||
let t = TestContext::new().await;
|
||||
BlobObject::create(&t, "foo.txt", b"hello").await.unwrap();
|
||||
let foo_path = t.get_blobdir().join("foo.txt");
|
||||
assert!(foo_path.exists());
|
||||
assert!(foo_path.exists().await);
|
||||
BlobObject::create(&t, "foo.txt", b"world").await.unwrap();
|
||||
let mut dir = fs::read_dir(t.get_blobdir()).await.unwrap();
|
||||
while let Ok(Some(dirent)) = dir.next_entry().await {
|
||||
let fname = dirent.file_name();
|
||||
while let Some(dirent) = dir.next().await {
|
||||
let fname = dirent.unwrap().file_name();
|
||||
if fname == foo_path.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo_path).await.unwrap(), b"hello");
|
||||
} else {
|
||||
@@ -584,20 +574,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_double_ext_preserved() {
|
||||
let t = TestContext::new().await;
|
||||
BlobObject::create(&t, "foo.tar.gz", b"hello")
|
||||
.await
|
||||
.unwrap();
|
||||
let foo_path = t.get_blobdir().join("foo.tar.gz");
|
||||
assert!(foo_path.exists());
|
||||
assert!(foo_path.exists().await);
|
||||
BlobObject::create(&t, "foo.tar.gz", b"world")
|
||||
.await
|
||||
.unwrap();
|
||||
let mut dir = fs::read_dir(t.get_blobdir()).await.unwrap();
|
||||
while let Ok(Some(dirent)) = dir.next_entry().await {
|
||||
let fname = dirent.file_name();
|
||||
while let Some(dirent) = dir.next().await {
|
||||
let fname = dirent.unwrap().file_name();
|
||||
if fname == foo_path.file_name().unwrap() {
|
||||
assert_eq!(fs::read(&foo_path).await.unwrap(), b"hello");
|
||||
} else {
|
||||
@@ -609,7 +599,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_long_names() {
|
||||
let t = TestContext::new().await;
|
||||
let s = "1".repeat(150);
|
||||
@@ -618,7 +608,7 @@ mod tests {
|
||||
assert!(blobname.len() < 128);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_and_copy() {
|
||||
let t = TestContext::new().await;
|
||||
let src = t.dir.path().join("src");
|
||||
@@ -633,10 +623,10 @@ mod tests {
|
||||
.await
|
||||
.is_err());
|
||||
let whoops = t.get_blobdir().join("whoops");
|
||||
assert!(!whoops.exists());
|
||||
assert!(!whoops.exists().await);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_from_path() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -656,7 +646,7 @@ mod tests {
|
||||
let data = fs::read(blob.to_abs_path()).await.unwrap();
|
||||
assert_eq!(data, b"boo");
|
||||
}
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_from_name_long() {
|
||||
let t = TestContext::new().await;
|
||||
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
|
||||
@@ -719,7 +709,7 @@ mod tests {
|
||||
assert!(!stem.contains('?'));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_selfavatar_outside_blobdir() {
|
||||
let t = TestContext::new().await;
|
||||
let avatar_src = t.dir.path().join("avatar.jpg");
|
||||
@@ -731,17 +721,22 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
let avatar_blob = t.get_blobdir().join("avatar.jpg");
|
||||
assert!(!avatar_blob.exists());
|
||||
assert!(!avatar_blob.exists().await);
|
||||
t.set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(avatar_blob.exists());
|
||||
assert!(tokio::fs::metadata(&avatar_blob).await.unwrap().len() < avatar_bytes.len() as u64);
|
||||
assert!(avatar_blob.exists().await);
|
||||
assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64);
|
||||
let avatar_cfg = t.get_config(Config::Selfavatar).await.unwrap();
|
||||
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
|
||||
|
||||
check_image_size(avatar_src, 1000, 1000);
|
||||
check_image_size(&avatar_blob, BALANCED_AVATAR_SIZE, BALANCED_AVATAR_SIZE);
|
||||
let img = image::open(avatar_src).unwrap();
|
||||
assert_eq!(img.width(), 1000);
|
||||
assert_eq!(img.height(), 1000);
|
||||
|
||||
let img = image::open(&avatar_blob).unwrap();
|
||||
assert_eq!(img.width(), BALANCED_AVATAR_SIZE);
|
||||
assert_eq!(img.height(), BALANCED_AVATAR_SIZE);
|
||||
|
||||
async fn file_size(path_buf: &PathBuf) -> u64 {
|
||||
let file = File::open(path_buf).await.unwrap();
|
||||
@@ -755,14 +750,12 @@ mod tests {
|
||||
.unwrap();
|
||||
assert!(file_size(&avatar_blob).await <= 3000);
|
||||
assert!(file_size(&avatar_blob).await > 2000);
|
||||
tokio::task::block_in_place(move || {
|
||||
let img = image::open(&avatar_blob).unwrap();
|
||||
assert!(img.width() > 130);
|
||||
assert_eq!(img.width(), img.height());
|
||||
});
|
||||
let img = image::open(&avatar_blob).unwrap();
|
||||
assert!(img.width() > 130);
|
||||
assert_eq!(img.width(), img.height());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_selfavatar_in_blobdir() {
|
||||
let t = TestContext::new().await;
|
||||
let avatar_src = t.get_blobdir().join("avatar.png");
|
||||
@@ -773,7 +766,9 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
check_image_size(&avatar_src, 900, 900);
|
||||
let img = image::open(&avatar_src).unwrap();
|
||||
assert_eq!(img.width(), 900);
|
||||
assert_eq!(img.height(), 900);
|
||||
|
||||
t.set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap()))
|
||||
.await
|
||||
@@ -784,10 +779,12 @@ mod tests {
|
||||
avatar_src.with_extension("jpg").to_str().unwrap()
|
||||
);
|
||||
|
||||
check_image_size(avatar_cfg, BALANCED_AVATAR_SIZE, BALANCED_AVATAR_SIZE);
|
||||
let img = image::open(avatar_cfg).unwrap();
|
||||
assert_eq!(img.width(), BALANCED_AVATAR_SIZE);
|
||||
assert_eq!(img.height(), BALANCED_AVATAR_SIZE);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_selfavatar_copy_without_recode() {
|
||||
let t = TestContext::new().await;
|
||||
let avatar_src = t.dir.path().join("avatar.png");
|
||||
@@ -799,20 +796,20 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
let avatar_blob = t.get_blobdir().join("avatar.png");
|
||||
assert!(!avatar_blob.exists());
|
||||
assert!(!avatar_blob.exists().await);
|
||||
t.set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(avatar_blob.exists());
|
||||
assert!(avatar_blob.exists().await);
|
||||
assert_eq!(
|
||||
tokio::fs::metadata(&avatar_blob).await.unwrap().len(),
|
||||
std::fs::metadata(&avatar_blob).unwrap().len(),
|
||||
avatar_bytes.len() as u64
|
||||
);
|
||||
let avatar_cfg = t.get_config(Config::Selfavatar).await.unwrap();
|
||||
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_recode_image_1() {
|
||||
let bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
|
||||
// BALANCED_IMAGE_SIZE > 1000, the original image size, so the image is not scaled down:
|
||||
@@ -832,7 +829,7 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_recode_image_2() {
|
||||
// The "-rotated" files are rotated by 270 degrees using the Exif metadata
|
||||
let bytes = include_bytes!("../test-data/image/rectangle2000x1800-rotated.jpg");
|
||||
@@ -858,7 +855,7 @@ mod tests {
|
||||
// Do this in parallel to speed up the test a bit
|
||||
// (it still takes very long though)
|
||||
let bytes2 = bytes.clone();
|
||||
let join_handle = tokio::task::spawn(async move {
|
||||
let join_handle = async_std::task::spawn(async move {
|
||||
let img_rotated = send_image_check_mediaquality(
|
||||
Some("0"),
|
||||
&bytes2,
|
||||
@@ -886,10 +883,10 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_correct_rotation(&img_rotated);
|
||||
|
||||
join_handle.await.unwrap();
|
||||
join_handle.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_recode_image_3() {
|
||||
let bytes = include_bytes!("../test-data/image/rectangle200x180-rotated.jpg");
|
||||
let img_rotated = send_image_check_mediaquality(Some("0"), bytes, 200, 180, 270, 180, 200)
|
||||
@@ -937,10 +934,10 @@ mod tests {
|
||||
.await?;
|
||||
let file = alice.get_blobdir().join("file.jpg");
|
||||
|
||||
fs::write(&file, &bytes)
|
||||
.await
|
||||
.context("failed to write file")?;
|
||||
check_image_size(&file, original_width, original_height);
|
||||
File::create(&file).await?.write_all(bytes).await?;
|
||||
let img = image::open(&file)?;
|
||||
assert_eq!(img.width(), original_width);
|
||||
assert_eq!(img.height(), original_height);
|
||||
|
||||
let blob = BlobObject::new_from_path(&alice, &file).await?;
|
||||
assert_eq!(blob.get_exif_orientation(&alice).unwrap_or(0), orientation);
|
||||
@@ -952,11 +949,9 @@ mod tests {
|
||||
let alice_msg = alice.get_last_msg().await;
|
||||
assert_eq!(alice_msg.get_width() as u32, compressed_width);
|
||||
assert_eq!(alice_msg.get_height() as u32, compressed_height);
|
||||
check_image_size(
|
||||
alice_msg.get_file(&alice).unwrap(),
|
||||
compressed_width,
|
||||
compressed_height,
|
||||
);
|
||||
let img = image::open(alice_msg.get_file(&alice).unwrap())?;
|
||||
assert_eq!(img.width() as u32, compressed_width);
|
||||
assert_eq!(img.height() as u32, compressed_height);
|
||||
|
||||
let bob_msg = bob.recv_msg(&sent).await;
|
||||
assert_eq!(bob_msg.get_width() as u32, compressed_width);
|
||||
@@ -966,11 +961,13 @@ mod tests {
|
||||
let blob = BlobObject::new_from_path(&bob, &file).await?;
|
||||
assert_eq!(blob.get_exif_orientation(&bob).unwrap_or(0), 0);
|
||||
|
||||
let img = check_image_size(file, compressed_width, compressed_height);
|
||||
let img = image::open(file)?;
|
||||
assert_eq!(img.width() as u32, compressed_width);
|
||||
assert_eq!(img.height() as u32, compressed_height);
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_increation_in_blobdir() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "abc").await?;
|
||||
@@ -989,7 +986,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_increation_not_blobdir() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "abc").await?;
|
||||
|
||||
150
src/chat.rs
150
src/chat.rs
@@ -2,11 +2,11 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -3464,10 +3464,10 @@ mod tests {
|
||||
use crate::contact::Contact;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::test_utils::TestContext;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use async_std::fs::File;
|
||||
use async_std::prelude::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_chat_info() {
|
||||
let t = TestContext::new().await;
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.com").await;
|
||||
@@ -3498,7 +3498,7 @@ mod tests {
|
||||
assert_eq!(info, loaded);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_draft_no_draft() {
|
||||
let t = TestContext::new().await;
|
||||
let chat = t.get_self_chat().await;
|
||||
@@ -3506,14 +3506,14 @@ mod tests {
|
||||
assert!(draft.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_draft_special_chat_id() {
|
||||
let t = TestContext::new().await;
|
||||
let draft = DC_CHAT_ID_LAST_SPECIAL.get_draft(&t).await.unwrap();
|
||||
assert!(draft.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_draft_no_chat() {
|
||||
// This is a weird case, maybe this should be an error but we
|
||||
// do not get this info from the database currently.
|
||||
@@ -3522,7 +3522,7 @@ mod tests {
|
||||
assert!(draft.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_draft() {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = &t.get_self_chat().await.id;
|
||||
@@ -3536,7 +3536,7 @@ mod tests {
|
||||
assert_eq!(msg_text, draft_text);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_delete_draft() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "abc").await?;
|
||||
@@ -3557,7 +3557,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_forwarding_draft_failing() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = &t.get_self_chat().await.id;
|
||||
@@ -3571,7 +3571,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_draft_stable_ids() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = &t.get_self_chat().await.id;
|
||||
@@ -3616,7 +3616,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_change_quotes_on_reused_message_object() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
@@ -3668,7 +3668,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_contact_to_chat_ex_add_self() {
|
||||
// Adding self to a contact should succeed, even though it's pointless.
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -3681,7 +3681,7 @@ mod tests {
|
||||
assert_eq!(added, false);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_modify_chat_multi_device() -> Result<()> {
|
||||
let a1 = TestContext::new_alice().await;
|
||||
let a2 = TestContext::new_alice().await;
|
||||
@@ -3756,7 +3756,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_modify_chat_disordered() -> Result<()> {
|
||||
// Alice creates a group with Bob, Claire and Daisy and then removes Claire and Daisy
|
||||
// (sleep() is needed as otherwise smeared time from Alice looks to Bob like messages from the future which are all set to "now" then)
|
||||
@@ -3770,47 +3770,47 @@ mod tests {
|
||||
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
|
||||
|
||||
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
let add1 = alice.pop_sent_msg().await;
|
||||
|
||||
add_contact_to_chat(&alice, alice_chat_id, claire_id).await?;
|
||||
let add2 = alice.pop_sent_msg().await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
add_contact_to_chat(&alice, alice_chat_id, daisy_id).await?;
|
||||
let add3 = alice.pop_sent_msg().await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 4);
|
||||
|
||||
remove_contact_from_chat(&alice, alice_chat_id, claire_id).await?;
|
||||
let remove1 = alice.pop_sent_msg().await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
remove_contact_from_chat(&alice, alice_chat_id, daisy_id).await?;
|
||||
let remove2 = alice.pop_sent_msg().await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 2);
|
||||
|
||||
// Bob receives the add and deletion messages out of order
|
||||
let bob = TestContext::new_bob().await;
|
||||
bob.recv_msg(&add1).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
bob.recv_msg(&add3).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
let bob_chat_id = bob.recv_msg(&add2).await.chat_id;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 4);
|
||||
|
||||
bob.recv_msg(&remove2).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
bob.recv_msg(&remove1).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 2);
|
||||
|
||||
@@ -3818,7 +3818,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Test that group updates are robust to lost messages and eventual out of order arrival.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_modify_chat_lost() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
@@ -3833,11 +3833,11 @@ mod tests {
|
||||
|
||||
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
|
||||
let add = alice.pop_sent_msg().await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
remove_contact_from_chat(&alice, alice_chat_id, claire_id).await?;
|
||||
let remove1 = alice.pop_sent_msg().await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
remove_contact_from_chat(&alice, alice_chat_id, daisy_id).await?;
|
||||
let remove2 = alice.pop_sent_msg().await;
|
||||
@@ -3860,7 +3860,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_leave_group() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -3889,7 +3889,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_remove_contact_for_single() {
|
||||
let ctx = TestContext::new_alice().await;
|
||||
let bob = Contact::create(&ctx, "", "bob@f.br").await.unwrap();
|
||||
@@ -3913,7 +3913,7 @@ mod tests {
|
||||
assert_eq!(get_chat_contacts(&ctx, chat.id).await.unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_self_talk() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat = &t.get_self_chat().await;
|
||||
@@ -3944,7 +3944,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_device_msg_unlabelled() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -3979,7 +3979,7 @@ mod tests {
|
||||
assert_eq!(msg2.chat_id.get_msg_cnt(&t).await.unwrap(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_device_msg_labelled() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -4029,7 +4029,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_device_msg_label_only() {
|
||||
let t = TestContext::new().await;
|
||||
let res = add_device_msg(&t, Some(""), None).await;
|
||||
@@ -4049,7 +4049,7 @@ mod tests {
|
||||
assert!(!msg_id.as_ref().unwrap().is_unset());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_was_device_msg_ever_added() {
|
||||
let t = TestContext::new().await;
|
||||
add_device_msg(&t, Some("some-label"), None).await.ok();
|
||||
@@ -4069,7 +4069,7 @@ mod tests {
|
||||
assert!(was_device_msg_ever_added(&t, "").await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_delete_device_chat() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -4089,7 +4089,7 @@ mod tests {
|
||||
assert_eq!(chatlist_len(&t, 0).await, 0)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_device_chat_cannot_sent() {
|
||||
let t = TestContext::new().await;
|
||||
t.update_device_chats().await.unwrap();
|
||||
@@ -4106,7 +4106,7 @@ mod tests {
|
||||
assert!(forward_msgs(&t, &[msg_id], device_chat_id).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_delete_and_reset_all_device_msgs() {
|
||||
let t = TestContext::new().await;
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
@@ -4138,7 +4138,7 @@ mod tests {
|
||||
.len()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_archive() {
|
||||
// create two chats
|
||||
let t = TestContext::new().await;
|
||||
@@ -4241,7 +4241,7 @@ mod tests {
|
||||
assert_eq!(chatlist_len(&t, DC_GCL_ARCHIVED_ONLY).await, 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_unarchive_if_muted() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -4335,7 +4335,7 @@ mod tests {
|
||||
result
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_pinned() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -4347,9 +4347,9 @@ mod tests {
|
||||
.await
|
||||
.unwrap()
|
||||
.chat_id;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
let chat_id2 = t.get_self_chat().await.id;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
let chat_id3 = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -4392,7 +4392,7 @@ mod tests {
|
||||
assert_eq!(chatlist, vec![chat_id3, chat_id2, chat_id1]);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_set_chat_name() {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
@@ -4410,7 +4410,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_same_chat_twice() {
|
||||
let context = TestContext::new().await;
|
||||
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de")
|
||||
@@ -4433,7 +4433,7 @@ mod tests {
|
||||
assert_eq!(chat2.name, chat.name);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_shall_attach_selfavatar() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
@@ -4451,7 +4451,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_set_mute_duration() {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
@@ -4502,7 +4502,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_info_msg() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
@@ -4519,7 +4519,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_info_msg_with_cmd() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
|
||||
@@ -4549,7 +4549,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_set_protection() {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo")
|
||||
@@ -4617,7 +4617,7 @@ mod tests {
|
||||
assert_eq!(msg.get_state(), MessageState::OutDelivered); // as bcc-self is disabled and there is nobody else in the chat
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_lookup_by_contact_id() {
|
||||
let ctx = TestContext::new_alice().await;
|
||||
|
||||
@@ -4660,7 +4660,7 @@ mod tests {
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_lookup_self_by_contact_id() {
|
||||
let ctx = TestContext::new_alice().await;
|
||||
|
||||
@@ -4679,7 +4679,7 @@ mod tests {
|
||||
assert_eq!(chat.blocked, Blocked::Not);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_group_with_removed_message_id() -> Result<()> {
|
||||
// Alice creates a group with Bob, sends a message to bob
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -4733,7 +4733,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_marknoticed_chat() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.org").await;
|
||||
@@ -4778,7 +4778,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_contact_request_fresh_messages() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -4828,7 +4828,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_contact_request_archive() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -4868,7 +4868,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_classic_email_chat() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
@@ -4913,7 +4913,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_chat_get_color() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat").await?;
|
||||
@@ -4956,7 +4956,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_sticker_png() -> Result<()> {
|
||||
test_sticker(
|
||||
"sticker.png",
|
||||
@@ -4967,7 +4967,7 @@ mod tests {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_sticker_jpeg() -> Result<()> {
|
||||
test_sticker(
|
||||
"sticker.jpg",
|
||||
@@ -4978,7 +4978,7 @@ mod tests {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_sticker_gif() -> Result<()> {
|
||||
test_sticker(
|
||||
"sticker.gif",
|
||||
@@ -4989,7 +4989,7 @@ mod tests {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_sticker_forward() -> Result<()> {
|
||||
// create chats
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -5020,7 +5020,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_forward() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -5041,7 +5041,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_forward_info_msg() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -5067,7 +5067,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_forward_quote() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -5102,7 +5102,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_forward_group() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -5152,7 +5152,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_only_minimal_data_are_forwarded() -> Result<()> {
|
||||
// send a message from Alice to a group with Bob
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -5194,7 +5194,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_resend_own_message() -> Result<()> {
|
||||
// Alice creates group with Bob and sends an initial message
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -5247,7 +5247,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_resend_foreign_message_fails() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let alice_grp = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
@@ -5266,7 +5266,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_resend_opportunistically_encryption() -> Result<()> {
|
||||
// Alice creates group with Bob and sends an initial message
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -5303,7 +5303,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_resend_info_message_fails() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let alice_grp = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
|
||||
@@ -5327,7 +5327,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_can_send_group() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = Contact::create(&alice, "", "bob@f.br").await?;
|
||||
@@ -5353,7 +5353,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_broadcast() -> Result<()> {
|
||||
// create two context, send two messages so both know the other
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -5396,7 +5396,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_for_contact_with_blocked() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let (contact_id, _) =
|
||||
@@ -5430,7 +5430,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_chat_get_encryption_info() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
@@ -376,7 +376,7 @@ mod tests {
|
||||
use crate::stock_str::StockMessage;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_try_load() {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id1 = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat")
|
||||
@@ -432,7 +432,7 @@ mod tests {
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_sort_self_talk_up_on_forward() {
|
||||
let t = TestContext::new().await;
|
||||
t.update_device_chats().await.unwrap();
|
||||
@@ -457,7 +457,7 @@ mod tests {
|
||||
.is_self_talk());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_search_special_chat_names() {
|
||||
let t = TestContext::new().await;
|
||||
t.update_device_chats().await.unwrap();
|
||||
@@ -488,7 +488,7 @@ mod tests {
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_search_single_chat() -> anyhow::Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -548,7 +548,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_search_single_chat_without_authname() -> anyhow::Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -610,7 +610,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_summary_unwrap() {
|
||||
let t = TestContext::new().await;
|
||||
let chat_id1 = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat")
|
||||
|
||||
@@ -454,7 +454,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_media_quality_config_option() {
|
||||
let t = TestContext::new().await;
|
||||
let media_quality = t.get_config_int(Config::MediaQuality).await.unwrap();
|
||||
@@ -471,7 +471,7 @@ mod tests {
|
||||
assert_eq!(media_quality, constants::MediaQuality::Worse);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ui_config() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -493,7 +493,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Regression test for https://github.com/deltachat/deltachat-core-rust/issues/3012
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_set_config_bool() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -505,7 +505,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_self_addrs() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
@@ -558,7 +558,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_change_primary_self_addr() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
let alice = tcm.alice().await;
|
||||
|
||||
@@ -6,10 +6,9 @@ mod read_url;
|
||||
mod server_params;
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use futures::FutureExt;
|
||||
use futures_lite::FutureExt as _;
|
||||
use async_std::prelude::*;
|
||||
use async_std::task;
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
use tokio::task;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
@@ -56,6 +55,8 @@ impl Context {
|
||||
|
||||
/// Configures this account with the currently set parameters.
|
||||
pub async fn configure(&self) -> Result<()> {
|
||||
use futures::future::FutureExt;
|
||||
|
||||
ensure!(
|
||||
self.scheduler.read().await.is_none(),
|
||||
"cannot configure, already running"
|
||||
@@ -403,7 +404,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
progress!(ctx, 850);
|
||||
|
||||
// Wait for SMTP configuration
|
||||
match smtp_config_task.await.unwrap() {
|
||||
match smtp_config_task.await {
|
||||
Ok(smtp_param) => {
|
||||
param.smtp = smtp_param;
|
||||
}
|
||||
@@ -446,7 +447,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
ctx.interrupt_inbox(InterruptInfo::new(false)).await;
|
||||
|
||||
progress!(ctx, 940);
|
||||
update_device_chats_handle.await??;
|
||||
update_device_chats_handle.await?;
|
||||
|
||||
ctx.sql.set_raw_config_bool("configured", true).await?;
|
||||
|
||||
@@ -548,7 +549,7 @@ async fn try_imap_one_param(
|
||||
);
|
||||
info!(context, "Trying: {}", inf);
|
||||
|
||||
let (_s, r) = async_channel::bounded(1);
|
||||
let (_s, r) = async_std::channel::bounded(1);
|
||||
|
||||
let mut imap = match Imap::new(param, socks5_config.clone(), addr, provider_strict_tls, r).await
|
||||
{
|
||||
@@ -633,13 +634,10 @@ async fn nicer_configuration_error(context: &Context, errors: Vec<ConfigurationE
|
||||
return "no error".to_string();
|
||||
};
|
||||
|
||||
if errors.iter().all(|e| {
|
||||
e.msg.to_lowercase().contains("could not resolve")
|
||||
|| e.msg
|
||||
.to_lowercase()
|
||||
.contains("temporary failure in name resolution")
|
||||
|| e.msg.to_lowercase().contains("name or service not known")
|
||||
}) {
|
||||
if errors
|
||||
.iter()
|
||||
.all(|e| e.msg.to_lowercase().contains("could not resolve"))
|
||||
{
|
||||
return stock_str::error_no_network(context).await;
|
||||
}
|
||||
|
||||
@@ -680,7 +678,7 @@ mod tests {
|
||||
use crate::config::Config;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_no_panic_on_bad_credentials() {
|
||||
let t = TestContext::new().await;
|
||||
t.set_config(Config::Addr, Some("probably@unexistant.addr"))
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::{anyhow, format_err};
|
||||
|
||||
use crate::context::Context;
|
||||
|
||||
use anyhow::format_err;
|
||||
use anyhow::Context as _;
|
||||
|
||||
pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||
match read_url_inner(context, url).await {
|
||||
Ok(s) => {
|
||||
@@ -15,27 +16,24 @@ pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result<String> {
|
||||
let client = reqwest::Client::new();
|
||||
let mut url = url.to_string();
|
||||
pub async fn read_url_inner(context: &Context, mut url: &str) -> anyhow::Result<String> {
|
||||
let mut _temp; // For the borrow checker
|
||||
|
||||
// Follow up to 10 http-redirects
|
||||
for _i in 0..10 {
|
||||
let response = client.get(&url).send().await?;
|
||||
let mut response = surf::get(url).send().await.map_err(|e| e.into_inner())?;
|
||||
if response.status().is_redirection() {
|
||||
let headers = response.headers();
|
||||
let header = headers
|
||||
.get_all("location")
|
||||
.iter()
|
||||
_temp = response
|
||||
.header("location")
|
||||
.context("Redirection doesn't have a target location")?
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("Redirection doesn't have a target location"))?
|
||||
.to_str()?;
|
||||
info!(context, "Following redirect to {}", header);
|
||||
url = header.to_string();
|
||||
.to_string();
|
||||
info!(context, "Following redirect to {}", _temp);
|
||||
url = &_temp;
|
||||
continue;
|
||||
}
|
||||
|
||||
return response.text().await.map_err(Into::into);
|
||||
return response.body_string().await.map_err(|e| e.into_inner());
|
||||
}
|
||||
|
||||
Err(format_err!("Followed 10 redirections"))
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use async_std::path::PathBuf;
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
@@ -1438,8 +1438,8 @@ fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use async_std::fs::File;
|
||||
use async_std::io::WriteExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1508,7 +1508,7 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_contacts() -> Result<()> {
|
||||
let context = TestContext::new().await;
|
||||
|
||||
@@ -1572,7 +1572,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_is_self_addr() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
assert_eq!(t.is_self_addr("me@me.org").await?, false);
|
||||
@@ -1584,7 +1584,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_add_or_lookup() {
|
||||
// add some contacts, this also tests add_address_book()
|
||||
let t = TestContext::new().await;
|
||||
@@ -1685,7 +1685,7 @@ mod tests {
|
||||
assert!(!contact.is_blocked());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_contact_name_changes() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -1797,7 +1797,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_delete() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
@@ -1825,7 +1825,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_remote_authnames() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -1876,7 +1876,7 @@ mod tests {
|
||||
assert_eq!(contact.get_display_name(), "bob3");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_remote_authnames_create_empty() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -1925,7 +1925,7 @@ mod tests {
|
||||
///
|
||||
/// In the past, "Not Bob" name was stuck until "Bob" changed the name to "Not Bob" and back in
|
||||
/// the "From:" field or user set the name to empty string manually.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_remote_authnames_update_to() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -1958,7 +1958,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_remote_authnames_edit_empty() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -1995,7 +1995,7 @@ mod tests {
|
||||
assert!(addr_cmp(" mailto:AA@AA.ORG", "Aa@Aa.orG"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_name_in_address() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -2034,7 +2034,7 @@ mod tests {
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_lookup_id_by_addr() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -2059,7 +2059,7 @@ mod tests {
|
||||
assert_eq!(id, Some(ContactId::SELF));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_contact_get_color() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
let contact_id = Contact::create(&t, "name", "name@example.net").await?;
|
||||
@@ -2078,7 +2078,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_contact_get_encrinfo() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
@@ -2123,7 +2123,7 @@ CCCB 5AA9 F6E1 141C 9431
|
||||
|
||||
/// Tests that status is synchronized when sending encrypted BCC-self messages and not
|
||||
/// synchronized when the message is not encrypted.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_synchronize_status() -> Result<()> {
|
||||
// Alice has two devices.
|
||||
let alice1 = TestContext::new_alice().await;
|
||||
@@ -2188,7 +2188,7 @@ CCCB 5AA9 F6E1 141C 9431
|
||||
}
|
||||
|
||||
/// Tests that DC_EVENT_SELFAVATAR_CHANGED is emitted on avatar changes.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_selfavatar_changed_event() -> Result<()> {
|
||||
// Alice has two devices.
|
||||
let alice1 = TestContext::new_alice().await;
|
||||
@@ -2247,7 +2247,7 @@ CCCB 5AA9 F6E1 141C 9431
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_last_seen() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::ffi::OsString;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use async_channel::{self as channel, Receiver, Sender};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use async_std::{
|
||||
channel::{self, Receiver, Sender},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
use crate::chat::{get_chat_cnt, ChatId};
|
||||
use crate::config::Config;
|
||||
@@ -74,7 +75,7 @@ pub struct InnerContext {
|
||||
/// The text of the last error logged and emitted as an event.
|
||||
/// If the ui wants to display an error after a failure,
|
||||
/// `last_error` should be used to avoid races with the event thread.
|
||||
pub(crate) last_error: std::sync::RwLock<String>,
|
||||
pub(crate) last_error: RwLock<String>,
|
||||
}
|
||||
|
||||
/// The state of ongoing process.
|
||||
@@ -114,7 +115,7 @@ pub fn get_info() -> BTreeMap<&'static str, String> {
|
||||
|
||||
impl Context {
|
||||
/// Creates new context and opens the database.
|
||||
pub async fn new(dbfile: &Path, id: u32, events: Events) -> Result<Context> {
|
||||
pub async fn new(dbfile: PathBuf, id: u32, events: Events) -> Result<Context> {
|
||||
let context = Self::new_closed(dbfile, id, events).await?;
|
||||
|
||||
// Open the database if is not encrypted.
|
||||
@@ -125,15 +126,15 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Creates new context without opening the database.
|
||||
pub async fn new_closed(dbfile: &Path, id: u32, events: Events) -> Result<Context> {
|
||||
pub async fn new_closed(dbfile: PathBuf, id: u32, events: Events) -> Result<Context> {
|
||||
let mut blob_fname = OsString::new();
|
||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
blob_fname.push("-blobs");
|
||||
let blobdir = dbfile.with_file_name(blob_fname);
|
||||
if !blobdir.exists() {
|
||||
tokio::fs::create_dir_all(&blobdir).await?;
|
||||
if !blobdir.exists().await {
|
||||
async_std::fs::create_dir_all(&blobdir).await?;
|
||||
}
|
||||
let context = Context::with_blobdir(dbfile.into(), blobdir, id, events).await?;
|
||||
let context = Context::with_blobdir(dbfile, blobdir, id, events).await?;
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ impl Context {
|
||||
events: Events,
|
||||
) -> Result<Context> {
|
||||
ensure!(
|
||||
blobdir.is_dir(),
|
||||
blobdir.is_dir().await,
|
||||
"Blobdir does not exist: {}",
|
||||
blobdir.display()
|
||||
);
|
||||
@@ -192,7 +193,7 @@ impl Context {
|
||||
quota: RwLock::new(None),
|
||||
creation_time: std::time::SystemTime::now(),
|
||||
last_full_folder_scan: Mutex::new(None),
|
||||
last_error: std::sync::RwLock::new("".to_string()),
|
||||
last_error: RwLock::new("".to_string()),
|
||||
};
|
||||
|
||||
let ctx = Context {
|
||||
@@ -642,14 +643,14 @@ impl Context {
|
||||
Ok(mvbox.as_deref() == Some(folder_name))
|
||||
}
|
||||
|
||||
pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
|
||||
pub(crate) fn derive_blobdir(dbfile: &PathBuf) -> PathBuf {
|
||||
let mut blob_fname = OsString::new();
|
||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
blob_fname.push("-blobs");
|
||||
dbfile.with_file_name(blob_fname)
|
||||
}
|
||||
|
||||
pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
|
||||
pub(crate) fn derive_walfile(dbfile: &PathBuf) -> PathBuf {
|
||||
let mut wal_fname = OsString::new();
|
||||
wal_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
wal_fname.push("-wal");
|
||||
@@ -678,19 +679,19 @@ mod tests {
|
||||
use strum::IntoEnumIterator;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_wrong_db() -> Result<()> {
|
||||
let tmp = tempfile::tempdir()?;
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
tokio::fs::write(&dbfile, b"123").await?;
|
||||
let res = Context::new(&dbfile, 1, Events::new()).await?;
|
||||
std::fs::write(&dbfile, b"123")?;
|
||||
let res = Context::new(dbfile.into(), 1, Events::new()).await?;
|
||||
|
||||
// Broken database is indistinguishable from encrypted one.
|
||||
assert_eq!(res.is_open().await, false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_fresh_msgs() {
|
||||
let t = TestContext::new().await;
|
||||
let fresh = t.get_fresh_msgs().await.unwrap();
|
||||
@@ -717,7 +718,7 @@ mod tests {
|
||||
dc_receive_imf(t, msg.as_bytes(), false).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_fresh_msgs_and_muted_chats() {
|
||||
// receive various mails in 3 chats
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -767,7 +768,7 @@ mod tests {
|
||||
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 9); // claire is counted again
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_fresh_msgs_and_muted_until() {
|
||||
let t = TestContext::new_alice().await;
|
||||
let bob = t.create_chat_with_contact("", "bob@g.it").await;
|
||||
@@ -825,61 +826,61 @@ mod tests {
|
||||
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_blobdir_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
Context::new(&dbfile, 1, Events::new()).await.unwrap();
|
||||
Context::new(dbfile.into(), 1, Events::new()).await.unwrap();
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_wrong_blogdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
tokio::fs::write(&blobdir, b"123").await.unwrap();
|
||||
let res = Context::new(&dbfile, 1, Events::new()).await;
|
||||
std::fs::write(&blobdir, b"123").unwrap();
|
||||
let res = Context::new(dbfile.into(), 1, Events::new()).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_sqlite_parent_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let subdir = tmp.path().join("subdir");
|
||||
let dbfile = subdir.join("db.sqlite");
|
||||
let dbfile2 = dbfile.clone();
|
||||
Context::new(&dbfile, 1, Events::new()).await.unwrap();
|
||||
Context::new(dbfile.into(), 1, Events::new()).await.unwrap();
|
||||
assert!(subdir.is_dir());
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_with_empty_blobdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = PathBuf::new();
|
||||
let res = Context::with_blobdir(dbfile, blobdir, 1, Events::new()).await;
|
||||
let res = Context::with_blobdir(dbfile.into(), blobdir, 1, Events::new()).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_with_blobdir_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("blobs");
|
||||
let res = Context::with_blobdir(dbfile, blobdir, 1, Events::new()).await;
|
||||
let res = Context::with_blobdir(dbfile.into(), blobdir.into(), 1, Events::new()).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn no_crashes_on_context_deref() {
|
||||
let t = TestContext::new().await;
|
||||
std::mem::drop(t);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_info() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -895,7 +896,7 @@ mod tests {
|
||||
assert_eq!(info.get("level").unwrap(), "awesome");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_info_completeness() {
|
||||
// For easier debugging,
|
||||
// get_info() shall return all important information configurable by the Config-values.
|
||||
@@ -943,7 +944,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_search_msgs() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let self_talk = ChatId::create_for_contact(&alice, ContactId::SELF).await?;
|
||||
@@ -999,7 +1000,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_limit_search_msgs() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let chat = alice
|
||||
@@ -1032,13 +1033,13 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_check_passphrase() -> Result<()> {
|
||||
let dir = tempdir()?;
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
|
||||
let id = 1;
|
||||
let context = Context::new_closed(&dbfile, id, Events::new())
|
||||
let context = Context::new_closed(dbfile.clone().into(), id, Events::new())
|
||||
.await
|
||||
.context("failed to create context")?;
|
||||
assert_eq!(context.open("foo".to_string()).await?, true);
|
||||
@@ -1046,7 +1047,7 @@ mod tests {
|
||||
drop(context);
|
||||
|
||||
let id = 2;
|
||||
let context = Context::new(&dbfile, id, Events::new())
|
||||
let context = Context::new(dbfile.into(), id, Events::new())
|
||||
.await
|
||||
.context("failed to create context")?;
|
||||
assert_eq!(context.is_open().await, false);
|
||||
@@ -1057,7 +1058,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ongoing() -> Result<()> {
|
||||
let context = TestContext::new().await;
|
||||
|
||||
|
||||
@@ -280,9 +280,7 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
// Always update the status, even if there is no footer, to allow removing the status.
|
||||
//
|
||||
// Ignore MDNs though, as they never contain the signature even if user has set it.
|
||||
// Ignore footers from mailinglists as they are often created or modified by the mailinglist software.
|
||||
if mime_parser.mdn_reports.is_empty()
|
||||
&& !mime_parser.is_mailinglist_message()
|
||||
&& is_partial_download.is_none()
|
||||
&& from_id != ContactId::UNDEFINED
|
||||
&& context
|
||||
@@ -1741,14 +1739,13 @@ async fn create_or_lookup_mailinglist(
|
||||
}
|
||||
}
|
||||
|
||||
// additional names in square brackets in the subject are preferred
|
||||
// if we have an additional name square brackets in the subject, we prefer that
|
||||
// (as that part is much more visible, we assume, that names is shorter and comes more to the point,
|
||||
// than the sometimes longer part from ListId)
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
static SUBJECT: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^.{0,5}\[(.+?)\](\s*\[.+\])?").unwrap()); // remove square brackets around first name
|
||||
static SUBJECT: Lazy<Regex> = Lazy::new(|| Regex::new(r"^.{0,5}\[(.*.)\]").unwrap());
|
||||
if let Some(cap) = SUBJECT.captures(&subject) {
|
||||
name = cap[1].to_string() + cap.get(2).map_or("", |m| m.as_str());
|
||||
name = cap[1].to_string();
|
||||
}
|
||||
|
||||
// if we do not have a name yet and `From` indicates, that this is a notification list,
|
||||
@@ -2206,8 +2203,8 @@ async fn add_or_lookup_contact_by_addr(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use tokio::fs::{self, File};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use async_std::fs::{self, File};
|
||||
use async_std::io::WriteExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -2219,7 +2216,7 @@ mod tests {
|
||||
use crate::message::Message;
|
||||
use crate::test_utils::{get_chat_msg, TestContext, TestContextManager};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_grpid_simple() {
|
||||
let context = TestContext::new().await;
|
||||
let raw = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
@@ -2237,7 +2234,7 @@ mod tests {
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_grpid_from_multiple() {
|
||||
let context = TestContext::new().await;
|
||||
let raw = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||
@@ -2286,7 +2283,7 @@ mod tests {
|
||||
\n\
|
||||
hello\n";
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_adhoc_group_show_chats_only() {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert_eq!(t.get_config_int(Config::ShowEmails).await.unwrap(), 0);
|
||||
@@ -2309,7 +2306,7 @@ mod tests {
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_adhoc_group_show_accepted_contact_unknown() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("1")).await.unwrap();
|
||||
@@ -2320,7 +2317,7 @@ mod tests {
|
||||
assert_eq!(chats.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_adhoc_group_show_accepted_contact_known() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("1")).await.unwrap();
|
||||
@@ -2333,7 +2330,7 @@ mod tests {
|
||||
assert_eq!(chats.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_adhoc_group_show_accepted_contact_accepted() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("1")).await.unwrap();
|
||||
@@ -2371,7 +2368,7 @@ mod tests {
|
||||
assert_eq!(chat::get_chat_contacts(&t, chat_id).await.unwrap().len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_adhoc_group_show_all() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -2390,7 +2387,7 @@ mod tests {
|
||||
assert_eq!(chat::get_chat_contacts(&t, chat_id).await.unwrap().len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_read_receipt_and_unarchive() -> Result<()> {
|
||||
// create alice's account
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -2504,7 +2501,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_no_from() {
|
||||
// if there is no from given, from_id stays 0 which is just fine. These messages
|
||||
// are very rare, however, we have to add them to the database
|
||||
@@ -2536,7 +2533,7 @@ mod tests {
|
||||
assert!(chats.get_msg_id(0).is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_escaped_from() {
|
||||
let t = TestContext::new_alice().await;
|
||||
let contact_id = Contact::create(&t, "foobar", "foobar@example.com")
|
||||
@@ -2569,7 +2566,7 @@ mod tests {
|
||||
assert_eq!(msg.param.get_int(Param::WantsMdn).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_escaped_recipients() {
|
||||
let t = TestContext::new_alice().await;
|
||||
Contact::create(&t, "foobar", "foobar@example.com")
|
||||
@@ -2611,7 +2608,7 @@ mod tests {
|
||||
assert_eq!(msg.param.get_int(Param::WantsMdn).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_cc_to_contact() {
|
||||
let t = TestContext::new_alice().await;
|
||||
Contact::create(&t, "foobar", "foobar@example.com")
|
||||
@@ -2646,7 +2643,7 @@ mod tests {
|
||||
assert_eq!(contact.get_display_name(), "Carl");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_tiscali() {
|
||||
test_parse_ndn(
|
||||
"alice@tiscali.it",
|
||||
@@ -2658,7 +2655,7 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_testrun() {
|
||||
test_parse_ndn(
|
||||
"alice@testrun.org",
|
||||
@@ -2670,7 +2667,7 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_yahoo() {
|
||||
test_parse_ndn(
|
||||
"alice@yahoo.com",
|
||||
@@ -2682,7 +2679,7 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_gmail() {
|
||||
test_parse_ndn(
|
||||
"alice@gmail.com",
|
||||
@@ -2694,7 +2691,7 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_gmx() {
|
||||
test_parse_ndn(
|
||||
"alice@gmx.com",
|
||||
@@ -2706,7 +2703,7 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_posteo() {
|
||||
test_parse_ndn(
|
||||
"alice@posteo.org",
|
||||
@@ -2718,7 +2715,7 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_testrun_2() {
|
||||
test_parse_ndn(
|
||||
"alice@example.org",
|
||||
@@ -2784,7 +2781,7 @@ mod tests {
|
||||
assert_eq!(msg.error(), error_msg.map(|error| error.to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_ndn_group_msg() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
t.configure_addr("alice@gmail.com").await;
|
||||
@@ -2844,7 +2841,7 @@ mod tests {
|
||||
Message::load_from_db(context, msg_id).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_html_only_mail() {
|
||||
let t = TestContext::new_alice().await;
|
||||
let msg = load_imf_email(&t, include_bytes!("../test-data/message/wrong-html.eml")).await;
|
||||
@@ -2877,7 +2874,7 @@ mod tests {
|
||||
\n\
|
||||
hello back\n";
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_github_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.ctx.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -2944,7 +2941,7 @@ mod tests {
|
||||
\n\
|
||||
body 4\n";
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_classic_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.ctx
|
||||
@@ -2990,7 +2987,7 @@ Hello mailinglist!\r\n"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_other_device_writes_to_mailinglist() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -3040,7 +3037,7 @@ Hello mailinglist!\r\n"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_block_mailing_list() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.ctx
|
||||
@@ -3074,7 +3071,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(msgs.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_mailing_list_decide_block_then_unblock() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3107,7 +3104,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(msgs.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_mailing_list_decide_not_now() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.ctx
|
||||
@@ -3140,7 +3137,7 @@ Hello mailinglist!\r\n"
|
||||
assert!(chat.is_contact_request());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_mailing_list_decide_accept() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.ctx
|
||||
@@ -3168,32 +3165,7 @@ Hello mailinglist!\r\n"
|
||||
assert!(chat.can_send(&t.ctx).await.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_multiple_names_in_subject() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Foo Bar <foo@bar.org>\n\
|
||||
To: deltachat/deltachat-core-rust <deltachat-core-rust@noreply.github.com>\n\
|
||||
Subject: [ola list] [foo][bar] just a subject\n\
|
||||
Message-ID: <3333@example.org>\n\
|
||||
List-ID: \"looong description of 'ola list', with foo, bar\" <delta.codespeak.net>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat_id = msg.get_chat_id();
|
||||
let chat = Chat::load_from_db(&t, chat_id).await?;
|
||||
assert_eq!(chat.name, "ola list [foo][bar]");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_majordomo_mailing_list() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3242,7 +3214,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(chat::get_chat_msgs(&t, chat.id, 0).await.unwrap().len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_mailchimp_mailing_list() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3272,7 +3244,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(chat.name, "Atlas Obscura");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_dhl_mailing_list() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3297,7 +3269,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(chat.name, "DHL Paket");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_dpd_mailing_list() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3322,7 +3294,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(chat.name, "DPD");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_xt_local_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -3352,7 +3324,7 @@ Hello mailinglist!\r\n"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_xing_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -3373,7 +3345,7 @@ Hello mailinglist!\r\n"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ttline_mailing_list() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -3394,7 +3366,7 @@ Hello mailinglist!\r\n"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_mailing_list_with_mimepart_footer() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3425,7 +3397,7 @@ Hello mailinglist!\r\n"
|
||||
assert_eq!(chat.name, "Intern");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_mailing_list_with_mimepart_footer_signed() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3450,7 +3422,7 @@ Hello mailinglist!\r\n"
|
||||
|
||||
/// Test that the changes from apply_mailinglist_changes() are also applied
|
||||
/// if the message is assigned to the chat by In-Reply-To
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_apply_mailinglist_changes_assigned_by_reply() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3492,7 +3464,7 @@ Hello mailinglist!\r\n"
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_dont_show_tokens_in_contacts_list() {
|
||||
check_dont_show_in_contacts_list(
|
||||
"reply+OGHVYCLVBEGATYBICAXBIRQATABUOTUCERABERAHNO@reply.github.com",
|
||||
@@ -3500,7 +3472,7 @@ Hello mailinglist!\r\n"
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_dont_show_noreply_in_contacts_list() {
|
||||
check_dont_show_in_contacts_list("noreply@github.com").await;
|
||||
}
|
||||
@@ -3538,7 +3510,7 @@ YEAAAAAA!.
|
||||
assert!(contacts.is_empty()); // The contact should not have been added to the db
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_pdf_filename_simple() {
|
||||
let t = TestContext::new_alice().await;
|
||||
let msg = load_imf_email(
|
||||
@@ -3551,7 +3523,7 @@ YEAAAAAA!.
|
||||
assert_eq!(msg.param.get(Param::File).unwrap(), "$BLOBDIR/simple.pdf");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_pdf_filename_continuation() {
|
||||
// test filenames split across multiple header lines, see rfc 2231
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -3578,7 +3550,7 @@ YEAAAAAA!.
|
||||
/// or mua may use multipart/related not correctly -
|
||||
/// so this test is in competition with parse_thunderbird_html_embedded_image()
|
||||
/// that wants the image to be kept in the chat.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_many_images() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -3599,7 +3571,7 @@ YEAAAAAA!.
|
||||
|
||||
/// Test that classical MUA messages are assigned to group chats based on the `In-Reply-To`
|
||||
/// header.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_in_reply_to() {
|
||||
let t = TestContext::new().await;
|
||||
t.configure_addr("bob@example.com").await;
|
||||
@@ -3664,7 +3636,7 @@ YEAAAAAA!.
|
||||
|
||||
/// Test that classical MUA messages are assigned to group chats
|
||||
/// based on the `In-Reply-To` header for two-member groups.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_in_reply_to_two_member_group() {
|
||||
let t = TestContext::new().await;
|
||||
t.configure_addr("bob@example.com").await;
|
||||
@@ -3766,7 +3738,7 @@ YEAAAAAA!.
|
||||
assert_eq!(msg.get_text().unwrap(), "private reply");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_save_mime_headers_off() -> anyhow::Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -3781,7 +3753,7 @@ YEAAAAAA!.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_save_mime_headers_on() -> anyhow::Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.set_config_bool(Config::SaveMimeHeaders, true).await?;
|
||||
@@ -3952,7 +3924,7 @@ YEAAAAAA!.
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_alias_support_answer_from_nondc() {
|
||||
// Bob, the other supporter, answers with a classic MUA.
|
||||
let bob_answer = b"To: support@example.org, claire@example.org\n\
|
||||
@@ -3972,7 +3944,7 @@ YEAAAAAA!.
|
||||
check_alias_reply(bob_answer, false, false).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_alias_answer_from_dc() {
|
||||
// Bob, the other supporter, answers with Delta Chat.
|
||||
let bob_answer = b"To: support@example.org, claire@example.org\n\
|
||||
@@ -3996,7 +3968,7 @@ YEAAAAAA!.
|
||||
check_alias_reply(bob_answer, false, false).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_dont_assign_to_trash_by_parent() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
@@ -4044,7 +4016,7 @@ YEAAAAAA!.
|
||||
assert_eq!(msg.text.unwrap(), "Reply");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_dont_show_all_outgoing_msgs_in_self_chat() {
|
||||
// Regression test for <https://github.com/deltachat/deltachat-android/issues/1940>:
|
||||
// Some servers add a `Bcc: <Self>` header, which caused all outgoing messages to
|
||||
@@ -4071,7 +4043,7 @@ Message content",
|
||||
assert_ne!(msg.chat_id, t.get_self_chat().await.id);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_outgoing_classic_mail_creates_chat() {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
@@ -4101,7 +4073,7 @@ Message content",
|
||||
assert_eq!(msg.get_text().unwrap(), "Subj – Message content");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_duplicate_message() -> Result<()> {
|
||||
// Test that duplicate messages are ignored based on the Message-ID
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -4160,80 +4132,7 @@ Second signature";
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_ignore_footer_status_from_mailinglist() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
let bob_id = Contact::add_or_lookup(&t, "", "bob@example.net", Origin::IncomingUnknownCc)
|
||||
.await?
|
||||
.0;
|
||||
let bob = Contact::load_from_db(&t, bob_id).await?;
|
||||
assert_eq!(bob.get_status(), "");
|
||||
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 0);
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob <bob@example.net>
|
||||
To: Alice <alice@example.org>
|
||||
Message-ID: <1@example.org>
|
||||
Subject: first message
|
||||
|
||||
body 1
|
||||
|
||||
--
|
||||
Original signature",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let one2one_chat_id = t.get_last_msg().await.chat_id;
|
||||
let bob = Contact::load_from_db(&t, bob_id).await?;
|
||||
assert_eq!(bob.get_status(), "Original signature");
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob <bob@example.net>
|
||||
Sender: ml@example.net
|
||||
To: Alice <alice@example.org>
|
||||
Message-ID: <2@example.net>
|
||||
Precedence: list
|
||||
Subject: second message
|
||||
|
||||
body 2
|
||||
|
||||
--
|
||||
The modified signature
|
||||
--
|
||||
Tap here to unsubscribe ...",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let ml_chat_id = t.get_last_msg().await.chat_id;
|
||||
let bob = Contact::load_from_db(&t, bob_id).await?;
|
||||
assert_eq!(bob.get_status(), "Original signature");
|
||||
|
||||
dc_receive_imf(
|
||||
&t,
|
||||
b"From: Bob <bob@example.net>
|
||||
To: Alice <alice@example.org>
|
||||
Message-ID: <3@example.org>
|
||||
Subject: third message
|
||||
|
||||
body 3
|
||||
|
||||
--
|
||||
Original signature updated",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let bob = Contact::load_from_db(&t, bob_id).await?;
|
||||
assert_eq!(bob.get_status(), "Original signature updated");
|
||||
assert_eq!(get_chat_msgs(&t, one2one_chat_id, 0).await?.len(), 2);
|
||||
assert_eq!(get_chat_msgs(&t, ml_chat_id, 0).await?.len(), 1);
|
||||
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_chat_assignment_private_classical_reply() {
|
||||
for outgoing_is_classical in &[true, false] {
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -4317,7 +4216,7 @@ Private reply"#,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_chat_assignment_private_chat_reply() {
|
||||
for (outgoing_is_classical, outgoing_has_multiple_recipients) in
|
||||
&[(true, true), (false, true), (false, false)]
|
||||
@@ -4413,7 +4312,7 @@ Sent with my Delta Chat Messenger: https://delta.chat
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_chat_assignment_nonprivate_classical_reply() {
|
||||
for outgoing_is_classical in &[true, false] {
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -4522,7 +4421,7 @@ Reply to all"#,
|
||||
/// messages have the same recipient lists and only differ in the subject and message contents.
|
||||
/// The messages can be properly assigned to chats only using the In-Reply-To or References
|
||||
/// headers.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_chat_assignment_adhoc() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_alice().await;
|
||||
@@ -4602,7 +4501,7 @@ Second thread."#;
|
||||
}
|
||||
|
||||
/// Test that read receipts don't create chats.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_read_receipts_dont_create_chats() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -4635,7 +4534,7 @@ Second thread."#;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_gmx_forwarded_msg() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -4655,7 +4554,7 @@ Second thread."#;
|
||||
}
|
||||
|
||||
/// Tests that user is notified about new incoming contact requests.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_incoming_contact_request() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -4680,7 +4579,7 @@ Second thread."#;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_parent_message() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -4743,7 +4642,7 @@ Message with references."#;
|
||||
}
|
||||
|
||||
/// Test a message with RFC 1847 encapsulation as created by Thunderbird.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_rfc1847_encapsulation() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -4769,7 +4668,7 @@ Message with references."#;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_invalid_to_address() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
@@ -4781,7 +4680,7 @@ Message with references."#;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_reply_from_different_addr() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
@@ -4846,7 +4745,7 @@ Reply from different address
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_long_filenames() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
let alice = tcm.alice().await;
|
||||
@@ -4901,7 +4800,7 @@ Reply from different address
|
||||
}
|
||||
|
||||
/// Tests that contact request is accepted automatically on outgoing message.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_accept_outgoing() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
let alice1 = tcm.alice().await;
|
||||
@@ -4946,7 +4845,7 @@ Reply from different address
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_outgoing_private_reply_multidevice() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
let alice1 = tcm.alice().await;
|
||||
@@ -5023,7 +4922,7 @@ Reply from different address
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_no_private_reply_to_blocked_account() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
let alice = tcm.alice().await;
|
||||
|
||||
@@ -5,19 +5,20 @@ use core::cmp::{max, min};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use async_std::prelude::*;
|
||||
use async_std::{fs, io};
|
||||
|
||||
use anyhow::{bail, Error, Result};
|
||||
use chrono::{Local, TimeZone};
|
||||
use futures::StreamExt;
|
||||
use mailparse::dateparse;
|
||||
use mailparse::headers::Headers;
|
||||
use mailparse::MailHeaderMap;
|
||||
use rand::{thread_rng, Rng};
|
||||
use tokio::{fs, io};
|
||||
|
||||
use crate::chat::{add_device_msg, add_device_msg_with_importance};
|
||||
use crate::constants::{DC_ELLIPSIS, DC_OUTDATED_WARNING_DAYS};
|
||||
@@ -277,7 +278,7 @@ pub fn dc_get_filemeta(buf: &[u8]) -> Result<(u32, u32), Error> {
|
||||
///
|
||||
/// If `path` starts with "$BLOBDIR", replaces it with the blobdir path.
|
||||
/// Otherwise, returns path as is.
|
||||
pub(crate) fn dc_get_abs_path(context: &Context, path: impl AsRef<Path>) -> PathBuf {
|
||||
pub(crate) fn dc_get_abs_path<P: AsRef<Path>>(context: &Context, path: P) -> PathBuf {
|
||||
let p: &Path = path.as_ref();
|
||||
if let Ok(p) = p.strip_prefix("$BLOBDIR") {
|
||||
context.get_blobdir().join(p)
|
||||
@@ -296,10 +297,10 @@ pub(crate) async fn dc_get_filebytes(context: &Context, path: impl AsRef<Path>)
|
||||
|
||||
pub(crate) async fn dc_delete_file(context: &Context, path: impl AsRef<Path>) -> bool {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
if !path_abs.exists() {
|
||||
if !path_abs.exists().await {
|
||||
return false;
|
||||
}
|
||||
if !path_abs.is_file() {
|
||||
if !path_abs.is_file().await {
|
||||
warn!(
|
||||
context,
|
||||
"refusing to delete non-file \"{}\".",
|
||||
@@ -322,9 +323,8 @@ pub(crate) async fn dc_delete_file(context: &Context, path: impl AsRef<Path>) ->
|
||||
}
|
||||
|
||||
pub async fn dc_delete_files_in_dir(context: &Context, path: impl AsRef<Path>) {
|
||||
match tokio::fs::read_dir(path).await {
|
||||
Ok(read_dir) => {
|
||||
let mut read_dir = tokio_stream::wrappers::ReadDirStream::new(read_dir);
|
||||
match async_std::fs::read_dir(path).await {
|
||||
Ok(mut read_dir) => {
|
||||
while let Some(entry) = read_dir.next().await {
|
||||
match entry {
|
||||
Ok(file) => {
|
||||
@@ -344,7 +344,7 @@ pub(crate) async fn dc_create_folder(
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<(), io::Error> {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
if !path_abs.exists() {
|
||||
if !path_abs.exists().await {
|
||||
match fs::create_dir_all(path_abs).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
@@ -655,7 +655,7 @@ mod tests {
|
||||
assert_eq!(hop_info, expected)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_parse_receive_headers_integration() {
|
||||
let raw = include_bytes!("../test-data/message/mail_with_cc.txt");
|
||||
let expected = r"State: Fresh
|
||||
@@ -873,7 +873,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_file_handling() {
|
||||
let t = TestContext::new().await;
|
||||
let context = &t;
|
||||
@@ -889,8 +889,8 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content")
|
||||
.await
|
||||
.is_ok());
|
||||
assert!(dc_file_exist!(context, "$BLOBDIR/foobar"));
|
||||
assert!(!dc_file_exist!(context, "$BLOBDIR/foobarx"));
|
||||
assert!(dc_file_exist!(context, "$BLOBDIR/foobar").await);
|
||||
assert!(!dc_file_exist!(context, "$BLOBDIR/foobarx").await);
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar").await, 7);
|
||||
|
||||
let abs_path = context
|
||||
@@ -899,23 +899,23 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
assert!(dc_file_exist!(context, &abs_path));
|
||||
assert!(dc_file_exist!(context, &abs_path).await);
|
||||
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar").await);
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder")
|
||||
.await
|
||||
.is_ok());
|
||||
assert!(dc_file_exist!(context, "$BLOBDIR/foobar-folder"));
|
||||
assert!(dc_file_exist!(context, "$BLOBDIR/foobar-folder").await);
|
||||
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder").await);
|
||||
|
||||
let fn0 = "$BLOBDIR/data.data";
|
||||
assert!(dc_write_file(context, &fn0, b"content").await.is_ok());
|
||||
|
||||
assert!(dc_delete_file(context, &fn0).await);
|
||||
assert!(!dc_file_exist!(context, &fn0));
|
||||
assert!(!dc_file_exist!(context, &fn0).await);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_smeared_timestamp() {
|
||||
let t = TestContext::new().await;
|
||||
assert_ne!(
|
||||
@@ -931,7 +931,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_smeared_timestamps() {
|
||||
let t = TestContext::new().await;
|
||||
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
|
||||
@@ -1001,7 +1001,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
assert_eq!(improve_single_line_input("\r\nahte\n\r"), "ahte");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_maybe_warn_on_bad_time() {
|
||||
let t = TestContext::new().await;
|
||||
let timestamp_now = time();
|
||||
@@ -1064,7 +1064,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
|
||||
assert_eq!(msgs.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_maybe_warn_on_outdated() {
|
||||
let t = TestContext::new().await;
|
||||
let timestamp_now: i64 = time();
|
||||
|
||||
@@ -382,7 +382,7 @@ mod tests {
|
||||
assert_eq!(txt.trim(), "two\nlines");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_quote_div() {
|
||||
let input = include_str!("../test-data/message/gmx-quote-body.eml");
|
||||
let dehtml = dehtml(input).unwrap();
|
||||
|
||||
@@ -280,7 +280,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_download_limit() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -303,7 +303,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_update_download_state() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat = t.create_chat_with_contact("Bob", "bob@example.org").await;
|
||||
@@ -328,7 +328,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_partial_receive_imf() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -376,7 +376,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_partial_download_and_ephemeral() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = t
|
||||
|
||||
10
src/e2ee.rs
10
src/e2ee.rs
@@ -422,7 +422,7 @@ mod tests {
|
||||
mod ensure_secret_key_exists {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_prexisting() {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert_eq!(
|
||||
@@ -431,7 +431,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_not_configured() {
|
||||
let t = TestContext::new().await;
|
||||
assert!(ensure_secret_key_exists(&t).await.is_err());
|
||||
@@ -480,7 +480,7 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
assert_eq!(has_decrypted_pgp_armor(data), false);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_encrypted_no_autocrypt() -> anyhow::Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -588,7 +588,7 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
vec![(Some(peerstate), addr)]
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_should_encrypt() {
|
||||
let t = TestContext::new_alice().await;
|
||||
let encrypt_helper = EncryptHelper::new(&t).await.unwrap();
|
||||
@@ -615,7 +615,7 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_mixed_up_mime() -> Result<()> {
|
||||
// "Mixed Up" mail as received when sending an encrypted
|
||||
// message using Delta Chat Desktop via ProtonMail IMAP/SMTP
|
||||
|
||||
@@ -62,9 +62,9 @@ use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
use async_channel::Receiver;
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::future::timeout;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::time::timeout;
|
||||
|
||||
use crate::chat::{send_msg, ChatId};
|
||||
use crate::constants::{DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH};
|
||||
@@ -581,7 +581,7 @@ mod tests {
|
||||
dc_tools::IsNoneOrEmpty,
|
||||
};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_stock_ephemeral_messages() {
|
||||
let context = TestContext::new().await;
|
||||
|
||||
@@ -711,7 +711,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Test enabling and disabling ephemeral timer remotely.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ephemeral_enable_disable() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -743,7 +743,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Test that timer is enabled even if the message explicitly enabling the timer is lost.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ephemeral_enable_lost() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -785,7 +785,7 @@ mod tests {
|
||||
|
||||
/// Test that Alice replying to the chat without a timer at the same time as Bob enables the
|
||||
/// timer does not result in disabling the timer on the Bob's side.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ephemeral_timer_rollback() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -859,7 +859,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ephemeral_delete_msgs() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let self_chat = t.get_self_chat().await;
|
||||
@@ -985,7 +985,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_delete_expired_imap_messages() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
const HOUR: i64 = 60 * 60;
|
||||
@@ -1096,7 +1096,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// Regression test for a bug in the timer rollback protection.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_ephemeral_timer_references() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! # Events specification.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use async_channel::{self as channel, Receiver, Sender, TrySendError};
|
||||
use async_std::channel::{self, Receiver, Sender, TrySendError};
|
||||
use async_std::path::PathBuf;
|
||||
|
||||
use crate::chat::ChatId;
|
||||
use crate::contact::ContactId;
|
||||
@@ -62,18 +61,23 @@ impl Events {
|
||||
///
|
||||
/// [`Context`]: crate::context::Context
|
||||
/// [`Context::get_event_emitter`]: crate::context::Context::get_event_emitter
|
||||
/// [`Stream`]: futures::stream::Stream
|
||||
/// [`Stream`]: async_std::stream::Stream
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventEmitter(Receiver<Event>);
|
||||
|
||||
impl EventEmitter {
|
||||
/// Blocking recv of an event. Return `None` if the `Sender` has been droped.
|
||||
pub fn recv_sync(&self) -> Option<Event> {
|
||||
async_std::task::block_on(self.recv())
|
||||
}
|
||||
|
||||
/// Async recv of an event. Return `None` if the `Sender` has been droped.
|
||||
pub async fn recv(&self) -> Option<Event> {
|
||||
self.0.recv().await.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl futures::stream::Stream for EventEmitter {
|
||||
impl async_std::stream::Stream for EventEmitter {
|
||||
type Item = Event;
|
||||
|
||||
fn poll_next(
|
||||
|
||||
@@ -203,7 +203,7 @@ mod tests {
|
||||
assert_eq!(format_flowed_quote(quote), expected);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_send_quotes() -> anyhow::Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
26
src/html.rs
26
src/html.rs
@@ -284,7 +284,7 @@ mod tests {
|
||||
use crate::message::{MessengerMessage, Viewtype};
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_plain_unspecified() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_plain_unspecified.eml");
|
||||
@@ -300,7 +300,7 @@ This message does not have Content-Type nor Subject.<br/>
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_plain_iso88591() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_plain_iso88591.eml");
|
||||
@@ -316,7 +316,7 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_plain_flowed() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_plain_flowed.eml");
|
||||
@@ -336,7 +336,7 @@ and will be wrapped as usual.<br/>
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_alt_plain() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_alt_plain.eml");
|
||||
@@ -355,7 +355,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_html() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_html.eml");
|
||||
@@ -373,7 +373,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_alt_html() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_alt_html.eml");
|
||||
@@ -388,7 +388,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_alt_plain_html() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
|
||||
@@ -405,7 +405,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_htmlparse_apple_cid_jpg() {
|
||||
// load raw mime html-data with related image-part (cid:)
|
||||
// and make sure, Content-Id has angle-brackets that are removed correctly.
|
||||
@@ -424,14 +424,14 @@ test some special html-characters as < > and & but also " and &#x
|
||||
assert!(!parser.html.contains("cid:"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_html_invalid_msgid() {
|
||||
let t = TestContext::new().await;
|
||||
let msg_id = MsgId::new(100);
|
||||
assert!(msg_id.get_html(&t).await.is_err())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_html_forwarding() {
|
||||
// alice receives a non-delta html-message
|
||||
let alice = TestContext::new_alice().await;
|
||||
@@ -478,7 +478,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
assert!(html.contains("this is <b>html</b>"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_html_forwarding_encrypted() {
|
||||
// Alice receives a non-delta html-message
|
||||
// (`ShowEmails=1` lets Alice actually receive non-delta messages for known contacts,
|
||||
@@ -515,7 +515,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
assert!(html.contains("this is <b>html</b>"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_set_html() {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
@@ -547,7 +547,7 @@ test some special html-characters as < > and & but also " and &#x
|
||||
assert!(html.contains("<b>html</b> text"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_cp1252_html() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
18
src/imap.rs
18
src/imap.rs
@@ -11,11 +11,11 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
use async_channel::Receiver;
|
||||
use async_imap::types::{
|
||||
Fetch, Flag, Mailbox, Name, NameAttribute, Quota, QuotaRoot, UnsolicitedResponse,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::prelude::*;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::chat::{self, ChatId, ChatIdBlocked};
|
||||
@@ -1922,7 +1922,7 @@ fn get_folder_meaning_by_name(folder_name: &str) -> FolderMeaning {
|
||||
|
||||
fn get_folder_meaning(folder_name: &Name) -> FolderMeaning {
|
||||
for attr in folder_name.attributes() {
|
||||
if let NameAttribute::Extension(ref label) = attr {
|
||||
if let NameAttribute::Custom(ref label) = attr {
|
||||
match label.as_ref() {
|
||||
"\\Trash" => return FolderMeaning::Other,
|
||||
"\\Sent" => return FolderMeaning::Sent,
|
||||
@@ -2388,7 +2388,7 @@ mod tests {
|
||||
assert_eq!(get_folder_meaning_by_name("SPAM"), FolderMeaning::Spam);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_set_uid_next_validity() {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert_eq!(get_uid_next(&t.ctx, "Inbox").await.unwrap(), 0);
|
||||
@@ -2570,7 +2570,7 @@ mod tests {
|
||||
("Spam", true, true, "DeltaChat"),
|
||||
];
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_target_folder_incoming_accepted() -> Result<()> {
|
||||
for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
||||
check_target_folder_combination(
|
||||
@@ -2587,7 +2587,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_target_folder_incoming_request() -> Result<()> {
|
||||
for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_REQUEST {
|
||||
check_target_folder_combination(
|
||||
@@ -2604,7 +2604,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_target_folder_outgoing() -> Result<()> {
|
||||
// Test outgoing emails
|
||||
for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
||||
@@ -2622,7 +2622,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_target_folder_setupmsg() -> Result<()> {
|
||||
// Test setupmessages
|
||||
for (folder, mvbox_move, chat_msg, _expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
||||
@@ -2640,7 +2640,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_imap_search_command() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert_eq!(
|
||||
|
||||
@@ -8,7 +8,7 @@ use anyhow::{Context as _, Result};
|
||||
use async_imap::Client as ImapClient;
|
||||
|
||||
use async_smtp::ServerAddress;
|
||||
use tokio::net::{self, TcpStream};
|
||||
use async_std::net::{self, TcpStream};
|
||||
|
||||
use super::session::Session;
|
||||
use crate::login_param::{dc_build_tls, Socks5Config};
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::Imap;
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use async_imap::extensions::idle::IdleResponse;
|
||||
use futures_lite::FutureExt;
|
||||
use async_std::prelude::*;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::{context::Context, scheduler::InterruptInfo};
|
||||
@@ -87,7 +87,9 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
let session = tokio::time::timeout(Duration::from_secs(15), handle.done())
|
||||
let session = handle
|
||||
.done()
|
||||
.timeout(Duration::from_secs(15))
|
||||
.await?
|
||||
.context("IMAP IDLE protocol timed out")?;
|
||||
self.session = Some(Session { inner: session });
|
||||
@@ -119,7 +121,7 @@ impl Imap {
|
||||
|
||||
// check every minute if there are new messages
|
||||
// TODO: grow sleep durations / make them more flexible
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(60));
|
||||
let mut interval = async_std::stream::interval(Duration::from_secs(60));
|
||||
|
||||
enum Event {
|
||||
Tick,
|
||||
@@ -129,7 +131,7 @@ impl Imap {
|
||||
let info = loop {
|
||||
use futures::future::FutureExt;
|
||||
match interval
|
||||
.tick()
|
||||
.next()
|
||||
.map(|_| Event::Tick)
|
||||
.race(
|
||||
self.idle_interrupt
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::{collections::BTreeMap, time::Instant};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::imap::Imap;
|
||||
use crate::log::LogExt;
|
||||
use crate::{context::Context, imap::FolderMeaning};
|
||||
|
||||
use async_std::stream::StreamExt;
|
||||
|
||||
use super::{get_folder_meaning, get_folder_meaning_by_name};
|
||||
|
||||
impl Imap {
|
||||
@@ -103,7 +104,7 @@ impl Imap {
|
||||
let list = session
|
||||
.list(Some(""), Some("*"))
|
||||
.await?
|
||||
.filter_map(|f| async { f.ok_or_log_msg(context, "list_folders() can't get folder") });
|
||||
.filter_map(|f| f.ok_or_log_msg(context, "list_folders() can't get folder"));
|
||||
Ok(list.collect().await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::ops::{Deref, DerefMut};
|
||||
|
||||
use async_imap::Session as ImapSession;
|
||||
use async_native_tls::TlsStream;
|
||||
use async_std::net::TcpStream;
|
||||
use fast_socks5::client::Socks5Stream;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Session {
|
||||
@@ -11,7 +11,7 @@ pub(crate) struct Session {
|
||||
}
|
||||
|
||||
pub(crate) trait SessionStream:
|
||||
tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + Sync + std::fmt::Debug
|
||||
async_std::io::Read + async_std::io::Write + Unpin + Send + Sync + std::fmt::Debug
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
118
src/imex.rs
118
src/imex.rs
@@ -2,15 +2,16 @@
|
||||
|
||||
use std::any::Any;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ::pgp::types::KeyTrait;
|
||||
use anyhow::{bail, ensure, format_err, Context as _, Result};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use futures_lite::FutureExt;
|
||||
use async_std::{
|
||||
fs::{self, File},
|
||||
path::{Path, PathBuf},
|
||||
prelude::*,
|
||||
};
|
||||
use async_tar::Archive;
|
||||
use rand::{thread_rng, Rng};
|
||||
use tokio::fs::{self, File};
|
||||
use tokio_tar::Archive;
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::{self, delete_and_reset_all_device_msgs, ChatId};
|
||||
@@ -108,22 +109,24 @@ pub async fn imex(
|
||||
|
||||
/// Returns the filename of the backup found (otherwise an error)
|
||||
pub async fn has_backup(_context: &Context, dir_name: &Path) -> Result<String> {
|
||||
let mut dir_iter = tokio::fs::read_dir(dir_name).await?;
|
||||
let mut dir_iter = async_std::fs::read_dir(dir_name).await?;
|
||||
let mut newest_backup_name = "".to_string();
|
||||
let mut newest_backup_path: Option<PathBuf> = None;
|
||||
|
||||
while let Ok(Some(dirent)) = dir_iter.next_entry().await {
|
||||
let path = dirent.path();
|
||||
let name = dirent.file_name();
|
||||
let name: String = name.to_string_lossy().into();
|
||||
if name.starts_with("delta-chat")
|
||||
&& name.ends_with(".tar")
|
||||
&& (newest_backup_name.is_empty() || name > newest_backup_name)
|
||||
{
|
||||
// We just use string comparison to determine which backup is newer.
|
||||
// This works fine because the filenames have the form ...delta-chat-backup-2020-07-24-00.tar
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_name = name;
|
||||
while let Some(dirent) = dir_iter.next().await {
|
||||
if let Ok(dirent) = dirent {
|
||||
let path = dirent.path();
|
||||
let name = dirent.file_name();
|
||||
let name: String = name.to_string_lossy().into();
|
||||
if name.starts_with("delta-chat")
|
||||
&& name.ends_with(".tar")
|
||||
&& (newest_backup_name.is_empty() || name > newest_backup_name)
|
||||
{
|
||||
// We just use string comparison to determine which backup is newer.
|
||||
// This works fine because the filenames have the form ...delta-chat-backup-2020-07-24-00.tar
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +177,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
let msg_id = chat::send_msg(context, chat_id, &mut msg).await?;
|
||||
info!(context, "Wait for setup message being sent ...",);
|
||||
while !context.shall_stop_ongoing().await {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id).await {
|
||||
if msg.is_sent() {
|
||||
info!(context, "... setup message sent.",);
|
||||
@@ -443,7 +446,7 @@ async fn import_backup(
|
||||
|
||||
context.sql.config_cache.write().await.clear();
|
||||
|
||||
let mut archive = Archive::new(backup_file);
|
||||
let archive = Archive::new(backup_file);
|
||||
|
||||
let mut entries = archive.entries()?;
|
||||
let mut last_progress = 0;
|
||||
@@ -474,7 +477,7 @@ async fn import_backup(
|
||||
// async_tar will unpack to blobdir/BLOBS_BACKUP_NAME, so we move the file afterwards.
|
||||
f.unpack_in(context.get_blobdir()).await?;
|
||||
let from_path = context.get_blobdir().join(f.path()?);
|
||||
if from_path.is_file() {
|
||||
if from_path.is_file().await {
|
||||
if let Some(name) = from_path.file_name() {
|
||||
fs::rename(&from_path, context.get_blobdir().join(name)).await?;
|
||||
} else {
|
||||
@@ -496,7 +499,10 @@ async fn import_backup(
|
||||
/// Returns Ok((temp_db_path, temp_path, dest_path)) on success. Unencrypted database can be
|
||||
/// written to temp_db_path. The backup can then be written to temp_path. If the backup succeeded,
|
||||
/// it can be renamed to dest_path. This guarantees that the backup is complete.
|
||||
fn get_next_backup_path(folder: &Path, backup_time: i64) -> Result<(PathBuf, PathBuf, PathBuf)> {
|
||||
async fn get_next_backup_path(
|
||||
folder: &Path,
|
||||
backup_time: i64,
|
||||
) -> Result<(PathBuf, PathBuf, PathBuf)> {
|
||||
let folder = PathBuf::from(folder);
|
||||
let stem = chrono::NaiveDateTime::from_timestamp(backup_time, 0)
|
||||
// Don't change this file name format, in has_backup() we use string comparison to determine which backup is newer:
|
||||
@@ -514,7 +520,7 @@ fn get_next_backup_path(folder: &Path, backup_time: i64) -> Result<(PathBuf, Pat
|
||||
let mut destfile = folder.clone();
|
||||
destfile.push(format!("{}-{:02}.tar", stem, i));
|
||||
|
||||
if !tempdbfile.exists() && !tempfile.exists() && !destfile.exists() {
|
||||
if !tempdbfile.exists().await && !tempfile.exists().await && !destfile.exists().await {
|
||||
return Ok((tempdbfile, tempfile, destfile));
|
||||
}
|
||||
}
|
||||
@@ -524,7 +530,7 @@ fn get_next_backup_path(folder: &Path, backup_time: i64) -> Result<(PathBuf, Pat
|
||||
async fn export_backup(context: &Context, dir: &Path, passphrase: String) -> Result<()> {
|
||||
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
|
||||
let now = time();
|
||||
let (temp_db_path, temp_path, dest_path) = get_next_backup_path(dir, now)?;
|
||||
let (temp_db_path, temp_path, dest_path) = get_next_backup_path(dir, now).await?;
|
||||
let _d1 = DeleteOnDrop(temp_db_path.clone());
|
||||
let _d2 = DeleteOnDrop(temp_path.clone());
|
||||
|
||||
@@ -578,8 +584,7 @@ impl Drop for DeleteOnDrop {
|
||||
fn drop(&mut self) {
|
||||
let file = self.0.clone();
|
||||
// Not using dc_delete_file() here because it would send a DeletedBlobFile event
|
||||
// Hack to avoid panic in nested runtime calls of tokio
|
||||
std::fs::remove_file(file).ok();
|
||||
async_std::task::block_on(fs::remove_file(file)).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,21 +595,19 @@ async fn export_backup_inner(
|
||||
) -> Result<()> {
|
||||
let file = File::create(temp_path).await?;
|
||||
|
||||
let mut builder = tokio_tar::Builder::new(file);
|
||||
let mut builder = async_tar::Builder::new(file);
|
||||
|
||||
builder
|
||||
.append_path_with_name(temp_db_path, DBFILE_BACKUP_NAME)
|
||||
.await?;
|
||||
|
||||
let read_dir: Vec<_> =
|
||||
tokio_stream::wrappers::ReadDirStream::new(fs::read_dir(context.get_blobdir()).await?)
|
||||
.try_collect()
|
||||
.await?;
|
||||
let read_dir: Vec<_> = fs::read_dir(context.get_blobdir()).await?.collect().await;
|
||||
let count = read_dir.len();
|
||||
let mut written_files = 0;
|
||||
|
||||
let mut last_progress = 0;
|
||||
for entry in read_dir.into_iter() {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name();
|
||||
if !entry.file_type().await?.is_file() {
|
||||
warn!(
|
||||
@@ -645,9 +648,9 @@ async fn import_self_keys(context: &Context, dir: &Path) -> Result<()> {
|
||||
let mut imported_cnt = 0;
|
||||
|
||||
let dir_name = dir.to_string_lossy();
|
||||
let mut dir_handle = tokio::fs::read_dir(&dir).await?;
|
||||
while let Ok(Some(entry)) = dir_handle.next_entry().await {
|
||||
let entry_fn = entry.file_name();
|
||||
let mut dir_handle = async_std::fs::read_dir(&dir).await?;
|
||||
while let Some(entry) = dir_handle.next().await {
|
||||
let entry_fn = entry?.file_name();
|
||||
let name_f = entry_fn.to_string_lossy();
|
||||
let path_plus_name = dir.join(&entry_fn);
|
||||
match dc_get_filesuffix_lc(&name_f) {
|
||||
@@ -797,7 +800,7 @@ mod tests {
|
||||
|
||||
use ::pgp::armor::BlockType;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_render_setup_file() {
|
||||
let t = TestContext::new_alice().await;
|
||||
let msg = render_setup_file(&t, "hello").await.unwrap();
|
||||
@@ -814,7 +817,7 @@ mod tests {
|
||||
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_render_setup_file_newline_replace() {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
|
||||
@@ -825,7 +828,7 @@ mod tests {
|
||||
assert!(msg.contains("<p>hello<br>there</p>"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_create_setup_code() {
|
||||
let t = TestContext::new().await;
|
||||
let setupcode = create_setup_code(&t);
|
||||
@@ -840,7 +843,7 @@ mod tests {
|
||||
assert_eq!(setupcode.chars().nth(39).unwrap(), '-');
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_export_public_key_to_asc_file() {
|
||||
let context = TestContext::new().await;
|
||||
let key = alice_keypair().public;
|
||||
@@ -850,12 +853,12 @@ mod tests {
|
||||
.is_ok());
|
||||
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||
let filename = format!("{}/public-key-default.asc", blobdir);
|
||||
let bytes = tokio::fs::read(&filename).await.unwrap();
|
||||
let bytes = async_std::fs::read(&filename).await.unwrap();
|
||||
|
||||
assert_eq!(bytes, key.to_asc(None).into_bytes());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_export_private_key_to_asc_file() {
|
||||
let context = TestContext::new().await;
|
||||
let key = alice_keypair().secret;
|
||||
@@ -865,12 +868,12 @@ mod tests {
|
||||
.is_ok());
|
||||
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
|
||||
let filename = format!("{}/private-key-default.asc", blobdir);
|
||||
let bytes = tokio::fs::read(&filename).await.unwrap();
|
||||
let bytes = async_std::fs::read(&filename).await.unwrap();
|
||||
|
||||
assert_eq!(bytes, key.to_asc(None).into_bytes());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_export_and_import_key() {
|
||||
let context = TestContext::new_alice().await;
|
||||
let blobdir = context.ctx.get_blobdir();
|
||||
@@ -884,7 +887,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_export_and_import_backup() -> Result<()> {
|
||||
let backup_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -893,21 +896,26 @@ mod tests {
|
||||
|
||||
let context2 = TestContext::new().await;
|
||||
assert!(!context2.is_configured().await?);
|
||||
assert!(has_backup(&context2, backup_dir.path()).await.is_err());
|
||||
assert!(has_backup(&context2, backup_dir.path().as_ref())
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
// export from context1
|
||||
assert!(
|
||||
imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None)
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
assert!(imex(
|
||||
&context1,
|
||||
ImexMode::ExportBackup,
|
||||
backup_dir.path().as_ref(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.is_ok());
|
||||
let _event = context1
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
|
||||
.await;
|
||||
|
||||
// import to context2
|
||||
let backup = has_backup(&context2, backup_dir.path()).await?;
|
||||
let backup = has_backup(&context2, backup_dir.path().as_ref()).await?;
|
||||
|
||||
// Import of unencrypted backup with incorrect "foobar" backup passphrase fails.
|
||||
assert!(imex(
|
||||
@@ -953,7 +961,7 @@ mod tests {
|
||||
const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597";
|
||||
const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt");
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_split_and_decrypt() {
|
||||
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
||||
@@ -976,20 +984,20 @@ mod tests {
|
||||
assert!(headers.get(HEADER_SETUPCODE).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_key_transfer() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let alice_clone = alice.clone();
|
||||
let key_transfer_task = tokio::task::spawn(async move {
|
||||
let key_transfer_task = async_std::task::spawn(async move {
|
||||
let ctx = alice_clone;
|
||||
initiate_key_transfer(&ctx).await
|
||||
});
|
||||
|
||||
// Wait for the message to be added to the queue.
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
let setup_code = key_transfer_task.await??;
|
||||
let setup_code = key_transfer_task.await?;
|
||||
|
||||
// Alice sets up a second device.
|
||||
let alice2 = TestContext::new().await;
|
||||
|
||||
@@ -448,7 +448,7 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_load_next_job_two() -> Result<()> {
|
||||
// We want to ensure that loading jobs skips over jobs which
|
||||
// fails to load from the database instead of failing to load
|
||||
@@ -464,7 +464,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_load_next_job_one() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
|
||||
32
src/key.rs
32
src/key.rs
@@ -11,7 +11,6 @@ use num_traits::FromPrimitive;
|
||||
use pgp::composed::Deserializable;
|
||||
use pgp::ser::Serialize;
|
||||
use pgp::types::{KeyTrait, SecretKeyTrait};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::KeyGenType;
|
||||
@@ -220,10 +219,9 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
||||
let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await?)
|
||||
.unwrap_or_default();
|
||||
info!(context, "Generating keypair with type {}", keytype);
|
||||
let keypair = Handle::current()
|
||||
.spawn_blocking(move || crate::pgp::create_keypair(addr, keytype))
|
||||
.await??;
|
||||
|
||||
let keypair =
|
||||
async_std::task::spawn_blocking(move || crate::pgp::create_keypair(addr, keytype))
|
||||
.await?;
|
||||
store_self_keypair(context, &keypair, KeyPairUse::Default).await?;
|
||||
info!(
|
||||
context,
|
||||
@@ -399,8 +397,8 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{alice_keypair, TestContext};
|
||||
|
||||
use async_std::sync::Arc;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::Arc;
|
||||
|
||||
static KEYPAIR: Lazy<KeyPair> = Lazy::new(alice_keypair);
|
||||
|
||||
@@ -522,7 +520,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert_eq!(key, key2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_load_self_existing() {
|
||||
let alice = alice_keypair();
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -532,7 +530,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert_eq!(alice.secret, seckey);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_load_self_generate_public() {
|
||||
let t = TestContext::new().await;
|
||||
t.set_config(Config::ConfiguredAddr, Some("alice@example.org"))
|
||||
@@ -542,7 +540,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert!(key.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_load_self_generate_secret() {
|
||||
let t = TestContext::new().await;
|
||||
t.set_config(Config::ConfiguredAddr, Some("alice@example.org"))
|
||||
@@ -552,7 +550,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert!(key.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_load_self_generate_concurrent() {
|
||||
use std::thread;
|
||||
|
||||
@@ -562,19 +560,11 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
.unwrap();
|
||||
let thr0 = {
|
||||
let ctx = t.clone();
|
||||
thread::spawn(move || {
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(SignedPublicKey::load_self(&ctx))
|
||||
})
|
||||
thread::spawn(move || async_std::task::block_on(SignedPublicKey::load_self(&ctx)))
|
||||
};
|
||||
let thr1 = {
|
||||
let ctx = t.clone();
|
||||
thread::spawn(move || {
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(SignedPublicKey::load_self(&ctx))
|
||||
})
|
||||
thread::spawn(move || async_std::task::block_on(SignedPublicKey::load_self(&ctx)))
|
||||
};
|
||||
let res0 = thr0.join().unwrap();
|
||||
let res1 = thr1.join().unwrap();
|
||||
@@ -587,7 +577,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert_eq!(pubkey.primary_key, KEYPAIR.public.primary_key);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_save_self_key_twice() {
|
||||
// Saving the same key twice should result in only one row in
|
||||
// the keypairs table.
|
||||
|
||||
@@ -76,7 +76,7 @@ mod tests {
|
||||
assert_eq!(sec_ring.keys(), [alice.secret]);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_keyring_load_self() {
|
||||
// new_self() implies load_self()
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! # Delta Chat Core Library.
|
||||
|
||||
#![recursion_limit = "256"]
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(
|
||||
unused,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! Location handling.
|
||||
use std::convert::TryFrom;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
use async_channel::Receiver;
|
||||
use async_std::channel::Receiver;
|
||||
use async_std::future::timeout;
|
||||
use bitflags::bitflags;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
use tokio::time::timeout;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::chat::{self, ChatId};
|
||||
use crate::contact::ContactId;
|
||||
@@ -731,7 +731,7 @@ mod tests {
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_kml_parse() {
|
||||
let context = TestContext::new().await;
|
||||
|
||||
@@ -763,7 +763,7 @@ mod tests {
|
||||
assert_eq!(locations_ref[1].timestamp, 1544739072);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_message_kml() {
|
||||
let context = TestContext::new().await;
|
||||
let timestamp = 1598490000;
|
||||
@@ -791,7 +791,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Tests that location.kml is hidden.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn receive_location_kml() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
|
||||
23
src/log.rs
23
src/log.rs
@@ -1,6 +1,7 @@
|
||||
//! # Logging.
|
||||
|
||||
use crate::context::Context;
|
||||
use async_std::task::block_on;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
@@ -48,13 +49,15 @@ impl Context {
|
||||
/// Set last error string.
|
||||
/// Implemented as blocking as used from macros in different, not always async blocks.
|
||||
pub fn set_last_error(&self, error: &str) {
|
||||
let mut last_error = self.last_error.write().unwrap();
|
||||
*last_error = error.to_string();
|
||||
block_on(async move {
|
||||
let mut last_error = self.last_error.write().await;
|
||||
*last_error = error.to_string();
|
||||
});
|
||||
}
|
||||
|
||||
/// Get last error string.
|
||||
pub fn get_last_error(&self) -> String {
|
||||
let last_error = &*self.last_error.read().unwrap();
|
||||
pub async fn get_last_error(&self) -> String {
|
||||
let last_error = &*self.last_error.read().await;
|
||||
last_error.clone()
|
||||
}
|
||||
}
|
||||
@@ -156,24 +159,24 @@ mod tests {
|
||||
use crate::test_utils::TestContext;
|
||||
use anyhow::Result;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_get_last_error() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
assert_eq!(t.get_last_error(), "");
|
||||
assert_eq!(t.get_last_error().await, "");
|
||||
|
||||
error!(t, "foo-error");
|
||||
assert_eq!(t.get_last_error(), "foo-error");
|
||||
assert_eq!(t.get_last_error().await, "foo-error");
|
||||
|
||||
warn!(t, "foo-warning");
|
||||
assert_eq!(t.get_last_error(), "foo-error");
|
||||
assert_eq!(t.get_last_error().await, "foo-error");
|
||||
|
||||
info!(t, "foo-info");
|
||||
assert_eq!(t.get_last_error(), "foo-error");
|
||||
assert_eq!(t.get_last_error().await, "foo-error");
|
||||
|
||||
error!(t, "bar-error");
|
||||
error!(t, "baz-error");
|
||||
assert_eq!(t.get_last_error(), "baz-error");
|
||||
assert_eq!(t.get_last_error().await, "baz-error");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2};
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
use crate::{context::Context, provider::Socket};
|
||||
use anyhow::{ensure, Result};
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::TcpStream;
|
||||
|
||||
use async_native_tls::Certificate;
|
||||
pub use async_smtp::ServerAddress;
|
||||
use fast_socks5::client::Socks5Stream;
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::{io, net::TcpStream};
|
||||
|
||||
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2};
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
use crate::{context::Context, provider::Socket};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Display, FromPrimitive, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
@@ -422,7 +424,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_save_load_login_param() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
@@ -458,7 +460,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[async_std::test]
|
||||
async fn test_build_tls() -> Result<()> {
|
||||
// we are using some additional root certificates.
|
||||
// make sure, they do not break construction of TlsConnector
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user