Merge remote-tracking branch 'origin/master' into flub/send-backup

This commit is contained in:
dignifiedquire
2023-03-16 17:50:00 +01:00
43 changed files with 329 additions and 213 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -19,7 +19,7 @@ $ curl https://sh.rustup.rs -sSf | sh
Compile and run Delta Chat Core command line utility, using `cargo`:
```
$ RUST_LOG=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)

View File

@@ -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;

View File

@@ -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

View File

@@ -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]

View File

@@ -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.");

View File

@@ -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

View File

@@ -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):

View File

@@ -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")

View File

@@ -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!)

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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(())
}

View File

@@ -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?;

View File

@@ -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) => {

View File

@@ -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) {

View File

@@ -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(),

View File

@@ -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
);

View File

@@ -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 &lt; &gt; and &amp; but also &quot; and &#x27; :)<br/>

View File

@@ -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);

View File

@@ -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)?;

View File

@@ -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()
};

View File

@@ -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,

View File

@@ -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(())
}

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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?

View File

@@ -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.

View File

@@ -718,7 +718,7 @@ pub(crate) async fn deduplicate_peerstates(sql: &Sql) -> Result<()> {
FROM acpeerstates
GROUP BY addr
)",
paramsv![],
(),
)
.await?;

View File

@@ -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 &lt;<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.link/?foo=_bar</a>&gt; here!<br/>
</body></html>
"#
@@ -158,7 +169,10 @@ line with &lt;<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>&gt;quote </em><br/>
<em>&gt;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>&gt;quote </em><br/>
<em>&gt;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>&gt;quote </em><br/>

View File

@@ -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={:?}",

View File

@@ -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,

View File

@@ -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)?,

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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(())

View File

@@ -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!(

View File

@@ -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
);