From 3a03c9993c74cb8595c8831d34effadefa3c2463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kl=C3=A4hn?= Date: Sun, 20 Jul 2025 22:30:07 +0200 Subject: [PATCH 1/7] Extend delete queue with webxdc info messages This allocates a vector of size #messagesToDelete which should be no problem. --- src/message.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/message.rs b/src/message.rs index 4325295e13..03e3c183ca 100644 --- a/src/message.rs +++ b/src/message.rs @@ -2,6 +2,7 @@ use std::collections::BTreeSet; use std::collections::HashSet; +use std::collections::VecDeque; use std::path::{Path, PathBuf}; use std::str; @@ -1713,6 +1714,35 @@ pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Resu Ok(()) } +pub(crate) async fn get_webxdc_info_messages( + context: &Context, + msg: &Message, +) -> Result> { + let msg_ids = context + .sql + .query_map( + r#"SELECT id, param + FROM msgs + WHERE chat_id=?1 AND hidden=0 AND mime_in_reply_to = ?2 + "#, + (msg.chat_id, msg.id), + |row| { + let info_msg_id: MsgId = row.get(0)?; + let last_param: Params = row.get::<_, String>(2)?.parse().unwrap_or_default(); + Ok((info_msg_id, last_param)) + }, + |row| { + Ok(row + .filter_map(Result::ok) + .filter(|(_, param)| param.get_cmd() == SystemMessage::WebxdcInfoMessage) + .map(|(msg_id, _)| msg_id) + .collect::>()) + }, + ) + .await?; + Ok(msg_ids) +} + /// Do final events and jobs after batch deletion using calls to delete_msg_locally(). /// To avoid additional database queries, collecting data is up to the caller. pub(crate) async fn delete_msgs_locally_done( @@ -1751,8 +1781,9 @@ pub async fn delete_msgs_ex( let mut modified_chat_ids = HashSet::new(); let mut deleted_rfc724_mid = Vec::new(); let mut res = Ok(()); + let mut msg_ids_queue = VecDeque::from_iter(msg_ids.iter().cloned()); - for &msg_id in msg_ids { + while let Some(msg_id) = msg_ids_queue.pop_front() { let msg = Message::load_from_db(context, msg_id).await?; ensure!( !delete_for_all || msg.from_id == ContactId::SELF, @@ -1763,6 +1794,7 @@ pub async fn delete_msgs_ex( "Cannot request deletion of unencrypted message for others" ); + msg_ids_queue.extend(get_webxdc_info_messages(context, &msg).await?); modified_chat_ids.insert(msg.chat_id); deleted_rfc724_mid.push(msg.rfc724_mid.clone()); From 028b0211c9afbd00eaa0ad586eab21fe3a8f2e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kl=C3=A4hn?= Date: Tue, 22 Jul 2025 11:25:23 +0200 Subject: [PATCH 2/7] add failing test --- src/webxdc/webxdc_tests.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/webxdc/webxdc_tests.rs b/src/webxdc/webxdc_tests.rs index 35039459ed..787ecc6fe1 100644 --- a/src/webxdc/webxdc_tests.rs +++ b/src/webxdc/webxdc_tests.rs @@ -12,6 +12,7 @@ use crate::chatlist::Chatlist; use crate::config::Config; use crate::download::DownloadState; use crate::ephemeral; +use crate::message::delete_msgs; use crate::receive_imf::{receive_imf, receive_imf_from_inbox}; use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager}; use crate::tools::{self, SystemTime}; @@ -1616,6 +1617,26 @@ async fn test_webxdc_info_msg_no_cleanup_on_interrupted_series() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_webxdc_info_msg_delete() -> Result<()> { + let t = TestContext::new_alice().await; + let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "c").await?; + let instance = send_webxdc_instance(&t, chat_id).await?; + + t.send_webxdc_status_update(instance.id, r#"{"info":"i1", "payload":1}"#) + .await?; + assert_eq!(chat_id.get_msg_cnt(&t).await?, 2); + send_text_msg(&t, chat_id, "msg between info".to_string()).await?; + assert_eq!(chat_id.get_msg_cnt(&t).await?, 3); + t.send_webxdc_status_update(instance.id, r#"{"info":"i2", "payload":2}"#) + .await?; + assert_eq!(chat_id.get_msg_cnt(&t).await?, 4); + delete_msgs(&t, &[instance.id]).await?; + assert_eq!(chat_id.get_msg_cnt(&t).await?, 1); + + Ok(()) +} + // check that `info.internet_access` is not set for normal, non-integrated webxdc - // even if they use the deprecated option `request_internet_access` in manifest.toml #[tokio::test(flavor = "multi_thread", worker_threads = 2)] From 2db390b25af6ae676bf9c0357b21949bed8c23e7 Mon Sep 17 00:00:00 2001 From: Septias Date: Thu, 24 Jul 2025 16:00:29 +0200 Subject: [PATCH 3/7] fixes --- src/message.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/message.rs b/src/message.rs index 03e3c183ca..a7bf192c52 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1725,10 +1725,10 @@ pub(crate) async fn get_webxdc_info_messages( FROM msgs WHERE chat_id=?1 AND hidden=0 AND mime_in_reply_to = ?2 "#, - (msg.chat_id, msg.id), + (msg.chat_id, &msg.rfc724_mid), |row| { let info_msg_id: MsgId = row.get(0)?; - let last_param: Params = row.get::<_, String>(2)?.parse().unwrap_or_default(); + let last_param: Params = row.get::<_, String>(1)?.parse().unwrap_or_default(); Ok((info_msg_id, last_param)) }, |row| { @@ -1782,6 +1782,7 @@ pub async fn delete_msgs_ex( let mut deleted_rfc724_mid = Vec::new(); let mut res = Ok(()); let mut msg_ids_queue = VecDeque::from_iter(msg_ids.iter().cloned()); + let mut msg_ids = Vec::from(msg_ids); while let Some(msg_id) = msg_ids_queue.pop_front() { let msg = Message::load_from_db(context, msg_id).await?; @@ -1794,7 +1795,11 @@ pub async fn delete_msgs_ex( "Cannot request deletion of unencrypted message for others" ); - msg_ids_queue.extend(get_webxdc_info_messages(context, &msg).await?); + if msg.viewtype == Viewtype::Webxdc { + let info_msgs = get_webxdc_info_messages(context, &msg).await?; + msg_ids_queue.extend(info_msgs.clone()); + msg_ids.extend(info_msgs); + } modified_chat_ids.insert(msg.chat_id); deleted_rfc724_mid.push(msg.rfc724_mid.clone()); @@ -1840,11 +1845,11 @@ pub async fn delete_msgs_ex( .await?; } - for &msg_id in msg_ids { + for &msg_id in msg_ids.iter() { let msg = Message::load_from_db(context, msg_id).await?; delete_msg_locally(context, &msg).await?; } - delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?; + delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?; // Interrupt Inbox loop to start message deletion, run housekeeping and call send_sync_msg(). context.scheduler.interrupt_inbox().await; From 5096a438318f8614a163a95b6e3fc3ea24cde288 Mon Sep 17 00:00:00 2001 From: Septias Date: Thu, 24 Jul 2025 16:07:05 +0200 Subject: [PATCH 4/7] remove clone --- src/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message.rs b/src/message.rs index a7bf192c52..b7face1c69 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1797,7 +1797,7 @@ pub async fn delete_msgs_ex( if msg.viewtype == Viewtype::Webxdc { let info_msgs = get_webxdc_info_messages(context, &msg).await?; - msg_ids_queue.extend(info_msgs.clone()); + msg_ids_queue.extend(&info_msgs); msg_ids.extend(info_msgs); } modified_chat_ids.insert(msg.chat_id); From 0ce2f35909b093ce34dbcd4f2811ef5f76552ebd Mon Sep 17 00:00:00 2001 From: Septias Date: Thu, 24 Jul 2025 16:08:00 +0200 Subject: [PATCH 5/7] clippy --- src/message.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/message.rs b/src/message.rs index b7face1c69..af15ddf310 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1781,7 +1781,7 @@ pub async fn delete_msgs_ex( let mut modified_chat_ids = HashSet::new(); let mut deleted_rfc724_mid = Vec::new(); let mut res = Ok(()); - let mut msg_ids_queue = VecDeque::from_iter(msg_ids.iter().cloned()); + let mut msg_ids_queue = VecDeque::from_iter(msg_ids.iter().copied()); let mut msg_ids = Vec::from(msg_ids); while let Some(msg_id) = msg_ids_queue.pop_front() { @@ -1845,7 +1845,7 @@ pub async fn delete_msgs_ex( .await?; } - for &msg_id in msg_ids.iter() { + for &msg_id in &msg_ids { let msg = Message::load_from_db(context, msg_id).await?; delete_msg_locally(context, &msg).await?; } From c9fdfc32f2f428471d3f0b34fa4d328faac2ddd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kl=C3=A4hn?= Date: Sat, 9 Aug 2025 17:22:52 +0200 Subject: [PATCH 6/7] Delete info messages on delete requets When receiving a delete request for webxdcs, delete the associated info messages --- src/receive_imf.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 21dcf1c052..9dde7795c0 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -33,7 +33,8 @@ use crate::log::LogExt; use crate::log::{info, warn}; use crate::logged_debug_assert; use crate::message::{ - self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists, + self, Message, MessageState, MessengerMessage, MsgId, Viewtype, get_webxdc_info_messages, + rfc724_mid_exists, }; use crate::mimeparser::{AvatarAction, MimeMessage, SystemMessage, parse_message_ids}; use crate::param::{Param, Params}; @@ -2295,12 +2296,18 @@ async fn handle_edit_delete( let mut modified_chat_ids = HashSet::new(); let mut msg_ids = Vec::new(); + let mut deleted_info_msgs = vec![]; let rfc724_mid_vec: Vec<&str> = rfc724_mid_list.split_whitespace().collect(); for rfc724_mid in rfc724_mid_vec { if let Some((msg_id, _)) = message::rfc724_mid_exists(context, rfc724_mid).await? { if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? { + if msg.viewtype == Viewtype::Webxdc { + let info_msgs = get_webxdc_info_messages(context, &msg).await?; + deleted_info_msgs.extend(&info_msgs); + } + if msg.from_id == from_id { message::delete_msg_locally(context, &msg).await?; msg_ids.push(msg.id); @@ -2315,6 +2322,15 @@ async fn handle_edit_delete( warn!(context, "Delete message: {rfc724_mid:?} not found."); } } + if !deleted_info_msgs.is_empty() { + message::delete_msgs(context, &deleted_info_msgs).await?; + for msg_id in deleted_info_msgs { + if let Some(msg) = Message::load_from_db_optional(context, msg_id).await? { + message::delete_msg_locally(context, &msg).await?; + msg_ids.push(msg.id); + } + } + } message::delete_msgs_locally_done(context, &msg_ids, modified_chat_ids).await?; } else { warn!(context, "Delete message: Not encrypted."); From 7436368b505953d7603d01ecfbce58540ac268ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kl=C3=A4hn?= Date: Sat, 9 Aug 2025 17:23:12 +0200 Subject: [PATCH 7/7] Delete webxdc info messages in sync --- src/sync.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/sync.rs b/src/sync.rs index 90e302f06b..0c8c275f9a 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -11,7 +11,7 @@ use crate::contact::ContactId; use crate::context::Context; use crate::log::LogExt; use crate::log::{info, warn}; -use crate::message::{Message, MsgId, Viewtype}; +use crate::message::{Message, MsgId, Viewtype, get_webxdc_info_messages}; use crate::mimeparser::SystemMessage; use crate::param::Param; use crate::sync::SyncData::{AddQrToken, AlterChat, DeleteQrToken}; @@ -306,10 +306,15 @@ impl Context { async fn sync_message_deletion(&self, msgs: &Vec) -> Result<()> { let mut modified_chat_ids = HashSet::new(); + let mut deleted_info_msgs = vec![]; let mut msg_ids = Vec::new(); for rfc724_mid in msgs { if let Some((msg_id, _)) = message::rfc724_mid_exists(self, rfc724_mid).await? { if let Some(msg) = Message::load_from_db_optional(self, msg_id).await? { + if msg.viewtype == Viewtype::Webxdc { + let info_msgs = get_webxdc_info_messages(&self, &msg).await?; + deleted_info_msgs.extend(&info_msgs); + } message::delete_msg_locally(self, &msg).await?; msg_ids.push(msg.id); modified_chat_ids.insert(msg.chat_id); @@ -320,6 +325,15 @@ impl Context { warn!(self, "Sync message delete: {rfc724_mid:?} not found."); } } + if !deleted_info_msgs.is_empty() { + message::delete_msgs(&self, &deleted_info_msgs).await?; + for msg_id in deleted_info_msgs { + if let Some(msg) = Message::load_from_db_optional(&self, msg_id).await? { + message::delete_msg_locally(&self, &msg).await?; + msg_ids.push(msg.id); + } + } + } message::delete_msgs_locally_done(self, &msg_ids, modified_chat_ids).await?; Ok(()) }