mirror of
https://github.com/chatmail/core.git
synced 2026-05-04 22:06:29 +03:00
Merge remote-tracking branch 'origin/master' into flub/send-backup
This commit is contained in:
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
name: Rustfmt and Clippy
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.67.1
|
||||
RUSTUP_TOOLCHAIN: 1.68.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install rustfmt and clippy
|
||||
@@ -72,10 +72,10 @@ jobs:
|
||||
include:
|
||||
# Currently used Rust version.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.64.0
|
||||
rust: 1.68.0
|
||||
python: 3.9
|
||||
- os: windows-latest
|
||||
rust: 1.64.0
|
||||
rust: 1.68.0
|
||||
python: false # Python bindings compilation on Windows is not supported.
|
||||
|
||||
# Minimum Supported Rust Version = 1.63.0
|
||||
|
||||
15
.github/workflows/deltachat-rpc-server.yml
vendored
15
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -35,22 +35,29 @@ jobs:
|
||||
path: target/x86_64-unknown-linux-musl/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_aarch64_linux:
|
||||
name: Cross-compile deltachat-rpc-server for aarch64 Linux
|
||||
build_linux:
|
||||
name: Cross-compile deltachat-rpc-server for aarch64 and armv7 Linux
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
run: sh scripts/aarch64-unknown-linux-musl.sh
|
||||
run: sh scripts/zig-rpc-server.sh
|
||||
|
||||
- name: Upload binary
|
||||
- name: Upload aarch64 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64
|
||||
path: target/aarch64-unknown-linux-musl/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload armv7 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-armv7
|
||||
path: target/armv7-unknown-linux-musleabihf/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_android:
|
||||
name: Cross-compile deltachat-rpc-server for Android (armeabi-v7a, arm64-v8a, x86 and x86_64)
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
2
.github/workflows/upload-docs.yml
vendored
2
.github/workflows/upload-docs.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat --no-deps
|
||||
cargo doc --package deltachat --no-deps --document-private-items
|
||||
- name: Upload to rs.delta.chat
|
||||
uses: up9cloud/action-rsync@v1.3
|
||||
env:
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -3,6 +3,18 @@
|
||||
## Unreleased
|
||||
|
||||
### Changes
|
||||
- Drop unused SQL columns #4141
|
||||
- "full message view" not needed because of footers that go to contact status #4151
|
||||
- Pick up system's light/dark mode in generated message HTML #4150
|
||||
- Support non-persistent configuration with DELTACHAT_* env
|
||||
|
||||
### Fixes
|
||||
- Fix segmentation fault if `dc_context_unref()` is called during
|
||||
background process spawned by `dc_configure()` or `dc_imex()`
|
||||
or `dc_jsonrpc_instance_t` is unreferenced
|
||||
during handling the JSON-RPC request. #4153
|
||||
- Delete expired messages using multiple SQL requests. #4158
|
||||
- Do not emit "Failed to run incremental vacuum" warnings on success. #4160
|
||||
- Ability to send backup over network and QR code to setup second device #4007
|
||||
|
||||
## 1.111.0
|
||||
@@ -22,6 +34,7 @@
|
||||
- jsonrpc: add more advanced API to send a message. #4097
|
||||
- jsonrpc: add get webxdc blob API `getWebxdcBlob` #4070
|
||||
|
||||
|
||||
## 1.110.0
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -19,7 +19,7 @@ $ curl https://sh.rustup.rs -sSf | sh
|
||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
||||
|
||||
```
|
||||
$ RUST_LOG=repl=info cargo run -p deltachat-repl -- ~/deltachat-db
|
||||
$ RUST_LOG=deltachat_repl=info cargo run -p deltachat-repl -- ~/deltachat-db
|
||||
```
|
||||
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.
|
||||
|
||||
@@ -113,7 +113,7 @@ $ cargo build -p deltachat_ffi --release
|
||||
|
||||
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
|
||||
|
||||
- `RUST_LOG=repl=info,async_imap=trace,async_smtp=trace`: enable IMAP and
|
||||
- `RUST_LOG=deltachat_repl=info,async_imap=trace,async_smtp=trace`: enable IMAP and
|
||||
SMTP tracing in addition to info messages.
|
||||
|
||||
### Expensive tests
|
||||
@@ -170,7 +170,9 @@ Language bindings are available for:
|
||||
- over cffi (legacy): \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
|
||||
- over jsonrpc built with napi.rs: \[[📂 source](https://github.com/deltachat/napi-jsonrpc) | [📦 npm](https://www.npmjs.com/package/@deltachat/napi-jsonrpc)\]
|
||||
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
|
||||
- **Go**[^1] \[[📂 source](https://github.com/deltachat/go-deltachat/)\]
|
||||
- **Go**
|
||||
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
||||
- over cffi[^1]: \[[📂 source](https://github.com/deltachat/go-deltachat/)\]
|
||||
- **Free Pascal**[^1] \[[📂 source](https://github.com/deltachat/deltachat-fp/)\]
|
||||
- **Java** and **Swift** (contained in the Android/iOS repos)
|
||||
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
|
||||
:root {
|
||||
--accent: hsl(0 0% 85%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--accent: hsl(0 0% 25%);
|
||||
}
|
||||
}
|
||||
|
||||
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
|
||||
div.fragment {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--accent);
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--accent);
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -6746,7 +6746,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
|
||||
/// "You changed your email address from %1$s to %2$s.
|
||||
/// If you now send a message to a group, contacts there will automatically
|
||||
/// replace the old with your new address.\n\nIt's highly advised to set up
|
||||
/// replace the old with your new address.\n\n It's highly advised to set up
|
||||
/// your old email provider to forward all emails to your new email address.
|
||||
/// Otherwise you might miss messages of contacts who did not get your new
|
||||
/// address yet." + the link to the AEAP blog post
|
||||
|
||||
@@ -293,12 +293,12 @@ pub unsafe extern "C" fn dc_set_stock_translation(
|
||||
Some(id) => match ctx.set_stock_translation(id, msg).await {
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
warn!(ctx, "set_stock_translation failed: {}", err);
|
||||
warn!(ctx, "set_stock_translation failed: {err:#}");
|
||||
0
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!(ctx, "invalid stock message id {}", stock_id);
|
||||
warn!(ctx, "invalid stock message id {stock_id}");
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -321,7 +321,7 @@ pub unsafe extern "C" fn dc_set_config_from_qr(
|
||||
match qr::set_config_from_qr(ctx, &qr).await {
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
error!(ctx, "Failed to create account from QR code: {}", err);
|
||||
error!(ctx, "Failed to create account from QR code: {err:#}");
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -339,7 +339,7 @@ pub unsafe extern "C" fn dc_get_info(context: *const dc_context_t) -> *mut libc:
|
||||
match ctx.get_info().await {
|
||||
Ok(info) => render_info(info).unwrap_or_default().strdup(),
|
||||
Err(err) => {
|
||||
warn!(ctx, "failed to get info: {}", err);
|
||||
warn!(ctx, "failed to get info: {err:#}");
|
||||
"".strdup()
|
||||
}
|
||||
}
|
||||
@@ -380,7 +380,7 @@ pub unsafe extern "C" fn dc_get_connectivity_html(
|
||||
match ctx.get_connectivity_html().await {
|
||||
Ok(html) => html.strdup(),
|
||||
Err(err) => {
|
||||
error!(ctx, "Failed to get connectivity html: {}", err);
|
||||
error!(ctx, "Failed to get connectivity html: {err:#}");
|
||||
"".strdup()
|
||||
}
|
||||
}
|
||||
@@ -422,6 +422,10 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
||||
})
|
||||
}
|
||||
|
||||
fn spawn_configure(ctx: Context) {
|
||||
spawn(async move { ctx.configure().await.log_err(&ctx, "Configure failed") });
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_configure(context: *mut dc_context_t) {
|
||||
if context.is_null() {
|
||||
@@ -430,8 +434,7 @@ pub unsafe extern "C" fn dc_configure(context: *mut dc_context_t) {
|
||||
}
|
||||
|
||||
let ctx = &*context;
|
||||
|
||||
spawn(async move { ctx.configure().await.log_err(ctx, "Configure failed") });
|
||||
spawn_configure(ctx.clone());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -1138,7 +1141,7 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32)
|
||||
}
|
||||
Ok(None) => ptr::null_mut(),
|
||||
Err(err) => {
|
||||
error!(ctx, "Failed to get draft for chat #{}: {}", chat_id, err);
|
||||
error!(ctx, "Failed to get draft for chat #{chat_id}: {err:#}");
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -1728,7 +1731,7 @@ pub unsafe extern "C" fn dc_get_chat_encrinfo(
|
||||
.await
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|e| {
|
||||
error!(ctx, "{}", e);
|
||||
error!(ctx, "{e:#}");
|
||||
ptr::null_mut()
|
||||
})
|
||||
})
|
||||
@@ -1891,7 +1894,7 @@ pub unsafe extern "C" fn dc_resend_msgs(
|
||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
||||
|
||||
if let Err(err) = block_on(chat::resend_msgs(ctx, &msg_ids)) {
|
||||
error!(ctx, "Resending failed: {}", err);
|
||||
error!(ctx, "Resending failed: {err:#}");
|
||||
0
|
||||
} else {
|
||||
1
|
||||
@@ -1932,14 +1935,11 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
|
||||
// C-core API returns empty messages, do the same
|
||||
warn!(
|
||||
ctx,
|
||||
"dc_get_msg called with special msg_id={}, returning empty msg", msg_id
|
||||
"dc_get_msg called with special msg_id={msg_id}, returning empty msg"
|
||||
);
|
||||
message::Message::default()
|
||||
} else {
|
||||
error!(
|
||||
ctx,
|
||||
"dc_get_msg could not retrieve msg_id {}: {}", msg_id, e
|
||||
);
|
||||
error!(ctx, "dc_get_msg could not retrieve msg_id {msg_id}: {e:#}");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
}
|
||||
@@ -2132,7 +2132,7 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo(
|
||||
.await
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(|e| {
|
||||
error!(ctx, "{}", e);
|
||||
error!(ctx, "{e:#}");
|
||||
ptr::null_mut()
|
||||
})
|
||||
})
|
||||
@@ -2154,7 +2154,7 @@ pub unsafe extern "C" fn dc_delete_contact(
|
||||
match Contact::delete(ctx, contact_id).await {
|
||||
Ok(_) => 1,
|
||||
Err(err) => {
|
||||
error!(ctx, "cannot delete contact: {}", err);
|
||||
error!(ctx, "cannot delete contact: {err:#}");
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -2180,6 +2180,14 @@ pub unsafe extern "C" fn dc_get_contact(
|
||||
})
|
||||
}
|
||||
|
||||
fn spawn_imex(ctx: Context, what: imex::ImexMode, param1: String, passphrase: Option<String>) {
|
||||
spawn(async move {
|
||||
imex::imex(&ctx, what, param1.as_ref(), passphrase)
|
||||
.await
|
||||
.log_err(&ctx, "IMEX failed")
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_imex(
|
||||
context: *mut dc_context_t,
|
||||
@@ -2203,11 +2211,7 @@ pub unsafe extern "C" fn dc_imex(
|
||||
let ctx = &*context;
|
||||
|
||||
if let Some(param1) = to_opt_string_lossy(param1) {
|
||||
spawn(async move {
|
||||
imex::imex(ctx, what, param1.as_ref(), passphrase)
|
||||
.await
|
||||
.log_err(ctx, "IMEX failed")
|
||||
});
|
||||
spawn_imex(ctx.clone(), what, param1, passphrase);
|
||||
} else {
|
||||
eprintln!("dc_imex called without a valid directory");
|
||||
}
|
||||
@@ -2230,7 +2234,7 @@ pub unsafe extern "C" fn dc_imex_has_backup(
|
||||
Err(err) => {
|
||||
// do not bubble up error to the user,
|
||||
// the ui will expect that the file does not exist or cannot be accessed
|
||||
warn!(ctx, "dc_imex_has_backup: {}", err);
|
||||
warn!(ctx, "dc_imex_has_backup: {err:#}");
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -2249,7 +2253,7 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
|
||||
match imex::initiate_key_transfer(ctx).await {
|
||||
Ok(res) => res.strdup(),
|
||||
Err(err) => {
|
||||
error!(ctx, "dc_initiate_key_transfer(): {}", err);
|
||||
error!(ctx, "dc_initiate_key_transfer(): {err:#}");
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -2274,7 +2278,7 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
|
||||
{
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
warn!(ctx, "dc_continue_key_transfer: {}", err);
|
||||
warn!(ctx, "dc_continue_key_transfer: {err:#}");
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -2705,7 +2709,7 @@ pub unsafe extern "C" fn dc_chatlist_get_chat_id(
|
||||
match ffi_list.list.get_chat_id(index) {
|
||||
Ok(chat_id) => chat_id.to_u32(),
|
||||
Err(err) => {
|
||||
warn!(ctx, "get_chat_id failed: {}", err);
|
||||
warn!(ctx, "get_chat_id failed: {err:#}");
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -2725,7 +2729,7 @@ pub unsafe extern "C" fn dc_chatlist_get_msg_id(
|
||||
match ffi_list.list.get_msg_id(index) {
|
||||
Ok(msg_id) => msg_id.map_or(0, |msg_id| msg_id.to_u32()),
|
||||
Err(err) => {
|
||||
warn!(ctx, "get_msg_id failed: {}", err);
|
||||
warn!(ctx, "get_msg_id failed: {err:#}");
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -2884,7 +2888,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
|
||||
Ok(Some(p)) => p.to_string_lossy().strdup(),
|
||||
Ok(None) => ptr::null_mut(),
|
||||
Err(err) => {
|
||||
error!(ctx, "failed to get profile image: {:?}", err);
|
||||
error!(ctx, "failed to get profile image: {err:#}");
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -3036,7 +3040,7 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
|
||||
let chat = match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
|
||||
Ok(chat) => chat,
|
||||
Err(err) => {
|
||||
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
|
||||
error!(ctx, "dc_get_chat_info_json() failed to load chat: {err:#}");
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
@@ -3045,7 +3049,7 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
|
||||
Err(err) => {
|
||||
error!(
|
||||
ctx,
|
||||
"dc_get_chat_info_json() failed to get chat info: {}", err
|
||||
"dc_get_chat_info_json() failed to get chat info: {err:#}"
|
||||
);
|
||||
return "".strdup();
|
||||
}
|
||||
@@ -3284,7 +3288,7 @@ pub unsafe extern "C" fn dc_msg_get_webxdc_info(msg: *mut dc_msg_t) -> *mut libc
|
||||
let info = match ffi_msg.message.get_webxdc_info(ctx).await {
|
||||
Ok(info) => info,
|
||||
Err(err) => {
|
||||
error!(ctx, "dc_msg_get_webxdc_info() failed to get info: {}", err);
|
||||
error!(ctx, "dc_msg_get_webxdc_info() failed to get info: {err:#}");
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
@@ -4251,7 +4255,7 @@ impl<T: Default, E: std::fmt::Display> ResultExt<T, E> for Result<T, E> {
|
||||
match self {
|
||||
Ok(t) => t,
|
||||
Err(err) => {
|
||||
error!(context, "{}: {}", message, err);
|
||||
error!(context, "{message}: {err:#}");
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
@@ -4744,6 +4748,12 @@ mod jsonrpc {
|
||||
drop(Box::from_raw(jsonrpc_instance));
|
||||
}
|
||||
|
||||
fn spawn_handle_jsonrpc_request(handle: RpcSession<CommandApi>, request: String) {
|
||||
spawn(async move {
|
||||
handle.handle_incoming(&request).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_jsonrpc_request(
|
||||
jsonrpc_instance: *mut dc_jsonrpc_instance_t,
|
||||
@@ -4754,12 +4764,9 @@ mod jsonrpc {
|
||||
return;
|
||||
}
|
||||
|
||||
let api = &*jsonrpc_instance;
|
||||
let handle = &api.handle;
|
||||
let handle = &(*jsonrpc_instance).handle;
|
||||
let request = to_string_lossy(request);
|
||||
spawn(async move {
|
||||
handle.handle_incoming(&request).await;
|
||||
});
|
||||
spawn_handle_jsonrpc_request(handle.clone(), request);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
@@ -35,7 +35,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 1 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM jobs;", paramsv![])
|
||||
.execute("DELETE FROM jobs;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(1) Jobs reset.");
|
||||
@@ -43,7 +43,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 2 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM acpeerstates;", paramsv![])
|
||||
.execute("DELETE FROM acpeerstates;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(2) Peerstates reset.");
|
||||
@@ -51,7 +51,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 4 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM keypairs;", paramsv![])
|
||||
.execute("DELETE FROM keypairs;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(4) Private keypairs reset.");
|
||||
@@ -59,36 +59,36 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 8 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM contacts WHERE id>9;", paramsv![])
|
||||
.execute("DELETE FROM contacts WHERE id>9;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM chats WHERE id>9;", paramsv![])
|
||||
.execute("DELETE FROM chats WHERE id>9;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM chats_contacts;", paramsv![])
|
||||
.execute("DELETE FROM chats_contacts;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM msgs WHERE id>9;", paramsv![])
|
||||
.execute("DELETE FROM msgs WHERE id>9;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute(
|
||||
"DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';",
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
context.sql().config_cache().write().await.clear();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM leftgrps;", paramsv![])
|
||||
.execute("DELETE FROM leftgrps;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(8) Rest but server config reset.");
|
||||
|
||||
@@ -5,10 +5,13 @@ over standard I/O.
|
||||
|
||||
## Install
|
||||
|
||||
To install run:
|
||||
To download binary pre-builds check the [releases page](https://github.com/deltachat/deltachat-core-rust/releases).
|
||||
Rename the downloaded binary to `deltachat-rpc-server` and add it to your `PATH`.
|
||||
|
||||
To install from source run:
|
||||
|
||||
```sh
|
||||
cargo install --path ../deltachat-rpc-server
|
||||
cargo install --git https://github.com/deltachat/deltachat-core-rust/ deltachat-rpc-server
|
||||
```
|
||||
|
||||
The `deltachat-rpc-server` executable will be installed into `$HOME/.cargo/bin` that should be available
|
||||
|
||||
@@ -72,6 +72,19 @@ def test_configure_canceled(acfactory):
|
||||
pass
|
||||
|
||||
|
||||
def test_configure_unref(tmpdir):
|
||||
"""Test that removing the last reference to the context during ongoing configuration
|
||||
does not result in use-after-free."""
|
||||
from deltachat.capi import ffi, lib
|
||||
|
||||
path = tmpdir.mkdir("test_configure_unref").join("dc.db").strpath
|
||||
dc_context = lib.dc_context_new(ffi.NULL, path.encode("utf8"), ffi.NULL)
|
||||
lib.dc_set_config(dc_context, "addr".encode("utf8"), "foo@x.testrun.org".encode("utf8"))
|
||||
lib.dc_set_config(dc_context, "mail_pw".encode("utf8"), "abc".encode("utf8"))
|
||||
lib.dc_configure(dc_context)
|
||||
lib.dc_context_unref(dc_context)
|
||||
|
||||
|
||||
def test_export_import_self_keys(acfactory, tmpdir, lp):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
@@ -204,13 +217,13 @@ def test_html_message(acfactory, lp):
|
||||
lp.sec("ac1: prepare and send text message to ac2")
|
||||
msg1 = chat.send_text("message0")
|
||||
assert not msg1.has_html()
|
||||
assert msg1.html == ""
|
||||
assert not msg1.html
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
msg2 = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg2.text == "message0"
|
||||
assert not msg2.has_html()
|
||||
assert msg2.html == ""
|
||||
assert not msg2.html
|
||||
|
||||
lp.sec("ac1: prepare and send HTML+text message to ac2")
|
||||
msg1 = Message.new_empty(ac1, "text")
|
||||
@@ -2137,7 +2150,7 @@ def test_status(acfactory):
|
||||
chat12.send_text("hello")
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg.text == "hello"
|
||||
assert msg.get_sender_contact().status == ""
|
||||
assert not msg.get_sender_contact().status
|
||||
|
||||
|
||||
def test_group_quote(acfactory, lp):
|
||||
|
||||
@@ -295,8 +295,8 @@ class TestOfflineChat:
|
||||
assert d["archived"] == chat.is_archived()
|
||||
# assert d["param"] == chat.param
|
||||
assert d["color"] == chat.get_color()
|
||||
assert d["profile_image"] == "" if chat.get_profile_image() is None else chat.get_profile_image()
|
||||
assert d["draft"] == "" if chat.get_draft() is None else chat.get_draft()
|
||||
assert not d["profile_image"] if chat.get_profile_image() is None else chat.get_profile_image()
|
||||
assert not d["draft"] if chat.get_draft() is None else chat.get_draft()
|
||||
|
||||
def test_group_chat_creation_with_translation(self, ac1):
|
||||
ac1.set_stock_translation(const.DC_STR_GROUP_NAME_CHANGED_BY_YOU, "abc %1$s xyz %2$s")
|
||||
|
||||
@@ -20,7 +20,9 @@ and an own build machine.
|
||||
|
||||
- `run_all.sh` builds Python wheels
|
||||
|
||||
- `aarch64-unknown-linux-musl.sh` cross-compiles static `deltachat-rpc-server` for aarch64
|
||||
- `zig-rpc-server.sh` compiles binaries of `deltachat-rpc-server` using Zig toolchain statically linked against musl libc.
|
||||
|
||||
- `android-rpc-server.sh` compiles binaries of `deltachat-rpc-server` using Android NDK.
|
||||
|
||||
## Triggering runs on the build machine locally (fast!)
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Build statically linked deltachat-rpc-server for aarch64-unknown-linux-musl.
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Download Zig
|
||||
rm -fr zig-linux-x86_64-0.10.1 zig-linux-x86_64-0.10.1.tar.xz
|
||||
wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz
|
||||
tar xf zig-linux-x86_64-0.10.1.tar.xz
|
||||
export PATH="$PATH:$PWD/zig-linux-x86_64-0.10.1"
|
||||
|
||||
cargo install cargo-zigbuild
|
||||
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
|
||||
cargo zigbuild --release --target aarch64-unknown-linux-musl -p deltachat-rpc-server --features vendored
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Run clippy for all Rust code in the project.
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
23
scripts/zig-rpc-server.sh
Executable file
23
scripts/zig-rpc-server.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Build statically linked deltachat-rpc-server using cargo-zigbuild.
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
unset RUSTFLAGS
|
||||
|
||||
ZIG_VERSION=0.11.0-dev.1935+1d96a17af
|
||||
|
||||
# Download Zig
|
||||
rm -fr "$ZIG_VERSION" "ZIG_VERSION.tar.xz"
|
||||
wget "https://ziglang.org/builds/zig-linux-x86_64-$ZIG_VERSION.tar.xz"
|
||||
tar xf "zig-linux-x86_64-$ZIG_VERSION.tar.xz"
|
||||
export PATH="$PWD/zig-linux-x86_64-$ZIG_VERSION:$PATH"
|
||||
|
||||
cargo install cargo-zigbuild
|
||||
|
||||
for TARGET in aarch64-unknown-linux-musl armv7-unknown-linux-musleabihf; do
|
||||
rustup target add "$TARGET"
|
||||
cargo zigbuild --release --target "$TARGET" -p deltachat-rpc-server --features vendored
|
||||
done
|
||||
@@ -334,7 +334,7 @@ async fn set_dkim_works_timestamp(
|
||||
async fn clear_dkim_works(context: &Context) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM sending_domains", paramsv![])
|
||||
.execute("DELETE FROM sending_domains", ())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
18
src/chat.rs
18
src/chat.rs
@@ -418,7 +418,7 @@ impl ChatId {
|
||||
ProtectionStatus::Protected => match chat.typ {
|
||||
Chattype::Single | Chattype::Group | Chattype::Broadcast => {
|
||||
let contact_ids = get_chat_contacts(context, self).await?;
|
||||
for contact_id in contact_ids.into_iter() {
|
||||
for contact_id in contact_ids {
|
||||
let contact = Contact::get_by_id(context, contact_id).await?;
|
||||
if contact.is_verified(context).await? != VerifiedStatus::BidirectVerified {
|
||||
bail!("{} is not verified.", contact.get_display_name());
|
||||
@@ -853,7 +853,7 @@ impl ChatId {
|
||||
AND c.blocked=0
|
||||
AND c.archived=1
|
||||
",
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
@@ -2578,7 +2578,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
|
||||
"SELECT DISTINCT(m.chat_id) FROM msgs m
|
||||
LEFT JOIN chats c ON m.chat_id=c.id
|
||||
WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.blocked=0 AND c.archived=1",
|
||||
paramsv![],
|
||||
(),
|
||||
|row| row.get::<_, ChatId>(0),
|
||||
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into)
|
||||
)
|
||||
@@ -3500,10 +3500,7 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
|
||||
// no database, no chats - this is no error (needed eg. for information)
|
||||
let count = context
|
||||
.sql
|
||||
.count(
|
||||
"SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;",
|
||||
paramsv![],
|
||||
)
|
||||
.count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
|
||||
.await?;
|
||||
Ok(count)
|
||||
} else {
|
||||
@@ -3676,17 +3673,14 @@ pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Resul
|
||||
paramsv![ContactId::DEVICE],
|
||||
)
|
||||
.await?;
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM devmsglabels;", paramsv![])
|
||||
.await?;
|
||||
context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
|
||||
|
||||
// Insert labels for welcome messages to avoid them being readded on reconfiguration.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await?;
|
||||
context.set_config(Config::QuotaExceeding, None).await?;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! # Key-value configuration management.
|
||||
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
@@ -317,6 +318,11 @@ impl Context {
|
||||
|
||||
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
|
||||
pub async fn get_config(&self, key: Config) -> Result<Option<String>> {
|
||||
let env_key = format!("DELTACHAT_{}", key.as_ref().to_uppercase());
|
||||
if let Ok(value) = env::var(env_key) {
|
||||
return Ok(Some(value));
|
||||
}
|
||||
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_raw_config(key.as_ref()).await?;
|
||||
@@ -418,7 +424,7 @@ impl Context {
|
||||
match key {
|
||||
Config::Selfavatar => {
|
||||
self.sql
|
||||
.execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![])
|
||||
.execute("UPDATE contacts SET selfavatar_sent=0;", ())
|
||||
.await?;
|
||||
match value {
|
||||
Some(value) => {
|
||||
|
||||
@@ -727,7 +727,7 @@ impl Contact {
|
||||
pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
|
||||
let mut modify_cnt = 0;
|
||||
|
||||
for (name, addr) in split_address_book(addr_book).into_iter() {
|
||||
for (name, addr) in split_address_book(addr_book) {
|
||||
let (name, addr) = sanitize_name_and_addr(name, addr);
|
||||
let name = normalize_name(&name);
|
||||
match ContactAddress::new(&addr) {
|
||||
|
||||
@@ -622,7 +622,7 @@ impl Context {
|
||||
.unwrap_or_default();
|
||||
let journal_mode = self
|
||||
.sql
|
||||
.query_get_value("PRAGMA journal_mode;", paramsv![])
|
||||
.query_get_value("PRAGMA journal_mode;", ())
|
||||
.await?
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
|
||||
@@ -630,14 +630,11 @@ impl Context {
|
||||
let bcc_self = self.get_config_int(Config::BccSelf).await?;
|
||||
let send_sync_msgs = self.get_config_int(Config::SendSyncMsgs).await?;
|
||||
|
||||
let prv_key_cnt = self
|
||||
.sql
|
||||
.count("SELECT COUNT(*) FROM keypairs;", paramsv![])
|
||||
.await?;
|
||||
let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
|
||||
|
||||
let pub_key_cnt = self
|
||||
.sql
|
||||
.count("SELECT COUNT(*) FROM acpeerstates;", paramsv![])
|
||||
.count("SELECT COUNT(*) FROM acpeerstates;", ())
|
||||
.await?;
|
||||
let fingerprint_str = match SignedPublicKey::load_self(self).await {
|
||||
Ok(key) => key.fingerprint().hex(),
|
||||
|
||||
@@ -68,7 +68,7 @@ use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use anyhow::{ensure, Context as _, Result};
|
||||
use anyhow::{ensure, Result};
|
||||
use async_channel::Receiver;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::time::timeout;
|
||||
@@ -433,37 +433,40 @@ pub(crate) async fn delete_expired_messages(context: &Context, now: i64) -> Resu
|
||||
let rows = select_expired_messages(context, now).await?;
|
||||
|
||||
if !rows.is_empty() {
|
||||
context
|
||||
info!(context, "Attempting to delete {} messages.", rows.len());
|
||||
|
||||
let (msgs_changed, webxdc_deleted) = context
|
||||
.sql
|
||||
.execute(
|
||||
.transaction(|transaction| {
|
||||
let mut msgs_changed = Vec::with_capacity(rows.len());
|
||||
let mut webxdc_deleted = Vec::new();
|
||||
|
||||
// If you change which information is removed here, also change MsgId::trash() and
|
||||
// which information receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
||||
&format!(
|
||||
r#"
|
||||
UPDATE msgs
|
||||
SET
|
||||
chat_id=?, txt='', subject='', txt_raw='',
|
||||
mime_headers='', from_id=0, to_id=0, param=''
|
||||
WHERE id IN ({})
|
||||
"#,
|
||||
sql::repeat_vars(rows.len())
|
||||
),
|
||||
rusqlite::params_from_iter(
|
||||
std::iter::once(&DC_CHAT_ID_TRASH as &dyn crate::ToSql).chain(
|
||||
rows.iter()
|
||||
.map(|(msg_id, _chat_id, _viewtype)| msg_id as &dyn crate::ToSql),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context("update failed")?;
|
||||
for (msg_id, chat_id, viewtype) in rows {
|
||||
transaction.execute(
|
||||
"UPDATE msgs
|
||||
SET chat_id=?, txt='', subject='', txt_raw='',
|
||||
mime_headers='', from_id=0, to_id=0, param=''
|
||||
WHERE id=?",
|
||||
params![DC_CHAT_ID_TRASH, msg_id],
|
||||
)?;
|
||||
|
||||
for (msg_id, chat_id, viewtype) in rows {
|
||||
msgs_changed.push((chat_id, msg_id));
|
||||
if viewtype == Viewtype::Webxdc {
|
||||
webxdc_deleted.push(msg_id)
|
||||
}
|
||||
}
|
||||
Ok((msgs_changed, webxdc_deleted))
|
||||
})
|
||||
.await?;
|
||||
|
||||
for (chat_id, msg_id) in msgs_changed {
|
||||
context.emit_msgs_changed(chat_id, msg_id);
|
||||
}
|
||||
|
||||
if viewtype == Viewtype::Webxdc {
|
||||
context.emit_event(EventType::WebxdcInstanceDeleted { msg_id });
|
||||
}
|
||||
for msg_id in webxdc_deleted {
|
||||
context.emit_event(EventType::WebxdcInstanceDeleted { msg_id });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,7 +1173,7 @@ mod tests {
|
||||
// No other messages are marked for deletion.
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM imap WHERE target=''", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM imap WHERE target=''", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
@@ -1184,10 +1187,7 @@ mod tests {
|
||||
.update_download_state(&t, DownloadState::Available)
|
||||
.await?;
|
||||
t.sql
|
||||
.execute(
|
||||
"UPDATE imap SET target=folder WHERE rfc724_mid='1000'",
|
||||
paramsv![],
|
||||
)
|
||||
.execute("UPDATE imap SET target=folder WHERE rfc724_mid='1000'", ())
|
||||
.await?;
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 1000).await?; // Delete downloadable anyway.
|
||||
@@ -1198,10 +1198,7 @@ mod tests {
|
||||
delete_expired_imap_messages(&t).await?;
|
||||
test_marked_for_deletion(&t, 1010).await?;
|
||||
t.sql
|
||||
.execute(
|
||||
"UPDATE imap SET target=folder WHERE rfc724_mid='1010'",
|
||||
paramsv![],
|
||||
)
|
||||
.execute("UPDATE imap SET target=folder WHERE rfc724_mid='1010'", ())
|
||||
.await?;
|
||||
|
||||
MsgId::new(1010)
|
||||
@@ -1211,7 +1208,7 @@ mod tests {
|
||||
// Keep downloadable for now.
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM imap WHERE target=''", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM imap WHERE target=''", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
|
||||
24
src/html.rs
24
src/html.rs
@@ -124,7 +124,7 @@ impl HtmlMsgParser {
|
||||
async move {
|
||||
match get_mime_multipart_type(&mail.ctype) {
|
||||
MimeMultipartType::Multiple => {
|
||||
for cur_data in mail.subparts.iter() {
|
||||
for cur_data in &mail.subparts {
|
||||
self.collect_texts_recursive(cur_data).await?
|
||||
}
|
||||
Ok(())
|
||||
@@ -180,7 +180,7 @@ impl HtmlMsgParser {
|
||||
async move {
|
||||
match get_mime_multipart_type(&mail.ctype) {
|
||||
MimeMultipartType::Multiple => {
|
||||
for cur_data in mail.subparts.iter() {
|
||||
for cur_data in &mail.subparts {
|
||||
self.cid_to_data_recursive(context, cur_data).await?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -292,7 +292,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
This message does not have Content-Type nor Subject.<br/>
|
||||
<br/>
|
||||
</body></html>
|
||||
@@ -308,7 +311,10 @@ This message does not have Content-Type nor Subject.<br/>
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||
<br/>
|
||||
</body></html>
|
||||
@@ -325,7 +331,10 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
This line ends with a space and will be merged with the next one due to format=flowed.<br/>
|
||||
<br/>
|
||||
This line does not end with a space<br/>
|
||||
@@ -344,7 +353,10 @@ and will be wrapped as usual.<br/>
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r##"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
mime-modified should not be set set as there is no html and no special stuff;<br/>
|
||||
although not being a delta-message.<br/>
|
||||
test some special html-characters as < > and & but also " and ' :)<br/>
|
||||
|
||||
@@ -1390,7 +1390,7 @@ impl Imap {
|
||||
let mut uid_msgs = HashMap::with_capacity(request_uids.len());
|
||||
|
||||
let mut count = 0;
|
||||
for &request_uid in request_uids.iter() {
|
||||
for &request_uid in &request_uids {
|
||||
// Check if FETCH response is already in `uid_msgs`.
|
||||
let mut fetch_response = uid_msgs.remove(&request_uid);
|
||||
|
||||
|
||||
@@ -654,7 +654,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> {
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||
paramsv![],
|
||||
(),
|
||||
|row| {
|
||||
let id = row.get(0)?;
|
||||
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||
|
||||
@@ -148,7 +148,7 @@ impl DcKey for SignedSecretKey {
|
||||
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
||||
AND is_default=1;
|
||||
"#,
|
||||
paramsv![],
|
||||
(),
|
||||
|row| {
|
||||
let bytes: Vec<u8> = row.get(0)?;
|
||||
Ok(bytes)
|
||||
@@ -302,7 +302,7 @@ pub async fn store_self_keypair(
|
||||
.context("failed to remove old use of key")?;
|
||||
if default == KeyPairUse::Default {
|
||||
transaction
|
||||
.execute("UPDATE keypairs SET is_default=0;", paramsv![])
|
||||
.execute("UPDATE keypairs SET is_default=0;", ())
|
||||
.context("failed to clear default")?;
|
||||
}
|
||||
let is_default = match default {
|
||||
@@ -592,7 +592,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
|
||||
let nrows = || async {
|
||||
ctx.sql
|
||||
.count("SELECT COUNT(*) FROM keypairs;", paramsv![])
|
||||
.count("SELECT COUNT(*) FROM keypairs;", ())
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
clippy::wildcard_imports,
|
||||
clippy::needless_borrow,
|
||||
clippy::cast_lossless,
|
||||
clippy::unused_async
|
||||
clippy::unused_async,
|
||||
clippy::explicit_iter_loop,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::cloned_instead_of_copied
|
||||
)]
|
||||
#![allow(
|
||||
clippy::match_bool,
|
||||
|
||||
@@ -434,10 +434,7 @@ fn is_marker(txt: &str) -> bool {
|
||||
|
||||
/// Deletes all locations from the database.
|
||||
pub async fn delete_all(context: &Context) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM locations;", paramsv![])
|
||||
.await?;
|
||||
context.sql.execute("DELETE FROM locations;", ()).await?;
|
||||
context.emit_event(EventType::LocationChanged(None));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1502,7 +1502,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
|
||||
curr_rfc724_mid,
|
||||
curr_blocked,
|
||||
_curr_ephemeral_timer,
|
||||
) in msgs.into_iter()
|
||||
) in msgs
|
||||
{
|
||||
if curr_blocked == Blocked::Not
|
||||
&& (curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed)
|
||||
@@ -1741,7 +1741,7 @@ pub(crate) async fn handle_ndn(
|
||||
};
|
||||
|
||||
let mut first = true;
|
||||
for msg in msgs.into_iter() {
|
||||
for msg in msgs {
|
||||
let (msg_id, chat_id, chat_type) = msg?;
|
||||
set_msg_failed(context, msg_id, &error).await;
|
||||
if first {
|
||||
@@ -1794,7 +1794,7 @@ pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
|
||||
"SELECT COUNT(*) \
|
||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -1814,7 +1814,7 @@ pub async fn get_request_msg_cnt(context: &Context) -> usize {
|
||||
"SELECT COUNT(*) \
|
||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE c.blocked=2;",
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -485,7 +485,7 @@ impl<'a> MimeFactory<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
for (name, addr) in self.recipients.iter() {
|
||||
for (name, addr) in &self.recipients {
|
||||
if let Some(email_to_remove) = email_to_remove {
|
||||
if email_to_remove == addr {
|
||||
continue;
|
||||
|
||||
@@ -640,7 +640,7 @@ impl MimeMessage {
|
||||
}
|
||||
|
||||
if self.is_forwarded {
|
||||
for part in self.parts.iter_mut() {
|
||||
for part in &mut self.parts {
|
||||
part.param.set_int(Param::Forwarded, 1);
|
||||
}
|
||||
}
|
||||
@@ -943,7 +943,7 @@ impl MimeMessage {
|
||||
}
|
||||
|
||||
// Add all parts (we need another part, preferably text/plain, to show as an error message)
|
||||
for cur_data in mail.subparts.iter() {
|
||||
for cur_data in &mail.subparts {
|
||||
if self
|
||||
.parse_mime_recursive(context, cur_data, is_related)
|
||||
.await?
|
||||
@@ -977,7 +977,7 @@ impl MimeMessage {
|
||||
_ => {
|
||||
// Add all parts (in fact, AddSinglePartIfKnown() later check if
|
||||
// the parts are really supported)
|
||||
for cur_data in mail.subparts.iter() {
|
||||
for cur_data in &mail.subparts {
|
||||
if self
|
||||
.parse_mime_recursive(context, cur_data, is_related)
|
||||
.await?
|
||||
|
||||
@@ -57,7 +57,7 @@ async fn lookup_host_with_cache(
|
||||
}
|
||||
};
|
||||
|
||||
for addr in resolved_addrs.iter() {
|
||||
for addr in &resolved_addrs {
|
||||
let ip_string = addr.ip().to_string();
|
||||
if ip_string == hostname {
|
||||
// IP address resolved into itself, not interesting to cache.
|
||||
|
||||
@@ -718,7 +718,7 @@ pub(crate) async fn deduplicate_peerstates(sql: &Sql) -> Result<()> {
|
||||
FROM acpeerstates
|
||||
GROUP BY addr
|
||||
)",
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -34,8 +34,13 @@ impl PlainText {
|
||||
|
||||
let lines = split_lines(&self.text);
|
||||
|
||||
let mut ret =
|
||||
"<!DOCTYPE html>\n<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body>\n".to_string();
|
||||
let mut ret = r#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
for line in lines {
|
||||
let is_quote = line.starts_with('>');
|
||||
@@ -118,7 +123,10 @@ http://link-at-start-of-line.org
|
||||
assert_eq!(
|
||||
html,
|
||||
r##"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
line 1<br/>
|
||||
line 2<br/>
|
||||
line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a> and <a href="http://link-end-of-line.com/file?foo=bar%20">http://link-end-of-line.com/file?foo=bar%20</a><br/>
|
||||
@@ -140,7 +148,10 @@ line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a
|
||||
assert_eq!(
|
||||
html,
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
line with <<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.link/?foo=_bar</a>> here!<br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -158,7 +169,10 @@ line with <<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.l
|
||||
assert_eq!(
|
||||
html,
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
line with nohttp://no.link here<br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -176,7 +190,10 @@ line with nohttp://no.link here<br/>
|
||||
assert_eq!(
|
||||
html,
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
just an address: <a href="mailto:foo@bar.org">foo@bar.org</a> <a href="mailto:another@one.de">another@one.de</a><br/>
|
||||
</body></html>
|
||||
"#
|
||||
@@ -194,7 +211,10 @@ just an address: <a href="mailto:foo@bar.org">foo@bar.org</a> <a href="mailto:an
|
||||
assert_eq!(
|
||||
html,
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
line still line<br/>
|
||||
<em>>quote </em><br/>
|
||||
<em>>still quote</em><br/>
|
||||
@@ -215,7 +235,10 @@ line still line<br/>
|
||||
assert_eq!(
|
||||
html,
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
linestill line<br/>
|
||||
<em>>quote </em><br/>
|
||||
<em>>still quote</em><br/>
|
||||
@@ -236,7 +259,10 @@ linestill line<br/>
|
||||
assert_eq!(
|
||||
html,
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body>
|
||||
line <br/>
|
||||
still line<br/>
|
||||
<em>>quote </em><br/>
|
||||
|
||||
@@ -604,7 +604,7 @@ async fn add_parts(
|
||||
// to the sender's name, indicating to the user that he/she is not part of the group.
|
||||
let from = &mime_parser.from;
|
||||
let name: &str = from.display_name.as_ref().unwrap_or(&from.addr);
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
for part in &mut mime_parser.parts {
|
||||
part.param.set(Param::OverrideSenderDisplayname, name);
|
||||
}
|
||||
}
|
||||
@@ -668,7 +668,7 @@ async fn add_parts(
|
||||
// we use name from From:-header as override name
|
||||
if prevent_rename {
|
||||
if let Some(name) = &mime_parser.from.display_name {
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
for part in &mut mime_parser.parts {
|
||||
part.param.set(Param::OverrideSenderDisplayname, name);
|
||||
}
|
||||
}
|
||||
@@ -737,7 +737,7 @@ async fn add_parts(
|
||||
// the mail is on the IMAP server, probably it is also delivered.
|
||||
// We cannot recreate other states (read, error).
|
||||
state = MessageState::OutDelivered;
|
||||
to_id = to_ids.get(0).cloned().unwrap_or_default();
|
||||
to_id = to_ids.get(0).copied().unwrap_or_default();
|
||||
|
||||
let self_sent =
|
||||
from_id == ContactId::SELF && to_ids.len() == 1 && to_ids.contains(&ContactId::SELF);
|
||||
@@ -2162,7 +2162,7 @@ async fn check_verified_properties(
|
||||
)
|
||||
.await?;
|
||||
|
||||
for (to_addr, mut is_verified) in rows.into_iter() {
|
||||
for (to_addr, mut is_verified) in rows {
|
||||
info!(
|
||||
context,
|
||||
"check_verified_properties: {:?} self={:?}",
|
||||
|
||||
@@ -2085,9 +2085,11 @@ Original signature",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let one2one_chat_id = t.get_last_msg().await.chat_id;
|
||||
let msg = t.get_last_msg().await;
|
||||
let one2one_chat_id = msg.chat_id;
|
||||
let bob = Contact::load_from_db(&t, bob_id).await?;
|
||||
assert_eq!(bob.get_status(), "Original signature");
|
||||
assert!(!msg.has_html());
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
|
||||
@@ -163,7 +163,7 @@ impl BobState {
|
||||
// guaranteed to only have one row.
|
||||
sql.query_row_optional(
|
||||
"SELECT id, invite, next_step, chat_id FROM bobstate;",
|
||||
paramsv![],
|
||||
(),
|
||||
|row| {
|
||||
let s = BobState {
|
||||
id: row.get(0)?,
|
||||
|
||||
@@ -80,7 +80,8 @@ pub(crate) struct SimplifiedText {
|
||||
/// True if the message is forwarded.
|
||||
pub is_forwarded: bool,
|
||||
|
||||
/// True if nonstandard footer was removed.
|
||||
/// True if nonstandard footer was removed
|
||||
/// or if the message contains quotes other than `top_quote`.
|
||||
pub is_cut: bool,
|
||||
|
||||
/// Top quote, if any.
|
||||
@@ -103,7 +104,6 @@ pub(crate) fn simplify(mut input: String, is_chat_message: bool) -> SimplifiedTe
|
||||
let original_lines = &lines;
|
||||
let (lines, footer_lines) = remove_message_footer(lines);
|
||||
let footer = footer_lines.map(|footer_lines| render_message(footer_lines, false));
|
||||
is_cut = is_cut || footer.is_some();
|
||||
|
||||
let text = if is_chat_message {
|
||||
render_message(lines, false)
|
||||
@@ -330,7 +330,7 @@ mod tests {
|
||||
} = simplify(input, true);
|
||||
assert_eq!(text, "Hi! How are you?\n\n---\n\nI am good.");
|
||||
assert!(!is_forwarded);
|
||||
assert!(is_cut);
|
||||
assert!(!is_cut);
|
||||
assert_eq!(
|
||||
footer.unwrap(),
|
||||
"Sent with my Delta Chat Messenger: https://delta.chat"
|
||||
@@ -365,7 +365,7 @@ mod tests {
|
||||
|
||||
assert_eq!(text, "Forwarded message");
|
||||
assert!(is_forwarded);
|
||||
assert!(is_cut);
|
||||
assert!(!is_cut);
|
||||
assert_eq!(footer.unwrap(), "Signature goes here");
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ mod tests {
|
||||
..
|
||||
} = simplify(input, true);
|
||||
assert_eq!(text, "text\n\n--\nno footer");
|
||||
assert!(is_cut);
|
||||
assert!(!is_cut);
|
||||
assert_eq!(footer.unwrap(), "footer");
|
||||
|
||||
let input = "text\n\n--\ntreated as footer when unescaped".to_string();
|
||||
@@ -453,7 +453,7 @@ mod tests {
|
||||
..
|
||||
} = simplify(input.clone(), true);
|
||||
assert_eq!(text, "text"); // see remove_message_footer() for some explanations
|
||||
assert!(is_cut);
|
||||
assert!(!is_cut);
|
||||
assert_eq!(footer.unwrap(), "treated as footer when unescaped");
|
||||
let escaped = escape_message_footer_marks(&input);
|
||||
let SimplifiedText {
|
||||
@@ -495,7 +495,7 @@ mod tests {
|
||||
..
|
||||
} = simplify(input.clone(), true);
|
||||
assert_eq!(text, ""); // see remove_message_footer() for some explanations
|
||||
assert!(is_cut);
|
||||
assert!(!is_cut);
|
||||
assert_eq!(footer.unwrap(), "treated as footer when unescaped");
|
||||
|
||||
let escaped = escape_message_footer_marks(&input);
|
||||
|
||||
24
src/sql.rs
24
src/sql.rs
@@ -220,7 +220,7 @@ impl Sql {
|
||||
let addrs = self
|
||||
.query_map(
|
||||
"SELECT addr FROM acpeerstates;",
|
||||
paramsv![],
|
||||
(),
|
||||
|row| row.get::<_, String>(0),
|
||||
|addrs| {
|
||||
addrs
|
||||
@@ -713,12 +713,22 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
|
||||
// Try to clear the freelist to free some space on the disk. This
|
||||
// only works if auto_vacuum is enabled.
|
||||
if let Err(err) = context
|
||||
match context
|
||||
.sql
|
||||
.execute("PRAGMA incremental_vacuum", paramsv![])
|
||||
.query_row_optional("PRAGMA incremental_vacuum", (), |_row| Ok(()))
|
||||
.await
|
||||
{
|
||||
warn!(context, "Failed to run incremental vacuum: {}", err);
|
||||
Err(err) => {
|
||||
warn!(context, "Failed to run incremental vacuum: {err:#}");
|
||||
}
|
||||
Ok(Some(())) => {
|
||||
// Incremental vacuum returns a zero-column result if it did anything.
|
||||
info!(context, "Successfully ran incremental vacuum.");
|
||||
}
|
||||
Ok(None) => {
|
||||
// Incremental vacuum returned `SQLITE_DONE` immediately,
|
||||
// there were no pages to remove.
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = context
|
||||
@@ -732,7 +742,7 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM msgs_mdns WHERE msg_id NOT IN (SELECT id FROM msgs)",
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.ok_or_log_msg(context, "failed to remove old MDNs");
|
||||
@@ -780,7 +790,7 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT value FROM config;",
|
||||
paramsv![],
|
||||
(),
|
||||
|row| row.get::<_, String>(0),
|
||||
|rows| {
|
||||
for row in rows {
|
||||
@@ -915,7 +925,7 @@ async fn maybe_add_from_param(
|
||||
) -> Result<()> {
|
||||
sql.query_map(
|
||||
query,
|
||||
paramsv![],
|
||||
(),
|
||||
|row| row.get::<_, String>(0),
|
||||
|rows| {
|
||||
for row in rows {
|
||||
|
||||
@@ -391,7 +391,7 @@ UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
|
||||
sql.execute(
|
||||
r#"
|
||||
CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#,
|
||||
paramsv![]
|
||||
()
|
||||
)
|
||||
.await?;
|
||||
for c in &[
|
||||
@@ -690,6 +690,17 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
|
||||
}
|
||||
sql.set_db_version(98).await?;
|
||||
}
|
||||
if dbversion < 99 {
|
||||
sql.execute_migration(
|
||||
"ALTER TABLE msgs DROP COLUMN server_folder;
|
||||
ALTER TABLE msgs DROP COLUMN server_uid;
|
||||
ALTER TABLE msgs DROP COLUMN move_state;
|
||||
ALTER TABLE chats DROP COLUMN draft_timestamp;
|
||||
ALTER TABLE chats DROP COLUMN draft_txt",
|
||||
99,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
|
||||
@@ -155,7 +155,7 @@ impl Context {
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, item FROM multi_device_sync ORDER BY id;",
|
||||
paramsv![],
|
||||
(),
|
||||
|row| Ok((row.get::<_, u32>(0)?, row.get::<_, String>(1)?)),
|
||||
|rows| {
|
||||
let mut ids = vec![];
|
||||
@@ -201,7 +201,7 @@ impl Context {
|
||||
self.sql
|
||||
.execute(
|
||||
&format!("DELETE FROM multi_device_sync WHERE id IN ({ids});"),
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -403,7 +403,7 @@ impl TestContext {
|
||||
SELECT id, msg_id, mime, recipients
|
||||
FROM smtp
|
||||
ORDER BY id DESC"#,
|
||||
paramsv![],
|
||||
(),
|
||||
|row| {
|
||||
let rowid: i64 = row.get(0)?;
|
||||
let msg_id: MsgId = row.get(1)?;
|
||||
@@ -604,7 +604,6 @@ impl TestContext {
|
||||
/// [`TestContext::recv_msg`] with the returned [`SentMessage`] if it wants to receive
|
||||
/// the message.
|
||||
pub async fn send_msg(&self, chat_id: ChatId, msg: &mut Message) -> SentMessage<'_> {
|
||||
chat::prepare_msg(self, chat_id, msg).await.unwrap();
|
||||
let msg_id = chat::send_msg(self, chat_id, msg).await.unwrap();
|
||||
let res = self.pop_sent_msg().await;
|
||||
assert_eq!(
|
||||
|
||||
@@ -438,7 +438,7 @@ impl Context {
|
||||
"DELETE FROM smtp_status_updates
|
||||
WHERE msg_id IN (SELECT msg_id FROM smtp_status_updates LIMIT 1)
|
||||
RETURNING msg_id, first_serial, last_serial, descr",
|
||||
paramsv![],
|
||||
(),
|
||||
|row| {
|
||||
let instance_id: MsgId = row.get(0)?;
|
||||
let first_serial: StatusUpdateSerial = row.get(1)?;
|
||||
@@ -1195,7 +1195,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM msgs_status_updates;", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM msgs_status_updates;", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
@@ -1543,14 +1543,14 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", ())
|
||||
.await?,
|
||||
1
|
||||
);
|
||||
t.flush_status_updates().await?;
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
@@ -1580,7 +1580,7 @@ mod tests {
|
||||
.await?;
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", ())
|
||||
.await?,
|
||||
3
|
||||
);
|
||||
@@ -1606,7 +1606,7 @@ mod tests {
|
||||
}
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", ())
|
||||
.await?,
|
||||
2 - i
|
||||
);
|
||||
@@ -1648,7 +1648,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
alice
|
||||
.sql
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM smtp_status_updates", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
@@ -2438,7 +2438,7 @@ sth_for_the = "future""#
|
||||
assert_eq!(
|
||||
alice
|
||||
.sql
|
||||
.count("SELECT COUNT(*) FROM msgs_status_updates;", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM msgs_status_updates;", ())
|
||||
.await?,
|
||||
0
|
||||
);
|
||||
@@ -2461,7 +2461,7 @@ sth_for_the = "future""#
|
||||
assert!(
|
||||
alice
|
||||
.sql
|
||||
.count("SELECT COUNT(*) FROM msgs_status_updates;", paramsv![],)
|
||||
.count("SELECT COUNT(*) FROM msgs_status_updates;", ())
|
||||
.await?
|
||||
> 0
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user