diff --git a/src/message.rs b/src/message.rs index 4325295e13..af15ddf310 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.rfc724_mid), + |row| { + let info_msg_id: MsgId = row.get(0)?; + let last_param: Params = row.get::<_, String>(1)?.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,10 @@ 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().copied()); + let mut msg_ids = Vec::from(msg_ids); - 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 +1795,11 @@ pub async fn delete_msgs_ex( "Cannot request deletion of unencrypted message for others" ); + if msg.viewtype == Viewtype::Webxdc { + let info_msgs = get_webxdc_info_messages(context, &msg).await?; + msg_ids_queue.extend(&info_msgs); + msg_ids.extend(info_msgs); + } modified_chat_ids.insert(msg.chat_id); deleted_rfc724_mid.push(msg.rfc724_mid.clone()); @@ -1808,11 +1845,11 @@ pub async fn delete_msgs_ex( .await?; } - for &msg_id in msg_ids { + for &msg_id in &msg_ids { 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; 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."); 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(()) } 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)]