diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 286ba3757..59e7810d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/deltachat-rpc-server.yml b/.github/workflows/deltachat-rpc-server.yml index f073474f6..194b67ddc 100644 --- a/.github/workflows/deltachat-rpc-server.yml +++ b/.github/workflows/deltachat-rpc-server.yml @@ -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 diff --git a/.github/workflows/upload-docs.yml b/.github/workflows/upload-docs.yml index 8f0ceb3c0..1e813855a 100644 --- a/.github/workflows/upload-docs.yml +++ b/.github/workflows/upload-docs.yml @@ -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: diff --git a/CHANGELOG.md b/CHANGELOG.md index 20443be9f..02dbcd118 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/README.md b/README.md index 7e9f6b1eb..937967a1c 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/deltachat-ffi/Doxyfile.css b/deltachat-ffi/Doxyfile.css index 9ff6816ed..fbd8a4354 100644 --- a/deltachat-ffi/Doxyfile.css +++ b/deltachat-ffi/Doxyfile.css @@ -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; diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 4aab53276..f20ea9b66 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -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 diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 9e6edb7c4..8101eda93 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -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) { + 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 ResultExt for Result { 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, 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] diff --git a/deltachat-repl/src/cmdline.rs b/deltachat-repl/src/cmdline.rs index acc9006fc..9fdc8e91e 100644 --- a/deltachat-repl/src/cmdline.rs +++ b/deltachat-repl/src/cmdline.rs @@ -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."); diff --git a/deltachat-rpc-server/README.md b/deltachat-rpc-server/README.md index f64ec1cae..2027072a5 100644 --- a/deltachat-rpc-server/README.md +++ b/deltachat-rpc-server/README.md @@ -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 diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 7f62dcefd..412247cbb 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -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): diff --git a/python/tests/test_3_offline.py b/python/tests/test_3_offline.py index e7a1a6231..e9a297681 100644 --- a/python/tests/test_3_offline.py +++ b/python/tests/test_3_offline.py @@ -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") diff --git a/scripts/README.md b/scripts/README.md index af6a6addd..d8fdf0360 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -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!) diff --git a/scripts/aarch64-unknown-linux-musl.sh b/scripts/aarch64-unknown-linux-musl.sh deleted file mode 100755 index 272574fce..000000000 --- a/scripts/aarch64-unknown-linux-musl.sh +++ /dev/null @@ -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 diff --git a/scripts/clippy.sh b/scripts/clippy.sh index f462c4651..986d03325 100755 --- a/scripts/clippy.sh +++ b/scripts/clippy.sh @@ -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 diff --git a/scripts/zig-rpc-server.sh b/scripts/zig-rpc-server.sh new file mode 100755 index 000000000..f18f301ca --- /dev/null +++ b/scripts/zig-rpc-server.sh @@ -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 diff --git a/src/authres.rs b/src/authres.rs index eb267ddc9..41e45e8cd 100644 --- a/src/authres.rs +++ b/src/authres.rs @@ -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(()) } diff --git a/src/chat.rs b/src/chat.rs index 06b029dd8..0442dea3c 100644 --- a/src/chat.rs +++ b/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::, _>>().map_err(Into::into) ) @@ -3500,10 +3500,7 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> Result { // 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?; diff --git a/src/config.rs b/src/config.rs index 2fa64ae96..76dca83a2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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> { + 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) => { diff --git a/src/contact.rs b/src/contact.rs index 2ae958603..4b8c4c3ab 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -727,7 +727,7 @@ impl Contact { pub async fn add_address_book(context: &Context, addr_book: &str) -> Result { 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) { diff --git a/src/context.rs b/src/context.rs index 03f634fe9..0a0fa45e6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -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(), diff --git a/src/ephemeral.rs b/src/ephemeral.rs index 507c65e57..f7dadf5d8 100644 --- a/src/ephemeral.rs +++ b/src/ephemeral.rs @@ -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 ); diff --git a/src/html.rs b/src/html.rs index d74a5bbac..a76214bc6 100644 --- a/src/html.rs +++ b/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##" - + + + + This message does not have Content-Type nor Subject.

@@ -308,7 +311,10 @@ This message does not have Content-Type nor Subject.
assert_eq!( parser.html, r##" - + + + + message with a non-UTF-8 encoding: Γ€ΓΆΓΌΓŸΓ„Γ–Γœ

@@ -325,7 +331,10 @@ message with a non-UTF-8 encoding: Γ€ΓΆΓΌΓŸΓ„Γ–Γœ
assert_eq!( parser.html, r##" - + + + + This line ends with a space and will be merged with the next one due to format=flowed.

This line does not end with a space
@@ -344,7 +353,10 @@ and will be wrapped as usual.
assert_eq!( parser.html, r##" - + + + + mime-modified should not be set set as there is no html and no special stuff;
although not being a delta-message.
test some special html-characters as < > and & but also " and ' :)
diff --git a/src/imap.rs b/src/imap.rs index 2ee391d3e..91f6ae9cc 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -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); diff --git a/src/imex.rs b/src/imex.rs index 141b91500..a1c2fd627 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -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 = row.get(1)?; diff --git a/src/key.rs b/src/key.rs index 3b48fe19e..74fb997cf 100644 --- a/src/key.rs +++ b/src/key.rs @@ -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 = 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() }; diff --git a/src/lib.rs b/src/lib.rs index c224ab09d..51a72a1d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, diff --git a/src/location.rs b/src/location.rs index 7bb381497..52315585b 100644 --- a/src/location.rs +++ b/src/location.rs @@ -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(()) } diff --git a/src/message.rs b/src/message.rs index fd88cf628..b287e6c57 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1502,7 +1502,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> 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 { diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 0de80907c..6d291ae23 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -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; diff --git a/src/mimeparser.rs b/src/mimeparser.rs index e05e46ad6..000cdac0c 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -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? diff --git a/src/net.rs b/src/net.rs index 2d36e90aa..a0f3a3313 100644 --- a/src/net.rs +++ b/src/net.rs @@ -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. diff --git a/src/peerstate.rs b/src/peerstate.rs index 2913326fe..16378c918 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -718,7 +718,7 @@ pub(crate) async fn deduplicate_peerstates(sql: &Sql) -> Result<()> { FROM acpeerstates GROUP BY addr )", - paramsv![], + (), ) .await?; diff --git a/src/plaintext.rs b/src/plaintext.rs index e50311677..2808e68a8 100644 --- a/src/plaintext.rs +++ b/src/plaintext.rs @@ -34,8 +34,13 @@ impl PlainText { let lines = split_lines(&self.text); - let mut ret = - "\n\n".to_string(); + let mut ret = r#" + + + + +"# + .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##" - + + + + line 1
line 2
line with https://link-mid-of-line.org and http://link-end-of-line.com/file?foo=bar%20
@@ -140,7 +148,10 @@ line with https://link-mid-of-line.org - + + + + line with <http://encapsulated.link/?foo=_bar> here!
"# @@ -158,7 +169,10 @@ line with <http://encapsulated.l assert_eq!( html, r#" - + + + + line with nohttp://no.link here
"# @@ -176,7 +190,10 @@ line with nohttp://no.link here
assert_eq!( html, r#" - + + + + just an address:
foo@bar.org another@one.de
"# @@ -194,7 +211,10 @@ just an address: foo@bar.org - + + + + line still line
>quote
>still quote
@@ -215,7 +235,10 @@ line still line
assert_eq!( html, r#" - + + + + linestill line
>quote
>still quote
@@ -236,7 +259,10 @@ linestill line
assert_eq!( html, r#" - + + + + line
still line
>quote
diff --git a/src/receive_imf.rs b/src/receive_imf.rs index d30880bf6..c37eb3633 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -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={:?}", diff --git a/src/receive_imf/tests.rs b/src/receive_imf/tests.rs index 9cdcc2c20..ed3791fe7 100644 --- a/src/receive_imf/tests.rs +++ b/src/receive_imf/tests.rs @@ -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, diff --git a/src/securejoin/bobstate.rs b/src/securejoin/bobstate.rs index 9640e5d1c..b6584f6e1 100644 --- a/src/securejoin/bobstate.rs +++ b/src/securejoin/bobstate.rs @@ -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)?, diff --git a/src/simplify.rs b/src/simplify.rs index 7709dbf37..623c9ab63 100644 --- a/src/simplify.rs +++ b/src/simplify.rs @@ -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); diff --git a/src/sql.rs b/src/sql.rs index aec50171a..eff04a157 100644 --- a/src/sql.rs +++ b/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 { diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index 02f13ecf3..d23261862 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -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) diff --git a/src/sync.rs b/src/sync.rs index a977d1c57..2976a07fc 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -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(()) diff --git a/src/test_utils.rs b/src/test_utils.rs index eb5562db5..07fbef649 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -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!( diff --git a/src/webxdc.rs b/src/webxdc.rs index 1c9a8095b..bfaaa8741 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -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 );