diff --git a/CHANGELOG.md b/CHANGELOG.md index 33d183b9c..6d20774bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixes - Securejoin: Fix adding and handling Autocrypt-Gossip headers #3914 +- Emit DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK when the number of archived chats with + unread messages increases #3959 ### API-Changes - jsonrpc: add verified-by information to `Contact`-Object diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 9634a470a..32a355fad 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -2261,6 +2261,39 @@ def test_aeap_flow_verified(acfactory, lp): assert ac1new.get_config("addr") in [contact.addr for contact in msg_in_2.chat.get_contacts()] +def test_archived_muted_chat(acfactory, lp): + """If an archived and muted chat receives a new message, DC_EVENT_MSGS_CHANGED for + DC_CHAT_ID_ARCHIVED_LINK must be generated if the chat had only seen messages previously. + """ + ac1, ac2 = acfactory.get_online_accounts(2) + chat = acfactory.get_accepted_chat(ac1, ac2) + + lp.sec("ac1: send message to ac2") + chat.send_text("message0") + + lp.sec("wait for ac2 to receive message") + msg2 = ac2._evtracker.wait_next_incoming_message() + assert msg2.text == "message0" + msg2.mark_seen() + + chat2 = msg2.chat + chat2.archive() + chat2.mute() + + lp.sec("ac1: send another message to ac2") + chat.send_text("message1") + + lp.sec("wait for ac2 to receive DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK") + while 1: + ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") + if ev.data1 == const.DC_CHAT_ID_ARCHIVED_LINK: + assert ev.data2 == 0 + archive = ac2.get_chat_by_id(const.DC_CHAT_ID_ARCHIVED_LINK) + assert archive.count_fresh_messages() == 1 + assert chat2.count_fresh_messages() == 1 + break + + class TestOnlineConfigureFails: def test_invalid_password(self, acfactory): configdict = acfactory.get_next_liveconfig() diff --git a/src/chat.rs b/src/chat.rs index 042f10b2d..d80364b9f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -531,16 +531,50 @@ impl ChatId { Ok(()) } - // Unarchives a chat that is archived and not muted. - // Needed when a message is added to a chat so that the chat gets a normal visibility again. - // Sending an appropriate event is up to the caller. - pub async fn unarchive_if_not_muted(self, context: &Context) -> Result<()> { + /// Unarchives a chat that is archived and not muted. + /// Needed after a message is added to a chat so that the chat gets a normal visibility again. + /// `msg_state` is the state of the message. Matters only for incoming messages currently. For + /// multiple outgoing messages the function may be called once with MessageState::Undefined. + /// Sending an appropriate event is up to the caller. + /// Also emits DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK when the number of archived + /// chats with unread messages increases (which is possible if the chat is muted). + pub async fn unarchive_if_not_muted( + self, + context: &Context, + msg_state: MessageState, + ) -> Result<()> { + if msg_state != MessageState::InFresh { + context.sql.execute( + "UPDATE chats SET archived=0 WHERE id=? AND NOT(muted_until=-1 OR muted_until>?)", + paramsv![self, time()], + ).await?; + return Ok(()); + } + let chat = Chat::load_from_db(context, self).await?; + if chat.visibility != ChatVisibility::Archived { + return Ok(()); + } + if chat.is_muted() { + let unread_cnt = context + .sql + .count( + "SELECT COUNT(*) + FROM msgs + WHERE state=? + AND hidden=0 + AND chat_id=?", + paramsv![MessageState::InFresh, self], + ) + .await?; + if unread_cnt == 1 { + // Added the first unread message in the chat. + context.emit_msgs_changed(DC_CHAT_ID_ARCHIVED_LINK, MsgId::new(0)); + } + return Ok(()); + } context .sql - .execute( - "UPDATE chats SET archived=0 WHERE id=? AND archived=1 AND NOT(muted_until=-1 OR muted_until>?)", - paramsv![self, time()], - ) + .execute("UPDATE chats SET archived=0 WHERE id=?", paramsv![self]) .await?; Ok(()) } @@ -2019,7 +2053,9 @@ async fn prepare_msg_common( msg.state = change_state_to; prepare_msg_blob(context, msg).await?; - chat_id.unarchive_if_not_muted(context).await?; + if !msg.hidden { + chat_id.unarchive_if_not_muted(context, msg.state).await?; + } msg.id = chat .prepare_msg_raw( context, @@ -3211,7 +3247,9 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) let mut created_msgs: Vec = Vec::new(); let mut curr_timestamp: i64; - chat_id.unarchive_if_not_muted(context).await?; + chat_id + .unarchive_if_not_muted(context, MessageState::Undefined) + .await?; if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { if let Some(reason) = chat.why_cant_send(context).await? { bail!("cannot send to {}: {}", chat_id, reason); @@ -3414,7 +3452,6 @@ pub async fn add_device_msg_with_importance( let rfc724_mid = create_outgoing_rfc724_mid(None, "@device"); msg.try_calc_and_set_dimensions(context).await.ok(); prepare_msg_blob(context, msg).await?; - chat_id.unarchive_if_not_muted(context).await?; let timestamp_sent = create_smeared_timestamp(context).await; @@ -3434,6 +3471,7 @@ pub async fn add_device_msg_with_importance( } } + let state = MessageState::InFresh; let row_id = context .sql .insert( @@ -3457,7 +3495,7 @@ pub async fn add_device_msg_with_importance( timestamp_sent, timestamp_sent, // timestamp_sent equals timestamp_rcvd msg.viewtype, - MessageState::InFresh, + state, msg.text.as_ref().cloned().unwrap_or_default(), msg.param.to_string(), rfc724_mid, @@ -3466,6 +3504,9 @@ pub async fn add_device_msg_with_importance( .await?; msg_id = MsgId::new(u32::try_from(row_id)?); + if !msg.hidden { + chat_id.unarchive_if_not_muted(context, state).await?; + } } if let Some(label) = label { diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 55f099726..5ede0f424 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -1235,7 +1235,7 @@ SET rfc724_mid=excluded.rfc724_mid, chat_id=excluded.chat_id, replace_msg_id.delete_from_db(context).await?; } - chat_id.unarchive_if_not_muted(context).await?; + chat_id.unarchive_if_not_muted(context, state).await?; info!( context,